Repository: ramensoftware/windhawk Branch: main Commit: b59b38cd77da Files: 429 Total size: 24.7 MB Directory structure: gitextract_zq1lf0ms/ ├── .gitattributes ├── .github/ │ ├── FUNDING.yml │ └── ISSUE_TEMPLATE/ │ ├── bug_report.md │ └── feature_request.md ├── LICENSE ├── README.md └── src/ ├── vscode-windhawk/ │ ├── .eslintrc.js │ ├── .gitignore │ ├── .prettierrc │ ├── .vscode/ │ │ ├── extensions.json │ │ ├── launch.json │ │ ├── settings.json │ │ └── tasks.json │ ├── .vscodeignore │ ├── LICENSE │ ├── README.md │ ├── files/ │ │ └── mod_template.wh.cpp │ ├── helper_scripts/ │ │ └── disable_default_keybindings.py │ ├── package.json │ ├── package.nls.json │ ├── src/ │ │ ├── config.ts │ │ ├── extension.ts │ │ ├── ini.ts │ │ ├── logOutputChannel.ts │ │ ├── storagePaths.ts │ │ ├── utils/ │ │ │ ├── appSettingsUtils.ts │ │ │ ├── compilerUtils.ts │ │ │ ├── editorWorkspaceUtils.ts │ │ │ ├── modConfigUtils.ts │ │ │ ├── modFilesUtils.ts │ │ │ ├── modSourceUtils.ts │ │ │ ├── trayProgramUtils.ts │ │ │ ├── updateUtils.ts │ │ │ └── userProfileUtils.ts │ │ ├── webviewIPC.ts │ │ └── webviewIPCMessages.ts │ ├── syntaxes/ │ │ └── cpp.injection.json │ ├── tsconfig.json │ └── webpack.config.js ├── vscode-windhawk-ui/ │ ├── .editorconfig │ ├── .eslintrc.json │ ├── .gitignore │ ├── .prettierignore │ ├── .prettierrc │ ├── .vscode/ │ │ └── extensions.json │ ├── apps/ │ │ ├── .gitkeep │ │ ├── vscode-windhawk-ui/ │ │ │ ├── .babelrc │ │ │ ├── .browserslistrc │ │ │ ├── .eslintrc.json │ │ │ ├── jest.config.ts │ │ │ ├── project.json │ │ │ ├── src/ │ │ │ │ ├── app/ │ │ │ │ │ ├── app.css │ │ │ │ │ ├── app.less │ │ │ │ │ ├── app.tsx │ │ │ │ │ ├── appUISettings.ts │ │ │ │ │ ├── components/ │ │ │ │ │ │ ├── EllipsisText.tsx │ │ │ │ │ │ ├── InputWithContextMenu.tsx │ │ │ │ │ │ └── ReactMarkdownCustom.tsx │ │ │ │ │ ├── i18n.ts │ │ │ │ │ ├── panel/ │ │ │ │ │ │ ├── About.tsx │ │ │ │ │ │ ├── AppHeader.tsx │ │ │ │ │ │ ├── ChangelogModal.tsx │ │ │ │ │ │ ├── CreateNewModButton.tsx │ │ │ │ │ │ ├── DevModeAction.tsx │ │ │ │ │ │ ├── ModCard.tsx │ │ │ │ │ │ ├── ModDetails.tsx │ │ │ │ │ │ ├── ModDetailsAdvanced.tsx │ │ │ │ │ │ ├── ModDetailsChangelog.tsx │ │ │ │ │ │ ├── ModDetailsHeader.tsx │ │ │ │ │ │ ├── ModDetailsReadme.tsx │ │ │ │ │ │ ├── ModDetailsSettings.spec.ts │ │ │ │ │ │ ├── ModDetailsSettings.tsx │ │ │ │ │ │ ├── ModDetailsSource.tsx │ │ │ │ │ │ ├── ModDetailsSourceDiff.tsx │ │ │ │ │ │ ├── ModMetadataLine.tsx │ │ │ │ │ │ ├── ModPreview.tsx │ │ │ │ │ │ ├── ModsBrowserLocal.tsx │ │ │ │ │ │ ├── ModsBrowserOnline.tsx │ │ │ │ │ │ ├── Panel.tsx │ │ │ │ │ │ ├── SafeModeIndicator.tsx │ │ │ │ │ │ ├── Settings.tsx │ │ │ │ │ │ ├── UpdateModal.tsx │ │ │ │ │ │ ├── VersionSelectorModal.tsx │ │ │ │ │ │ └── mockData.ts │ │ │ │ │ ├── sidebar/ │ │ │ │ │ │ ├── EditorModeControls.tsx │ │ │ │ │ │ ├── Sidebar.tsx │ │ │ │ │ │ └── mockData.ts │ │ │ │ │ ├── swrHelpers.ts │ │ │ │ │ ├── utils.spec.ts │ │ │ │ │ ├── utils.ts │ │ │ │ │ ├── vsCodeApi.ts │ │ │ │ │ ├── webviewIPC.ts │ │ │ │ │ └── webviewIPCMessages.ts │ │ │ │ ├── environments/ │ │ │ │ │ ├── environment.prod.ts │ │ │ │ │ └── environment.ts │ │ │ │ ├── index.html │ │ │ │ ├── locales/ │ │ │ │ │ ├── DO_NOT_EDIT.txt │ │ │ │ │ ├── ar/ │ │ │ │ │ │ ├── DO_NOT_EDIT.txt │ │ │ │ │ │ └── translation.json │ │ │ │ │ ├── cs/ │ │ │ │ │ │ ├── DO_NOT_EDIT.txt │ │ │ │ │ │ └── translation.json │ │ │ │ │ ├── da/ │ │ │ │ │ │ ├── DO_NOT_EDIT.txt │ │ │ │ │ │ └── translation.json │ │ │ │ │ ├── de/ │ │ │ │ │ │ ├── DO_NOT_EDIT.txt │ │ │ │ │ │ └── translation.json │ │ │ │ │ ├── el/ │ │ │ │ │ │ ├── DO_NOT_EDIT.txt │ │ │ │ │ │ └── translation.json │ │ │ │ │ ├── en/ │ │ │ │ │ │ ├── DO_NOT_EDIT.txt │ │ │ │ │ │ └── translation.json │ │ │ │ │ ├── es/ │ │ │ │ │ │ ├── DO_NOT_EDIT.txt │ │ │ │ │ │ └── translation.json │ │ │ │ │ ├── fr/ │ │ │ │ │ │ ├── DO_NOT_EDIT.txt │ │ │ │ │ │ └── translation.json │ │ │ │ │ ├── hi/ │ │ │ │ │ │ ├── DO_NOT_EDIT.txt │ │ │ │ │ │ └── translation.json │ │ │ │ │ ├── hr/ │ │ │ │ │ │ ├── DO_NOT_EDIT.txt │ │ │ │ │ │ └── translation.json │ │ │ │ │ ├── hu/ │ │ │ │ │ │ ├── DO_NOT_EDIT.txt │ │ │ │ │ │ └── translation.json │ │ │ │ │ ├── id/ │ │ │ │ │ │ ├── DO_NOT_EDIT.txt │ │ │ │ │ │ └── translation.json │ │ │ │ │ ├── it/ │ │ │ │ │ │ ├── DO_NOT_EDIT.txt │ │ │ │ │ │ └── translation.json │ │ │ │ │ ├── ja/ │ │ │ │ │ │ ├── DO_NOT_EDIT.txt │ │ │ │ │ │ └── translation.json │ │ │ │ │ ├── ko/ │ │ │ │ │ │ ├── DO_NOT_EDIT.txt │ │ │ │ │ │ └── translation.json │ │ │ │ │ ├── nl/ │ │ │ │ │ │ ├── DO_NOT_EDIT.txt │ │ │ │ │ │ └── translation.json │ │ │ │ │ ├── pl/ │ │ │ │ │ │ ├── DO_NOT_EDIT.txt │ │ │ │ │ │ └── translation.json │ │ │ │ │ ├── pt-BR/ │ │ │ │ │ │ ├── DO_NOT_EDIT.txt │ │ │ │ │ │ └── translation.json │ │ │ │ │ ├── ro/ │ │ │ │ │ │ ├── DO_NOT_EDIT.txt │ │ │ │ │ │ └── translation.json │ │ │ │ │ ├── ru/ │ │ │ │ │ │ ├── DO_NOT_EDIT.txt │ │ │ │ │ │ └── translation.json │ │ │ │ │ ├── sv/ │ │ │ │ │ │ ├── DO_NOT_EDIT.txt │ │ │ │ │ │ └── translation.json │ │ │ │ │ ├── ta/ │ │ │ │ │ │ ├── DO_NOT_EDIT.txt │ │ │ │ │ │ └── translation.json │ │ │ │ │ ├── th/ │ │ │ │ │ │ ├── DO_NOT_EDIT.txt │ │ │ │ │ │ └── translation.json │ │ │ │ │ ├── tr/ │ │ │ │ │ │ ├── DO_NOT_EDIT.txt │ │ │ │ │ │ └── translation.json │ │ │ │ │ ├── uk/ │ │ │ │ │ │ ├── DO_NOT_EDIT.txt │ │ │ │ │ │ └── translation.json │ │ │ │ │ ├── vi/ │ │ │ │ │ │ ├── DO_NOT_EDIT.txt │ │ │ │ │ │ └── translation.json │ │ │ │ │ ├── zh-CN/ │ │ │ │ │ │ ├── DO_NOT_EDIT.txt │ │ │ │ │ │ └── translation.json │ │ │ │ │ └── zh-TW/ │ │ │ │ │ ├── DO_NOT_EDIT.txt │ │ │ │ │ └── translation.json │ │ │ │ ├── main.css │ │ │ │ ├── main.tsx │ │ │ │ └── polyfills.ts │ │ │ ├── tsconfig.app.json │ │ │ ├── tsconfig.json │ │ │ ├── tsconfig.spec.json │ │ │ └── webpack.config.js │ │ └── vscode-windhawk-ui-e2e/ │ │ ├── .eslintrc.json │ │ ├── cypress.config.ts │ │ ├── project.json │ │ ├── src/ │ │ │ ├── e2e/ │ │ │ │ └── app.cy.ts │ │ │ ├── fixtures/ │ │ │ │ └── example.json │ │ │ └── support/ │ │ │ ├── app.po.ts │ │ │ ├── commands.ts │ │ │ └── e2e.ts │ │ └── tsconfig.json │ ├── babel.config.json │ ├── jest.config.ts │ ├── jest.preset.js │ ├── libs/ │ │ └── .gitkeep │ ├── nx.json │ ├── package.json │ ├── tools/ │ │ ├── generators/ │ │ │ └── .gitkeep │ │ └── tsconfig.tools.json │ └── tsconfig.base.json └── windhawk/ ├── .clang-format ├── .gitattributes ├── .gitignore ├── _typos.toml ├── app/ │ ├── app.cpp │ ├── app.vcxproj │ ├── app.vcxproj.filters │ ├── engine_control.cpp │ ├── engine_control.h │ ├── event_viewer_crash_monitor.cpp │ ├── event_viewer_crash_monitor.h │ ├── functions.cpp │ ├── functions.h │ ├── libraries/ │ │ ├── nlohmann/ │ │ │ └── json.hpp │ │ └── winhttpwrappers/ │ │ └── WinHTTPWrappers.h │ ├── logger.cpp │ ├── logger.h │ ├── main_window.cpp │ ├── main_window.h │ ├── packages.config │ ├── resource.h │ ├── rsrc/ │ │ └── compatibility.manifest │ ├── rsrc.rc │ ├── rsrc.rc2 │ ├── service.cpp │ ├── service.h │ ├── service_common.h │ ├── stdafx.cpp │ ├── stdafx.h │ ├── storage_manager.cpp │ ├── storage_manager.h │ ├── task_manager_dlg.cpp │ ├── task_manager_dlg.h │ ├── toolkit_dlg.cpp │ ├── toolkit_dlg.h │ ├── tray_icon.cpp │ ├── tray_icon.h │ ├── ui_control.cpp │ ├── ui_control.h │ ├── update_checker.cpp │ ├── update_checker.h │ ├── userprofile.cpp │ ├── userprofile.h │ └── winhttpsimple.h ├── build.bat ├── engine/ │ ├── _exports.def │ ├── all_processes_injector.cpp │ ├── all_processes_injector.h │ ├── customization_session.cpp │ ├── customization_session.h │ ├── dll_inject.cpp │ ├── dll_inject.h │ ├── engine.vcxproj │ ├── engine.vcxproj.filters │ ├── functions.cpp │ ├── functions.h │ ├── inject_shellcode/ │ │ ├── .clang-format │ │ ├── InjectShellcode.filters │ │ ├── InjectShellcode.sln │ │ ├── InjectShellcode.vcxproj │ │ └── main.cpp │ ├── libraries/ │ │ ├── MinHook/ │ │ │ ├── include/ │ │ │ │ └── MinHook.h │ │ │ └── src/ │ │ │ ├── buffer.c │ │ │ ├── buffer.h │ │ │ ├── hde/ │ │ │ │ ├── hde32.c │ │ │ │ ├── hde32.h │ │ │ │ ├── hde64.c │ │ │ │ ├── hde64.h │ │ │ │ ├── pstdint.h │ │ │ │ ├── table32.h │ │ │ │ └── table64.h │ │ │ ├── hook.c │ │ │ ├── trampoline.c │ │ │ └── trampoline.h │ │ ├── MinHook-Detours/ │ │ │ ├── MinHook.c │ │ │ ├── MinHook.h │ │ │ └── SlimDetours/ │ │ │ ├── .editorconfig │ │ │ ├── Disassembler.c │ │ │ ├── InlineHook.c │ │ │ ├── Instruction.c │ │ │ ├── LICENSE │ │ │ ├── Memory.c │ │ │ ├── SlimDetours.NDK.inl │ │ │ ├── SlimDetours.h │ │ │ ├── SlimDetours.inl │ │ │ ├── Thread.c │ │ │ ├── Trampoline.c │ │ │ └── Transaction.c │ │ ├── ThreadLocal.h │ │ ├── Zydis/ │ │ │ ├── Zydis.c │ │ │ └── Zydis.h │ │ ├── binaryninja-arm64-disassembler/ │ │ │ ├── arm64dis.h │ │ │ ├── decode.c │ │ │ ├── decode.h │ │ │ ├── decode0.c │ │ │ ├── decode1.c │ │ │ ├── decode1.h │ │ │ ├── decode2.c │ │ │ ├── decode2.h │ │ │ ├── decode_fields32.c │ │ │ ├── decode_fields32.h │ │ │ ├── decode_scratchpad.c │ │ │ ├── decompose_and_disassemble.c │ │ │ ├── decompose_and_disassemble.h │ │ │ ├── encodings_dec.c │ │ │ ├── encodings_dec.h │ │ │ ├── encodings_fmt.c │ │ │ ├── encodings_fmt.h │ │ │ ├── feature_flags.h │ │ │ ├── format.c │ │ │ ├── format.h │ │ │ ├── gofer.c │ │ │ ├── operations.c │ │ │ ├── operations.h │ │ │ ├── pcode.c │ │ │ ├── pcode.h │ │ │ ├── regs.c │ │ │ ├── regs.h │ │ │ ├── sysregs.c │ │ │ ├── sysregs.h │ │ │ ├── sysregs_fmt_gen.c │ │ │ ├── sysregs_fmt_gen.h │ │ │ ├── sysregs_gen.c │ │ │ └── sysregs_gen.h │ │ ├── dia/ │ │ │ ├── cvconst.h │ │ │ ├── dia2.h │ │ │ ├── diacreate.h │ │ │ └── lib/ │ │ │ ├── amd64/ │ │ │ │ └── diaguids.lib │ │ │ ├── arm/ │ │ │ │ └── diaguids.lib │ │ │ ├── arm64/ │ │ │ │ └── diaguids.lib │ │ │ └── diaguids.lib │ │ ├── phnt/ │ │ │ ├── CMakeLists.txt │ │ │ ├── LICENSE │ │ │ ├── README.md │ │ │ ├── ntafd.h │ │ │ ├── ntbcd.h │ │ │ ├── ntdbg.h │ │ │ ├── ntexapi.h │ │ │ ├── ntgdi.h │ │ │ ├── ntimage.h │ │ │ ├── ntintsafe.h │ │ │ ├── ntioapi.h │ │ │ ├── ntkeapi.h │ │ │ ├── ntldr.h │ │ │ ├── ntlpcapi.h │ │ │ ├── ntmisc.h │ │ │ ├── ntmmapi.h │ │ │ ├── ntnls.h │ │ │ ├── ntobapi.h │ │ │ ├── ntpebteb.h │ │ │ ├── ntpfapi.h │ │ │ ├── ntpnpapi.h │ │ │ ├── ntpoapi.h │ │ │ ├── ntpsapi.h │ │ │ ├── ntregapi.h │ │ │ ├── ntrtl.h │ │ │ ├── ntsam.h │ │ │ ├── ntseapi.h │ │ │ ├── ntsmss.h │ │ │ ├── ntstrsafe.h │ │ │ ├── ntsxs.h │ │ │ ├── nttmapi.h │ │ │ ├── nttp.h │ │ │ ├── ntuser.h │ │ │ ├── ntwmi.h │ │ │ ├── ntwow64.h │ │ │ ├── ntxcapi.h │ │ │ ├── ntzwapi.h │ │ │ ├── phnt.h │ │ │ ├── phnt_ntdef.h │ │ │ ├── phnt_windows.h │ │ │ ├── smbios.h │ │ │ ├── subprocesstag.h │ │ │ ├── usermgr.h │ │ │ └── winsta.h │ │ ├── thread-call-stack-scanner/ │ │ │ ├── Memory.c │ │ │ ├── Memory.h │ │ │ ├── Thread.c │ │ │ ├── Thread.h │ │ │ ├── ThreadsCallStackIterate.c │ │ │ ├── ThreadsCallStackIterate.h │ │ │ ├── ThreadsCallStackWaitForRegions.c │ │ │ └── ThreadsCallStackWaitForRegions.h │ │ └── wow64pp/ │ │ └── wow64pp.hpp │ ├── logger.cpp │ ├── logger.h │ ├── main.cpp │ ├── mod.cpp │ ├── mod.h │ ├── mods_api.cpp │ ├── mods_api.h │ ├── mods_api_internal.h │ ├── mods_manager.cpp │ ├── mods_manager.h │ ├── new_process_injector.cpp │ ├── new_process_injector.h │ ├── no_destructor.cpp │ ├── no_destructor.h │ ├── process_lists.h │ ├── resource.h │ ├── rsrc.rc │ ├── rsrc.rc2 │ ├── session_private_namespace.cpp │ ├── session_private_namespace.h │ ├── stdafx.cpp │ ├── stdafx.h │ ├── storage_manager.cpp │ ├── storage_manager.h │ ├── symbol_enum.cpp │ ├── symbol_enum.h │ └── var_init_once.h ├── shared/ │ ├── libraries/ │ │ └── wil/ │ │ ├── Tracelogging.h │ │ ├── _version.txt │ │ ├── com.h │ │ ├── com_apartment_variable.h │ │ ├── common.h │ │ ├── coroutine.h │ │ ├── cppwinrt.h │ │ ├── cppwinrt_authoring.h │ │ ├── cppwinrt_helpers.h │ │ ├── cppwinrt_notifiable_module_lock.h │ │ ├── cppwinrt_register_com_server.h │ │ ├── cppwinrt_wrl.h │ │ ├── filesystem.h │ │ ├── nt_result_macros.h │ │ ├── registry.h │ │ ├── registry_helpers.h │ │ ├── resource.h │ │ ├── result.h │ │ ├── result_macros.h │ │ ├── result_originate.h │ │ ├── rpc_helpers.h │ │ ├── safecast.h │ │ ├── stl.h │ │ ├── token_helpers.h │ │ ├── traceloggingconfig.h │ │ ├── win32_helpers.h │ │ ├── win32_result_macros.h │ │ ├── windowing.h │ │ ├── winrt.h │ │ ├── wistd_config.h │ │ ├── wistd_functional.h │ │ ├── wistd_memory.h │ │ ├── wistd_type_traits.h │ │ └── wrl.h │ ├── logger_base.cpp │ ├── logger_base.h │ ├── portable_settings.cpp │ ├── portable_settings.h │ └── version.h └── windhawk.sln ================================================ FILE CONTENTS ================================================ ================================================ FILE: .gitattributes ================================================ # Apply override to all files and directories in the directory # https://github.com/github-linguist/linguist/blob/master/docs/overrides.md#vendored-code /src/windhawk/*/libraries/** linguist-vendored /src/vscode-windhawk-ui/apps/vscode-windhawk-ui/src/app/app.css linguist-vendored ================================================ FILE: .github/FUNDING.yml ================================================ # These are supported funding model platforms github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] patreon: # Replace with a single Patreon username open_collective: # Replace with a single Open Collective username ko_fi: # Replace with a single Ko-fi username tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry liberapay: # Replace with a single Liberapay username issuehunt: # Replace with a single IssueHunt username otechie: # Replace with a single Otechie username custom: ['https://ramensoftware.com/donate'] ================================================ FILE: .github/ISSUE_TEMPLATE/bug_report.md ================================================ --- name: Bug report about: Report a bug in Windhawk title: '' labels: bug assignees: '' --- ================================================ FILE: .github/ISSUE_TEMPLATE/feature_request.md ================================================ --- name: Feature request about: Suggest an idea for Windhawk title: '' labels: enhancement assignees: '' --- ================================================ FILE: LICENSE ================================================ GNU GENERAL PUBLIC LICENSE Version 3, 29 June 2007 Copyright (C) 2007 Free Software Foundation, Inc. Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Preamble The GNU General Public License is a free, copyleft license for software and other kinds of works. The licenses for most software and other practical works are designed to take away your freedom to share and change the works. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change all versions of a program--to make sure it remains free software for all its users. We, the Free Software Foundation, use the GNU General Public License for most of our software; it applies also to any other work released this way by its authors. You can apply it to your programs, too. When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for them if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs, and that you know you can do these things. To protect your rights, we need to prevent others from denying you these rights or asking you to surrender the rights. Therefore, you have certain responsibilities if you distribute copies of the software, or if you modify it: responsibilities to respect the freedom of others. For example, if you distribute copies of such a program, whether gratis or for a fee, you must pass on to the recipients the same freedoms that you received. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights. Developers that use the GNU GPL protect your rights with two steps: (1) assert copyright on the software, and (2) offer you this License giving you legal permission to copy, distribute and/or modify it. For the developers' and authors' protection, the GPL clearly explains that there is no warranty for this free software. For both users' and authors' sake, the GPL requires that modified versions be marked as changed, so that their problems will not be attributed erroneously to authors of previous versions. Some devices are designed to deny users access to install or run modified versions of the software inside them, although the manufacturer can do so. This is fundamentally incompatible with the aim of protecting users' freedom to change the software. The systematic pattern of such abuse occurs in the area of products for individuals to use, which is precisely where it is most unacceptable. Therefore, we have designed this version of the GPL to prohibit the practice for those products. If such problems arise substantially in other domains, we stand ready to extend this provision to those domains in future versions of the GPL, as needed to protect the freedom of users. Finally, every program is threatened constantly by software patents. States should not allow patents to restrict development and use of software on general-purpose computers, but in those that do, we wish to avoid the special danger that patents applied to a free program could make it effectively proprietary. To prevent this, the GPL assures that patents cannot be used to render the program non-free. The precise terms and conditions for copying, distribution and modification follow. TERMS AND CONDITIONS 0. Definitions. "This License" refers to version 3 of the GNU General Public License. "Copyright" also means copyright-like laws that apply to other kinds of works, such as semiconductor masks. "The Program" refers to any copyrightable work licensed under this License. Each licensee is addressed as "you". "Licensees" and "recipients" may be individuals or organizations. To "modify" a work means to copy from or adapt all or part of the work in a fashion requiring copyright permission, other than the making of an exact copy. The resulting work is called a "modified version" of the earlier work or a work "based on" the earlier work. A "covered work" means either the unmodified Program or a work based on the Program. To "propagate" a work means to do anything with it that, without permission, would make you directly or secondarily liable for infringement under applicable copyright law, except executing it on a computer or modifying a private copy. Propagation includes copying, distribution (with or without modification), making available to the public, and in some countries other activities as well. To "convey" a work means any kind of propagation that enables other parties to make or receive copies. Mere interaction with a user through a computer network, with no transfer of a copy, is not conveying. An interactive user interface displays "Appropriate Legal Notices" to the extent that it includes a convenient and prominently visible feature that (1) displays an appropriate copyright notice, and (2) tells the user that there is no warranty for the work (except to the extent that warranties are provided), that licensees may convey the work under this License, and how to view a copy of this License. If the interface presents a list of user commands or options, such as a menu, a prominent item in the list meets this criterion. 1. Source Code. The "source code" for a work means the preferred form of the work for making modifications to it. "Object code" means any non-source form of a work. A "Standard Interface" means an interface that either is an official standard defined by a recognized standards body, or, in the case of interfaces specified for a particular programming language, one that is widely used among developers working in that language. The "System Libraries" of an executable work include anything, other than the work as a whole, that (a) is included in the normal form of packaging a Major Component, but which is not part of that Major Component, and (b) serves only to enable use of the work with that Major Component, or to implement a Standard Interface for which an implementation is available to the public in source code form. A "Major Component", in this context, means a major essential component (kernel, window system, and so on) of the specific operating system (if any) on which the executable work runs, or a compiler used to produce the work, or an object code interpreter used to run it. The "Corresponding Source" for a work in object code form means all the source code needed to generate, install, and (for an executable work) run the object code and to modify the work, including scripts to control those activities. However, it does not include the work's System Libraries, or general-purpose tools or generally available free programs which are used unmodified in performing those activities but which are not part of the work. For example, Corresponding Source includes interface definition files associated with source files for the work, and the source code for shared libraries and dynamically linked subprograms that the work is specifically designed to require, such as by intimate data communication or control flow between those subprograms and other parts of the work. The Corresponding Source need not include anything that users can regenerate automatically from other parts of the Corresponding Source. The Corresponding Source for a work in source code form is that same work. 2. Basic Permissions. All rights granted under this License are granted for the term of copyright on the Program, and are irrevocable provided the stated conditions are met. This License explicitly affirms your unlimited permission to run the unmodified Program. The output from running a covered work is covered by this License only if the output, given its content, constitutes a covered work. This License acknowledges your rights of fair use or other equivalent, as provided by copyright law. You may make, run and propagate covered works that you do not convey, without conditions so long as your license otherwise remains in force. You may convey covered works to others for the sole purpose of having them make modifications exclusively for you, or provide you with facilities for running those works, provided that you comply with the terms of this License in conveying all material for which you do not control copyright. Those thus making or running the covered works for you must do so exclusively on your behalf, under your direction and control, on terms that prohibit them from making any copies of your copyrighted material outside their relationship with you. Conveying under any other circumstances is permitted solely under the conditions stated below. Sublicensing is not allowed; section 10 makes it unnecessary. 3. Protecting Users' Legal Rights From Anti-Circumvention Law. No covered work shall be deemed part of an effective technological measure under any applicable law fulfilling obligations under article 11 of the WIPO copyright treaty adopted on 20 December 1996, or similar laws prohibiting or restricting circumvention of such measures. When you convey a covered work, you waive any legal power to forbid circumvention of technological measures to the extent such circumvention is effected by exercising rights under this License with respect to the covered work, and you disclaim any intention to limit operation or modification of the work as a means of enforcing, against the work's users, your or third parties' legal rights to forbid circumvention of technological measures. 4. Conveying Verbatim Copies. You may convey verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice; keep intact all notices stating that this License and any non-permissive terms added in accord with section 7 apply to the code; keep intact all notices of the absence of any warranty; and give all recipients a copy of this License along with the Program. You may charge any price or no price for each copy that you convey, and you may offer support or warranty protection for a fee. 5. Conveying Modified Source Versions. You may convey a work based on the Program, or the modifications to produce it from the Program, in the form of source code under the terms of section 4, provided that you also meet all of these conditions: a) The work must carry prominent notices stating that you modified it, and giving a relevant date. b) The work must carry prominent notices stating that it is released under this License and any conditions added under section 7. This requirement modifies the requirement in section 4 to "keep intact all notices". c) You must license the entire work, as a whole, under this License to anyone who comes into possession of a copy. This License will therefore apply, along with any applicable section 7 additional terms, to the whole of the work, and all its parts, regardless of how they are packaged. This License gives no permission to license the work in any other way, but it does not invalidate such permission if you have separately received it. d) If the work has interactive user interfaces, each must display Appropriate Legal Notices; however, if the Program has interactive interfaces that do not display Appropriate Legal Notices, your work need not make them do so. A compilation of a covered work with other separate and independent works, which are not by their nature extensions of the covered work, and which are not combined with it such as to form a larger program, in or on a volume of a storage or distribution medium, is called an "aggregate" if the compilation and its resulting copyright are not used to limit the access or legal rights of the compilation's users beyond what the individual works permit. Inclusion of a covered work in an aggregate does not cause this License to apply to the other parts of the aggregate. 6. Conveying Non-Source Forms. You may convey a covered work in object code form under the terms of sections 4 and 5, provided that you also convey the machine-readable Corresponding Source under the terms of this License, in one of these ways: a) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by the Corresponding Source fixed on a durable physical medium customarily used for software interchange. b) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by a written offer, valid for at least three years and valid for as long as you offer spare parts or customer support for that product model, to give anyone who possesses the object code either (1) a copy of the Corresponding Source for all the software in the product that is covered by this License, on a durable physical medium customarily used for software interchange, for a price no more than your reasonable cost of physically performing this conveying of source, or (2) access to copy the Corresponding Source from a network server at no charge. c) Convey individual copies of the object code with a copy of the written offer to provide the Corresponding Source. This alternative is allowed only occasionally and noncommercially, and only if you received the object code with such an offer, in accord with subsection 6b. d) Convey the object code by offering access from a designated place (gratis or for a charge), and offer equivalent access to the Corresponding Source in the same way through the same place at no further charge. You need not require recipients to copy the Corresponding Source along with the object code. If the place to copy the object code is a network server, the Corresponding Source may be on a different server (operated by you or a third party) that supports equivalent copying facilities, provided you maintain clear directions next to the object code saying where to find the Corresponding Source. Regardless of what server hosts the Corresponding Source, you remain obligated to ensure that it is available for as long as needed to satisfy these requirements. e) Convey the object code using peer-to-peer transmission, provided you inform other peers where the object code and Corresponding Source of the work are being offered to the general public at no charge under subsection 6d. A separable portion of the object code, whose source code is excluded from the Corresponding Source as a System Library, need not be included in conveying the object code work. A "User Product" is either (1) a "consumer product", which means any tangible personal property which is normally used for personal, family, or household purposes, or (2) anything designed or sold for incorporation into a dwelling. In determining whether a product is a consumer product, doubtful cases shall be resolved in favor of coverage. For a particular product received by a particular user, "normally used" refers to a typical or common use of that class of product, regardless of the status of the particular user or of the way in which the particular user actually uses, or expects or is expected to use, the product. A product is a consumer product regardless of whether the product has substantial commercial, industrial or non-consumer uses, unless such uses represent the only significant mode of use of the product. "Installation Information" for a User Product means any methods, procedures, authorization keys, or other information required to install and execute modified versions of a covered work in that User Product from a modified version of its Corresponding Source. The information must suffice to ensure that the continued functioning of the modified object code is in no case prevented or interfered with solely because modification has been made. If you convey an object code work under this section in, or with, or specifically for use in, a User Product, and the conveying occurs as part of a transaction in which the right of possession and use of the User Product is transferred to the recipient in perpetuity or for a fixed term (regardless of how the transaction is characterized), the Corresponding Source conveyed under this section must be accompanied by the Installation Information. But this requirement does not apply if neither you nor any third party retains the ability to install modified object code on the User Product (for example, the work has been installed in ROM). The requirement to provide Installation Information does not include a requirement to continue to provide support service, warranty, or updates for a work that has been modified or installed by the recipient, or for the User Product in which it has been modified or installed. Access to a network may be denied when the modification itself materially and adversely affects the operation of the network or violates the rules and protocols for communication across the network. Corresponding Source conveyed, and Installation Information provided, in accord with this section must be in a format that is publicly documented (and with an implementation available to the public in source code form), and must require no special password or key for unpacking, reading or copying. 7. Additional Terms. "Additional permissions" are terms that supplement the terms of this License by making exceptions from one or more of its conditions. Additional permissions that are applicable to the entire Program shall be treated as though they were included in this License, to the extent that they are valid under applicable law. If additional permissions apply only to part of the Program, that part may be used separately under those permissions, but the entire Program remains governed by this License without regard to the additional permissions. When you convey a copy of a covered work, you may at your option remove any additional permissions from that copy, or from any part of it. (Additional permissions may be written to require their own removal in certain cases when you modify the work.) You may place additional permissions on material, added by you to a covered work, for which you have or can give appropriate copyright permission. Notwithstanding any other provision of this License, for material you add to a covered work, you may (if authorized by the copyright holders of that material) supplement the terms of this License with terms: a) Disclaiming warranty or limiting liability differently from the terms of sections 15 and 16 of this License; or b) Requiring preservation of specified reasonable legal notices or author attributions in that material or in the Appropriate Legal Notices displayed by works containing it; or c) Prohibiting misrepresentation of the origin of that material, or requiring that modified versions of such material be marked in reasonable ways as different from the original version; or d) Limiting the use for publicity purposes of names of licensors or authors of the material; or e) Declining to grant rights under trademark law for use of some trade names, trademarks, or service marks; or f) Requiring indemnification of licensors and authors of that material by anyone who conveys the material (or modified versions of it) with contractual assumptions of liability to the recipient, for any liability that these contractual assumptions directly impose on those licensors and authors. All other non-permissive additional terms are considered "further restrictions" within the meaning of section 10. If the Program as you received it, or any part of it, contains a notice stating that it is governed by this License along with a term that is a further restriction, you may remove that term. If a license document contains a further restriction but permits relicensing or conveying under this License, you may add to a covered work material governed by the terms of that license document, provided that the further restriction does not survive such relicensing or conveying. If you add terms to a covered work in accord with this section, you must place, in the relevant source files, a statement of the additional terms that apply to those files, or a notice indicating where to find the applicable terms. Additional terms, permissive or non-permissive, may be stated in the form of a separately written license, or stated as exceptions; the above requirements apply either way. 8. Termination. You may not propagate or modify a covered work except as expressly provided under this License. Any attempt otherwise to propagate or modify it is void, and will automatically terminate your rights under this License (including any patent licenses granted under the third paragraph of section 11). However, if you cease all violation of this License, then your license from a particular copyright holder is reinstated (a) provisionally, unless and until the copyright holder explicitly and finally terminates your license, and (b) permanently, if the copyright holder fails to notify you of the violation by some reasonable means prior to 60 days after the cessation. Moreover, your license from a particular copyright holder is reinstated permanently if the copyright holder notifies you of the violation by some reasonable means, this is the first time you have received notice of violation of this License (for any work) from that copyright holder, and you cure the violation prior to 30 days after your receipt of the notice. Termination of your rights under this section does not terminate the licenses of parties who have received copies or rights from you under this License. If your rights have been terminated and not permanently reinstated, you do not qualify to receive new licenses for the same material under section 10. 9. Acceptance Not Required for Having Copies. You are not required to accept this License in order to receive or run a copy of the Program. Ancillary propagation of a covered work occurring solely as a consequence of using peer-to-peer transmission to receive a copy likewise does not require acceptance. However, nothing other than this License grants you permission to propagate or modify any covered work. These actions infringe copyright if you do not accept this License. Therefore, by modifying or propagating a covered work, you indicate your acceptance of this License to do so. 10. Automatic Licensing of Downstream Recipients. Each time you convey a covered work, the recipient automatically receives a license from the original licensors, to run, modify and propagate that work, subject to this License. You are not responsible for enforcing compliance by third parties with this License. An "entity transaction" is a transaction transferring control of an organization, or substantially all assets of one, or subdividing an organization, or merging organizations. If propagation of a covered work results from an entity transaction, each party to that transaction who receives a copy of the work also receives whatever licenses to the work the party's predecessor in interest had or could give under the previous paragraph, plus a right to possession of the Corresponding Source of the work from the predecessor in interest, if the predecessor has it or can get it with reasonable efforts. You may not impose any further restrictions on the exercise of the rights granted or affirmed under this License. For example, you may not impose a license fee, royalty, or other charge for exercise of rights granted under this License, and you may not initiate litigation (including a cross-claim or counterclaim in a lawsuit) alleging that any patent claim is infringed by making, using, selling, offering for sale, or importing the Program or any portion of it. 11. Patents. A "contributor" is a copyright holder who authorizes use under this License of the Program or a work on which the Program is based. The work thus licensed is called the contributor's "contributor version". A contributor's "essential patent claims" are all patent claims owned or controlled by the contributor, whether already acquired or hereafter acquired, that would be infringed by some manner, permitted by this License, of making, using, or selling its contributor version, but do not include claims that would be infringed only as a consequence of further modification of the contributor version. For purposes of this definition, "control" includes the right to grant patent sublicenses in a manner consistent with the requirements of this License. Each contributor grants you a non-exclusive, worldwide, royalty-free patent license under the contributor's essential patent claims, to make, use, sell, offer for sale, import and otherwise run, modify and propagate the contents of its contributor version. In the following three paragraphs, a "patent license" is any express agreement or commitment, however denominated, not to enforce a patent (such as an express permission to practice a patent or covenant not to sue for patent infringement). To "grant" such a patent license to a party means to make such an agreement or commitment not to enforce a patent against the party. If you convey a covered work, knowingly relying on a patent license, and the Corresponding Source of the work is not available for anyone to copy, free of charge and under the terms of this License, through a publicly available network server or other readily accessible means, then you must either (1) cause the Corresponding Source to be so available, or (2) arrange to deprive yourself of the benefit of the patent license for this particular work, or (3) arrange, in a manner consistent with the requirements of this License, to extend the patent license to downstream recipients. "Knowingly relying" means you have actual knowledge that, but for the patent license, your conveying the covered work in a country, or your recipient's use of the covered work in a country, would infringe one or more identifiable patents in that country that you have reason to believe are valid. If, pursuant to or in connection with a single transaction or arrangement, you convey, or propagate by procuring conveyance of, a covered work, and grant a patent license to some of the parties receiving the covered work authorizing them to use, propagate, modify or convey a specific copy of the covered work, then the patent license you grant is automatically extended to all recipients of the covered work and works based on it. A patent license is "discriminatory" if it does not include within the scope of its coverage, prohibits the exercise of, or is conditioned on the non-exercise of one or more of the rights that are specifically granted under this License. You may not convey a covered work if you are a party to an arrangement with a third party that is in the business of distributing software, under which you make payment to the third party based on the extent of your activity of conveying the work, and under which the third party grants, to any of the parties who would receive the covered work from you, a discriminatory patent license (a) in connection with copies of the covered work conveyed by you (or copies made from those copies), or (b) primarily for and in connection with specific products or compilations that contain the covered work, unless you entered into that arrangement, or that patent license was granted, prior to 28 March 2007. Nothing in this License shall be construed as excluding or limiting any implied license or other defenses to infringement that may otherwise be available to you under applicable patent law. 12. No Surrender of Others' Freedom. If conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot convey a covered work so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not convey it at all. For example, if you agree to terms that obligate you to collect a royalty for further conveying from those to whom you convey the Program, the only way you could satisfy both those terms and this License would be to refrain entirely from conveying the Program. 13. Use with the GNU Affero General Public License. Notwithstanding any other provision of this License, you have permission to link or combine any covered work with a work licensed under version 3 of the GNU Affero General Public License into a single combined work, and to convey the resulting work. The terms of this License will continue to apply to the part which is the covered work, but the special requirements of the GNU Affero General Public License, section 13, concerning interaction through a network will apply to the combination as such. 14. Revised Versions of this License. The Free Software Foundation may publish revised and/or new versions of the GNU General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Program specifies that a certain numbered version of the GNU General Public License "or any later version" applies to it, you have the option of following the terms and conditions either of that numbered version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of the GNU General Public License, you may choose any version ever published by the Free Software Foundation. If the Program specifies that a proxy can decide which future versions of the GNU General Public License can be used, that proxy's public statement of acceptance of a version permanently authorizes you to choose that version for the Program. Later license versions may give you additional or different permissions. However, no additional obligations are imposed on any author or copyright holder as a result of your choosing to follow a later version. 15. Disclaimer of Warranty. THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 16. Limitation of Liability. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. 17. Interpretation of Sections 15 and 16. If the disclaimer of warranty and limitation of liability provided above cannot be given local legal effect according to their terms, reviewing courts shall apply local law that most closely approximates an absolute waiver of all civil liability in connection with the Program, unless a warranty or assumption of liability accompanies a copy of the Program in return for a fee. END OF TERMS AND CONDITIONS How to Apply These Terms to Your New Programs If you develop a new program, and you want it to be of the greatest possible use to the public, the best way to achieve this is to make it free software which everyone can redistribute and change under these terms. To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively state the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. Windhawk, The customization marketplace for Windows programs. Copyright (C) 2023 Michael Maltsev This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . Also add information on how to contact you by electronic and paper mail. If the program does terminal interaction, make it output a short notice like this when it starts in an interactive mode: Windhawk Copyright (C) 2023 Michael Maltsev This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. This is free software, and you are welcome to redistribute it under certain conditions; type `show c' for details. The hypothetical commands `show w' and `show c' should show the appropriate parts of the General Public License. Of course, your program's commands might be different; for a GUI interface, you would use an "about box". You should also get your employer (if you work as a programmer) or school, if any, to sign a "copyright disclaimer" for the program, if necessary. For more information on this, and how to apply and follow the GNU GPL, see . The GNU General Public License does not permit incorporating your program into proprietary programs. If your program is a subroutine library, you may consider it more useful to permit linking proprietary applications with the library. If this is what you want to do, use the GNU Lesser General Public License instead of this License. But first, please read . ================================================ FILE: README.md ================================================ # Windhawk ![Screenshot](screenshot.png) Windhawk aims to make it easier to customize Windows programs. For more details, see [the official website](https://windhawk.net/) and [the announcement](https://ramensoftware.com/windhawk). This repository is used to [report issues](https://github.com/ramensoftware/windhawk/issues) and to [discuss Windhawk](https://github.com/ramensoftware/windhawk/discussions). For discussing Windhawk mods, refer to [the windhawk-mods repository](https://github.com/ramensoftware/windhawk-mods). You're also welcome to join [the Windhawk Discord channel](https://discord.com/servers/windhawk-923944342991818753) for a live discussion. ## Technical details High level architecture: ![High level architecture diagram](diagram.png) For technical details about the global injection and hooking method that is used, refer to the following blog post: [Implementing Global Injection and Hooking in Windows](https://m417z.com/Implementing-Global-Injection-and-Hooking-in-Windows/). ## Source code The Windhawk source code can be found in the `src` folder, which contains the following subfolders: * `windhawk`: The code of the main `windhawk.exe` executable and the 32-bit and 64-bit `windhawk.dll` engine libraries. * `vscode-windhawk`: The code of the VSCode extension that is responsible for UI operations such as installing mods and listing installed mods. * `vscode-windhawk-ui`: The UI part of the VSCode extension. A simple way to get started is by extracting the portable version of Windhawk with the official installer, building the part of Windhawk that you want to modify, and then replacing the corresponding files in the portable version with the newly built files. ## Additional resources Code which demonstrates the global injection and hooking method that is used can be found in this repository: [global-inject-demo](https://github.com/m417z/global-inject-demo). ================================================ FILE: src/vscode-windhawk/.eslintrc.js ================================================ /**@type {import('eslint').Linter.Config} */ // eslint-disable-next-line no-undef module.exports = { root: true, parser: '@typescript-eslint/parser', plugins: [ '@typescript-eslint', ], extends: [ 'eslint:recommended', 'plugin:@typescript-eslint/recommended', ], rules: { 'semi': [2, "always"], '@typescript-eslint/no-unused-vars': 0, '@typescript-eslint/no-explicit-any': 0, '@typescript-eslint/explicit-module-boundary-types': 0, '@typescript-eslint/no-non-null-assertion': 0, } }; ================================================ FILE: src/vscode-windhawk/.gitignore ================================================ node_modules/ out/ webview/ prebuilds/ dist/ windhawk*.vsix ================================================ FILE: src/vscode-windhawk/.prettierrc ================================================ { "singleQuote": true } ================================================ FILE: src/vscode-windhawk/.vscode/extensions.json ================================================ { // See https://go.microsoft.com/fwlink/?LinkId=827846 to learn about workspace recommendations. // Extension identifier format: ${publisher}.${name}. Example: vscode.csharp // List of extensions which should be recommended for users of this workspace. "recommendations": [ "dbaeumer.vscode-eslint" ] } ================================================ FILE: src/vscode-windhawk/.vscode/launch.json ================================================ // A launch configuration that compiles the extension and then opens it inside a new window // Use IntelliSense to learn about possible attributes. // Hover to view descriptions of existing attributes. // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 { "version": "0.2.0", "configurations": [ { "name": "Launch Extension", "type": "extensionHost", "request": "launch", "runtimeExecutable": "${execPath}", "args": [ "--extensionDevelopmentPath=${workspaceRoot}" ], "outFiles": [ "${workspaceFolder}/dist/**/*.js" ], "preLaunchTask": "npm: webpack" } ] } ================================================ FILE: src/vscode-windhawk/.vscode/settings.json ================================================ { "editor.insertSpaces": false, "cSpell.words": [ "DUNICODE", "DWORD", "HKCU", "HKEY", "HKLM", "Windhawk", "cppbuild", "cppdbg", "localizable", "opendir", "userprofile" ] } ================================================ FILE: src/vscode-windhawk/.vscode/tasks.json ================================================ // See https://go.microsoft.com/fwlink/?LinkId=733558 // for the documentation about the tasks.json format { "version": "2.0.0", "tasks": [ { "type": "npm", "script": "watch", "problemMatcher": "$tsc-watch", "isBackground": true, "presentation": { "reveal": "never" }, "group": { "kind": "build", "isDefault": true } } ] } ================================================ FILE: src/vscode-windhawk/.vscodeignore ================================================ .vscode/** helper_scripts/** src/** out/** node_modules/** .claude/** tsconfig.json .gitignore CLAUDE.local.md ================================================ FILE: src/vscode-windhawk/LICENSE ================================================ GNU GENERAL PUBLIC LICENSE Version 3, 29 June 2007 Copyright (C) 2007 Free Software Foundation, Inc. Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Preamble The GNU General Public License is a free, copyleft license for software and other kinds of works. The licenses for most software and other practical works are designed to take away your freedom to share and change the works. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change all versions of a program--to make sure it remains free software for all its users. We, the Free Software Foundation, use the GNU General Public License for most of our software; it applies also to any other work released this way by its authors. You can apply it to your programs, too. When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for them if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs, and that you know you can do these things. To protect your rights, we need to prevent others from denying you these rights or asking you to surrender the rights. Therefore, you have certain responsibilities if you distribute copies of the software, or if you modify it: responsibilities to respect the freedom of others. For example, if you distribute copies of such a program, whether gratis or for a fee, you must pass on to the recipients the same freedoms that you received. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights. Developers that use the GNU GPL protect your rights with two steps: (1) assert copyright on the software, and (2) offer you this License giving you legal permission to copy, distribute and/or modify it. For the developers' and authors' protection, the GPL clearly explains that there is no warranty for this free software. For both users' and authors' sake, the GPL requires that modified versions be marked as changed, so that their problems will not be attributed erroneously to authors of previous versions. Some devices are designed to deny users access to install or run modified versions of the software inside them, although the manufacturer can do so. This is fundamentally incompatible with the aim of protecting users' freedom to change the software. The systematic pattern of such abuse occurs in the area of products for individuals to use, which is precisely where it is most unacceptable. Therefore, we have designed this version of the GPL to prohibit the practice for those products. If such problems arise substantially in other domains, we stand ready to extend this provision to those domains in future versions of the GPL, as needed to protect the freedom of users. Finally, every program is threatened constantly by software patents. States should not allow patents to restrict development and use of software on general-purpose computers, but in those that do, we wish to avoid the special danger that patents applied to a free program could make it effectively proprietary. To prevent this, the GPL assures that patents cannot be used to render the program non-free. The precise terms and conditions for copying, distribution and modification follow. TERMS AND CONDITIONS 0. Definitions. "This License" refers to version 3 of the GNU General Public License. "Copyright" also means copyright-like laws that apply to other kinds of works, such as semiconductor masks. "The Program" refers to any copyrightable work licensed under this License. Each licensee is addressed as "you". "Licensees" and "recipients" may be individuals or organizations. To "modify" a work means to copy from or adapt all or part of the work in a fashion requiring copyright permission, other than the making of an exact copy. The resulting work is called a "modified version" of the earlier work or a work "based on" the earlier work. A "covered work" means either the unmodified Program or a work based on the Program. To "propagate" a work means to do anything with it that, without permission, would make you directly or secondarily liable for infringement under applicable copyright law, except executing it on a computer or modifying a private copy. Propagation includes copying, distribution (with or without modification), making available to the public, and in some countries other activities as well. To "convey" a work means any kind of propagation that enables other parties to make or receive copies. Mere interaction with a user through a computer network, with no transfer of a copy, is not conveying. An interactive user interface displays "Appropriate Legal Notices" to the extent that it includes a convenient and prominently visible feature that (1) displays an appropriate copyright notice, and (2) tells the user that there is no warranty for the work (except to the extent that warranties are provided), that licensees may convey the work under this License, and how to view a copy of this License. If the interface presents a list of user commands or options, such as a menu, a prominent item in the list meets this criterion. 1. Source Code. The "source code" for a work means the preferred form of the work for making modifications to it. "Object code" means any non-source form of a work. A "Standard Interface" means an interface that either is an official standard defined by a recognized standards body, or, in the case of interfaces specified for a particular programming language, one that is widely used among developers working in that language. The "System Libraries" of an executable work include anything, other than the work as a whole, that (a) is included in the normal form of packaging a Major Component, but which is not part of that Major Component, and (b) serves only to enable use of the work with that Major Component, or to implement a Standard Interface for which an implementation is available to the public in source code form. A "Major Component", in this context, means a major essential component (kernel, window system, and so on) of the specific operating system (if any) on which the executable work runs, or a compiler used to produce the work, or an object code interpreter used to run it. The "Corresponding Source" for a work in object code form means all the source code needed to generate, install, and (for an executable work) run the object code and to modify the work, including scripts to control those activities. However, it does not include the work's System Libraries, or general-purpose tools or generally available free programs which are used unmodified in performing those activities but which are not part of the work. For example, Corresponding Source includes interface definition files associated with source files for the work, and the source code for shared libraries and dynamically linked subprograms that the work is specifically designed to require, such as by intimate data communication or control flow between those subprograms and other parts of the work. The Corresponding Source need not include anything that users can regenerate automatically from other parts of the Corresponding Source. The Corresponding Source for a work in source code form is that same work. 2. Basic Permissions. All rights granted under this License are granted for the term of copyright on the Program, and are irrevocable provided the stated conditions are met. This License explicitly affirms your unlimited permission to run the unmodified Program. The output from running a covered work is covered by this License only if the output, given its content, constitutes a covered work. This License acknowledges your rights of fair use or other equivalent, as provided by copyright law. You may make, run and propagate covered works that you do not convey, without conditions so long as your license otherwise remains in force. You may convey covered works to others for the sole purpose of having them make modifications exclusively for you, or provide you with facilities for running those works, provided that you comply with the terms of this License in conveying all material for which you do not control copyright. Those thus making or running the covered works for you must do so exclusively on your behalf, under your direction and control, on terms that prohibit them from making any copies of your copyrighted material outside their relationship with you. Conveying under any other circumstances is permitted solely under the conditions stated below. Sublicensing is not allowed; section 10 makes it unnecessary. 3. Protecting Users' Legal Rights From Anti-Circumvention Law. No covered work shall be deemed part of an effective technological measure under any applicable law fulfilling obligations under article 11 of the WIPO copyright treaty adopted on 20 December 1996, or similar laws prohibiting or restricting circumvention of such measures. When you convey a covered work, you waive any legal power to forbid circumvention of technological measures to the extent such circumvention is effected by exercising rights under this License with respect to the covered work, and you disclaim any intention to limit operation or modification of the work as a means of enforcing, against the work's users, your or third parties' legal rights to forbid circumvention of technological measures. 4. Conveying Verbatim Copies. You may convey verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice; keep intact all notices stating that this License and any non-permissive terms added in accord with section 7 apply to the code; keep intact all notices of the absence of any warranty; and give all recipients a copy of this License along with the Program. You may charge any price or no price for each copy that you convey, and you may offer support or warranty protection for a fee. 5. Conveying Modified Source Versions. You may convey a work based on the Program, or the modifications to produce it from the Program, in the form of source code under the terms of section 4, provided that you also meet all of these conditions: a) The work must carry prominent notices stating that you modified it, and giving a relevant date. b) The work must carry prominent notices stating that it is released under this License and any conditions added under section 7. This requirement modifies the requirement in section 4 to "keep intact all notices". c) You must license the entire work, as a whole, under this License to anyone who comes into possession of a copy. This License will therefore apply, along with any applicable section 7 additional terms, to the whole of the work, and all its parts, regardless of how they are packaged. This License gives no permission to license the work in any other way, but it does not invalidate such permission if you have separately received it. d) If the work has interactive user interfaces, each must display Appropriate Legal Notices; however, if the Program has interactive interfaces that do not display Appropriate Legal Notices, your work need not make them do so. A compilation of a covered work with other separate and independent works, which are not by their nature extensions of the covered work, and which are not combined with it such as to form a larger program, in or on a volume of a storage or distribution medium, is called an "aggregate" if the compilation and its resulting copyright are not used to limit the access or legal rights of the compilation's users beyond what the individual works permit. Inclusion of a covered work in an aggregate does not cause this License to apply to the other parts of the aggregate. 6. Conveying Non-Source Forms. You may convey a covered work in object code form under the terms of sections 4 and 5, provided that you also convey the machine-readable Corresponding Source under the terms of this License, in one of these ways: a) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by the Corresponding Source fixed on a durable physical medium customarily used for software interchange. b) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by a written offer, valid for at least three years and valid for as long as you offer spare parts or customer support for that product model, to give anyone who possesses the object code either (1) a copy of the Corresponding Source for all the software in the product that is covered by this License, on a durable physical medium customarily used for software interchange, for a price no more than your reasonable cost of physically performing this conveying of source, or (2) access to copy the Corresponding Source from a network server at no charge. c) Convey individual copies of the object code with a copy of the written offer to provide the Corresponding Source. This alternative is allowed only occasionally and noncommercially, and only if you received the object code with such an offer, in accord with subsection 6b. d) Convey the object code by offering access from a designated place (gratis or for a charge), and offer equivalent access to the Corresponding Source in the same way through the same place at no further charge. You need not require recipients to copy the Corresponding Source along with the object code. If the place to copy the object code is a network server, the Corresponding Source may be on a different server (operated by you or a third party) that supports equivalent copying facilities, provided you maintain clear directions next to the object code saying where to find the Corresponding Source. Regardless of what server hosts the Corresponding Source, you remain obligated to ensure that it is available for as long as needed to satisfy these requirements. e) Convey the object code using peer-to-peer transmission, provided you inform other peers where the object code and Corresponding Source of the work are being offered to the general public at no charge under subsection 6d. A separable portion of the object code, whose source code is excluded from the Corresponding Source as a System Library, need not be included in conveying the object code work. A "User Product" is either (1) a "consumer product", which means any tangible personal property which is normally used for personal, family, or household purposes, or (2) anything designed or sold for incorporation into a dwelling. In determining whether a product is a consumer product, doubtful cases shall be resolved in favor of coverage. For a particular product received by a particular user, "normally used" refers to a typical or common use of that class of product, regardless of the status of the particular user or of the way in which the particular user actually uses, or expects or is expected to use, the product. A product is a consumer product regardless of whether the product has substantial commercial, industrial or non-consumer uses, unless such uses represent the only significant mode of use of the product. "Installation Information" for a User Product means any methods, procedures, authorization keys, or other information required to install and execute modified versions of a covered work in that User Product from a modified version of its Corresponding Source. The information must suffice to ensure that the continued functioning of the modified object code is in no case prevented or interfered with solely because modification has been made. If you convey an object code work under this section in, or with, or specifically for use in, a User Product, and the conveying occurs as part of a transaction in which the right of possession and use of the User Product is transferred to the recipient in perpetuity or for a fixed term (regardless of how the transaction is characterized), the Corresponding Source conveyed under this section must be accompanied by the Installation Information. But this requirement does not apply if neither you nor any third party retains the ability to install modified object code on the User Product (for example, the work has been installed in ROM). The requirement to provide Installation Information does not include a requirement to continue to provide support service, warranty, or updates for a work that has been modified or installed by the recipient, or for the User Product in which it has been modified or installed. Access to a network may be denied when the modification itself materially and adversely affects the operation of the network or violates the rules and protocols for communication across the network. Corresponding Source conveyed, and Installation Information provided, in accord with this section must be in a format that is publicly documented (and with an implementation available to the public in source code form), and must require no special password or key for unpacking, reading or copying. 7. Additional Terms. "Additional permissions" are terms that supplement the terms of this License by making exceptions from one or more of its conditions. Additional permissions that are applicable to the entire Program shall be treated as though they were included in this License, to the extent that they are valid under applicable law. If additional permissions apply only to part of the Program, that part may be used separately under those permissions, but the entire Program remains governed by this License without regard to the additional permissions. When you convey a copy of a covered work, you may at your option remove any additional permissions from that copy, or from any part of it. (Additional permissions may be written to require their own removal in certain cases when you modify the work.) You may place additional permissions on material, added by you to a covered work, for which you have or can give appropriate copyright permission. Notwithstanding any other provision of this License, for material you add to a covered work, you may (if authorized by the copyright holders of that material) supplement the terms of this License with terms: a) Disclaiming warranty or limiting liability differently from the terms of sections 15 and 16 of this License; or b) Requiring preservation of specified reasonable legal notices or author attributions in that material or in the Appropriate Legal Notices displayed by works containing it; or c) Prohibiting misrepresentation of the origin of that material, or requiring that modified versions of such material be marked in reasonable ways as different from the original version; or d) Limiting the use for publicity purposes of names of licensors or authors of the material; or e) Declining to grant rights under trademark law for use of some trade names, trademarks, or service marks; or f) Requiring indemnification of licensors and authors of that material by anyone who conveys the material (or modified versions of it) with contractual assumptions of liability to the recipient, for any liability that these contractual assumptions directly impose on those licensors and authors. All other non-permissive additional terms are considered "further restrictions" within the meaning of section 10. If the Program as you received it, or any part of it, contains a notice stating that it is governed by this License along with a term that is a further restriction, you may remove that term. If a license document contains a further restriction but permits relicensing or conveying under this License, you may add to a covered work material governed by the terms of that license document, provided that the further restriction does not survive such relicensing or conveying. If you add terms to a covered work in accord with this section, you must place, in the relevant source files, a statement of the additional terms that apply to those files, or a notice indicating where to find the applicable terms. Additional terms, permissive or non-permissive, may be stated in the form of a separately written license, or stated as exceptions; the above requirements apply either way. 8. Termination. You may not propagate or modify a covered work except as expressly provided under this License. Any attempt otherwise to propagate or modify it is void, and will automatically terminate your rights under this License (including any patent licenses granted under the third paragraph of section 11). However, if you cease all violation of this License, then your license from a particular copyright holder is reinstated (a) provisionally, unless and until the copyright holder explicitly and finally terminates your license, and (b) permanently, if the copyright holder fails to notify you of the violation by some reasonable means prior to 60 days after the cessation. Moreover, your license from a particular copyright holder is reinstated permanently if the copyright holder notifies you of the violation by some reasonable means, this is the first time you have received notice of violation of this License (for any work) from that copyright holder, and you cure the violation prior to 30 days after your receipt of the notice. Termination of your rights under this section does not terminate the licenses of parties who have received copies or rights from you under this License. If your rights have been terminated and not permanently reinstated, you do not qualify to receive new licenses for the same material under section 10. 9. Acceptance Not Required for Having Copies. You are not required to accept this License in order to receive or run a copy of the Program. Ancillary propagation of a covered work occurring solely as a consequence of using peer-to-peer transmission to receive a copy likewise does not require acceptance. However, nothing other than this License grants you permission to propagate or modify any covered work. These actions infringe copyright if you do not accept this License. Therefore, by modifying or propagating a covered work, you indicate your acceptance of this License to do so. 10. Automatic Licensing of Downstream Recipients. Each time you convey a covered work, the recipient automatically receives a license from the original licensors, to run, modify and propagate that work, subject to this License. You are not responsible for enforcing compliance by third parties with this License. An "entity transaction" is a transaction transferring control of an organization, or substantially all assets of one, or subdividing an organization, or merging organizations. If propagation of a covered work results from an entity transaction, each party to that transaction who receives a copy of the work also receives whatever licenses to the work the party's predecessor in interest had or could give under the previous paragraph, plus a right to possession of the Corresponding Source of the work from the predecessor in interest, if the predecessor has it or can get it with reasonable efforts. You may not impose any further restrictions on the exercise of the rights granted or affirmed under this License. For example, you may not impose a license fee, royalty, or other charge for exercise of rights granted under this License, and you may not initiate litigation (including a cross-claim or counterclaim in a lawsuit) alleging that any patent claim is infringed by making, using, selling, offering for sale, or importing the Program or any portion of it. 11. Patents. A "contributor" is a copyright holder who authorizes use under this License of the Program or a work on which the Program is based. The work thus licensed is called the contributor's "contributor version". A contributor's "essential patent claims" are all patent claims owned or controlled by the contributor, whether already acquired or hereafter acquired, that would be infringed by some manner, permitted by this License, of making, using, or selling its contributor version, but do not include claims that would be infringed only as a consequence of further modification of the contributor version. For purposes of this definition, "control" includes the right to grant patent sublicenses in a manner consistent with the requirements of this License. Each contributor grants you a non-exclusive, worldwide, royalty-free patent license under the contributor's essential patent claims, to make, use, sell, offer for sale, import and otherwise run, modify and propagate the contents of its contributor version. In the following three paragraphs, a "patent license" is any express agreement or commitment, however denominated, not to enforce a patent (such as an express permission to practice a patent or covenant not to sue for patent infringement). To "grant" such a patent license to a party means to make such an agreement or commitment not to enforce a patent against the party. If you convey a covered work, knowingly relying on a patent license, and the Corresponding Source of the work is not available for anyone to copy, free of charge and under the terms of this License, through a publicly available network server or other readily accessible means, then you must either (1) cause the Corresponding Source to be so available, or (2) arrange to deprive yourself of the benefit of the patent license for this particular work, or (3) arrange, in a manner consistent with the requirements of this License, to extend the patent license to downstream recipients. "Knowingly relying" means you have actual knowledge that, but for the patent license, your conveying the covered work in a country, or your recipient's use of the covered work in a country, would infringe one or more identifiable patents in that country that you have reason to believe are valid. If, pursuant to or in connection with a single transaction or arrangement, you convey, or propagate by procuring conveyance of, a covered work, and grant a patent license to some of the parties receiving the covered work authorizing them to use, propagate, modify or convey a specific copy of the covered work, then the patent license you grant is automatically extended to all recipients of the covered work and works based on it. A patent license is "discriminatory" if it does not include within the scope of its coverage, prohibits the exercise of, or is conditioned on the non-exercise of one or more of the rights that are specifically granted under this License. You may not convey a covered work if you are a party to an arrangement with a third party that is in the business of distributing software, under which you make payment to the third party based on the extent of your activity of conveying the work, and under which the third party grants, to any of the parties who would receive the covered work from you, a discriminatory patent license (a) in connection with copies of the covered work conveyed by you (or copies made from those copies), or (b) primarily for and in connection with specific products or compilations that contain the covered work, unless you entered into that arrangement, or that patent license was granted, prior to 28 March 2007. Nothing in this License shall be construed as excluding or limiting any implied license or other defenses to infringement that may otherwise be available to you under applicable patent law. 12. No Surrender of Others' Freedom. If conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot convey a covered work so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not convey it at all. For example, if you agree to terms that obligate you to collect a royalty for further conveying from those to whom you convey the Program, the only way you could satisfy both those terms and this License would be to refrain entirely from conveying the Program. 13. Use with the GNU Affero General Public License. Notwithstanding any other provision of this License, you have permission to link or combine any covered work with a work licensed under version 3 of the GNU Affero General Public License into a single combined work, and to convey the resulting work. The terms of this License will continue to apply to the part which is the covered work, but the special requirements of the GNU Affero General Public License, section 13, concerning interaction through a network will apply to the combination as such. 14. Revised Versions of this License. The Free Software Foundation may publish revised and/or new versions of the GNU General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Program specifies that a certain numbered version of the GNU General Public License "or any later version" applies to it, you have the option of following the terms and conditions either of that numbered version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of the GNU General Public License, you may choose any version ever published by the Free Software Foundation. If the Program specifies that a proxy can decide which future versions of the GNU General Public License can be used, that proxy's public statement of acceptance of a version permanently authorizes you to choose that version for the Program. Later license versions may give you additional or different permissions. However, no additional obligations are imposed on any author or copyright holder as a result of your choosing to follow a later version. 15. Disclaimer of Warranty. THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 16. Limitation of Liability. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. 17. Interpretation of Sections 15 and 16. If the disclaimer of warranty and limitation of liability provided above cannot be given local legal effect according to their terms, reviewing courts shall apply local law that most closely approximates an absolute waiver of all civil liability in connection with the Program, unless a warranty or assumption of liability accompanies a copy of the Program in return for a fee. END OF TERMS AND CONDITIONS How to Apply These Terms to Your New Programs If you develop a new program, and you want it to be of the greatest possible use to the public, the best way to achieve this is to make it free software which everyone can redistribute and change under these terms. To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively state the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. VSCode Windhawk - Part of the Windhawk Windows customization tool Copyright (C) 2021 Michael Maltsev This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . Also add information on how to contact you by electronic and paper mail. If the program does terminal interaction, make it output a short notice like this when it starts in an interactive mode: VSCode Windhawk Copyright (C) 2021 Michael Maltsev This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. This is free software, and you are welcome to redistribute it under certain conditions; type `show c' for details. The hypothetical commands `show w' and `show c' should show the appropriate parts of the General Public License. Of course, your program's commands might be different; for a GUI interface, you would use an "about box". You should also get your employer (if you work as a programmer) or school, if any, to sign a "copyright disclaimer" for the program, if necessary. For more information on this, and how to apply and follow the GNU GPL, see . The GNU General Public License does not permit incorporating your program into proprietary programs. If your program is a subroutine library, you may consider it more useful to permit linking proprietary applications with the library. If this is what you want to do, use the GNU Lesser General Public License instead of this License. But first, please read . ================================================ FILE: src/vscode-windhawk/README.md ================================================ # Windhawk VSCode extension Part of the Windhawk Windows customization tool. https://windhawk.net/ ================================================ FILE: src/vscode-windhawk/files/mod_template.wh.cpp ================================================ // ==WindhawkMod== // @id new-mod // @name Your Awesome Mod // @description The best mod ever that does great things // @version 0.1 // @author You // @github https://github.com/nat // @twitter https://twitter.com/jack // @homepage https://your-personal-homepage.example.com/ // @include mspaint.exe // @compilerOptions -lcomdlg32 // @license MIT // ==/WindhawkMod== // ==WindhawkModReadme== /* # Your Awesome Mod This is a place for useful information about your mod. Use it to describe the mod, explain why it's useful, and add any other relevant details. You can use [Markdown](https://en.wikipedia.org/wiki/Markdown) to add links and **formatting** to the readme. This short sample customizes Microsoft Paint by forcing it to use just a single color, and by blocking file opening. To see the mod in action: - Compile the mod with the button on the left or with Ctrl+B. - Run Microsoft Paint from the start menu (type "Paint") or by running mspaint.exe. - Draw something and notice that the orange color is always used, regardless of the color you pick. - Try opening a file and notice that it's blocked. # Getting started Check out the documentation [here](https://github.com/ramensoftware/windhawk/wiki/Creating-a-new-mod). */ // ==/WindhawkModReadme== // ==WindhawkModSettings== /* # Here you can define settings, in YAML format, that the mod users will be able # to configure. Metadata values such as $name and $description are optional. # Check out the documentation for more information: # https://github.com/ramensoftware/windhawk/wiki/Creating-a-new-mod#settings - color: - red: 255 - green: 127 - blue: 39 $name: Custom color $description: This color will be used regardless of the selected color. - blockOpen: true $name: Block opening files $description: When enabled, opening files in Paint is not allowed. */ // ==/WindhawkModSettings== // The source code of the mod starts here. This sample was inspired by the great // article of Kyle Halladay, X64 Function Hooking by Example: // https://kylehalladay.com/blog/2020/11/13/Hooking-By-Example.html // If you're new to terms such as code injection and function hooking, the // article is great to get started. #include using namespace Gdiplus; struct { BYTE red; BYTE green; BYTE blue; bool blockOpen; } settings; using GdipSetSolidFillColor_t = decltype(&DllExports::GdipSetSolidFillColor); GdipSetSolidFillColor_t GdipSetSolidFillColor_Original; GpStatus WINAPI GdipSetSolidFillColor_Hook(GpSolidFill* brush, ARGB color) { Wh_Log(L"GdipSetSolidFillColor_Hook: color=%08X", color); // If the color is not transparent, replace it. if (Color(color).GetAlpha() == 255) { color = Color::MakeARGB(255, settings.red, settings.green, settings.blue); } // Call the original function. return GdipSetSolidFillColor_Original(brush, color); } using GetOpenFileNameW_t = decltype(&GetOpenFileNameW); GetOpenFileNameW_t GetOpenFileNameW_Original; BOOL WINAPI GetOpenFileNameW_Hook(LPOPENFILENAMEW params) { Wh_Log(L"GetOpenFileNameW_Hook"); if (settings.blockOpen) { // Forbid the operation and return without calling the original // function. MessageBoxW(GetActiveWindow(), L"Opening files is forbidden", L"Surprise!", MB_OK); return FALSE; } return GetOpenFileNameW_Original(params); } void LoadSettings() { settings.red = Wh_GetIntSetting(L"color.red"); settings.green = Wh_GetIntSetting(L"color.green"); settings.blue = Wh_GetIntSetting(L"color.blue"); settings.blockOpen = Wh_GetIntSetting(L"blockOpen"); } // The mod is being initialized, load settings, hook functions, and do other // initialization stuff if required. BOOL Wh_ModInit() { Wh_Log(L"Init"); LoadSettings(); HMODULE gdiPlusModule = LoadLibrary(L"gdiplus.dll"); GdipSetSolidFillColor_t GdipSetSolidFillColor = (GdipSetSolidFillColor_t)GetProcAddress(gdiPlusModule, "GdipSetSolidFillColor"); Wh_SetFunctionHook((void*)GdipSetSolidFillColor, (void*)GdipSetSolidFillColor_Hook, (void**)&GdipSetSolidFillColor_Original); Wh_SetFunctionHook((void*)GetOpenFileNameW, (void*)GetOpenFileNameW_Hook, (void**)&GetOpenFileNameW_Original); return TRUE; } // The mod is being unloaded, free all allocated resources. void Wh_ModUninit() { Wh_Log(L"Uninit"); } // The mod setting were changed, reload them. void Wh_ModSettingsChanged() { Wh_Log(L"SettingsChanged"); LoadSettings(); } ================================================ FILE: src/vscode-windhawk/helper_scripts/disable_default_keybindings.py ================================================ import json # How to get these keybindings: # * Create Windhawk bundle. # * Remove Windhawk extension from UI. # * Run UI -> Ctrl+Shift+P -> Preferences: Open Default Keyboard Shortcuts (JSON). DEFAULT_KEYBINDINGS = json.loads(R''' [ { "key": "escape escape", "command": "workbench.action.exitZenMode", "when": "inZenMode" }, { "key": "shift+escape", "command": "closeReferenceSearch", "when": "inReferenceSearchEditor && !config.editor.stablePeek" }, { "key": "escape", "command": "closeReferenceSearch", "when": "inReferenceSearchEditor && !config.editor.stablePeek" }, { "key": "escape", "command": "editor.closeTestPeek", "when": "testing.isInPeek && !config.editor.stablePeek || testing.isPeekVisible && !config.editor.stablePeek" }, { "key": "shift+escape", "command": "cancelSelection", "when": "editorHasSelection && textInputFocus" }, { "key": "escape", "command": "cancelSelection", "when": "editorHasSelection && textInputFocus" }, { "key": "ctrl+end", "command": "cursorBottom", "when": "textInputFocus" }, { "key": "ctrl+shift+end", "command": "cursorBottomSelect", "when": "textInputFocus" }, { "key": "ctrl+shift+alt+down", "command": "cursorColumnSelectDown", "when": "textInputFocus" }, { "key": "ctrl+shift+alt+left", "command": "cursorColumnSelectLeft", "when": "textInputFocus" }, { "key": "ctrl+shift+alt+pagedown", "command": "cursorColumnSelectPageDown", "when": "textInputFocus" }, { "key": "ctrl+shift+alt+pageup", "command": "cursorColumnSelectPageUp", "when": "textInputFocus" }, { "key": "ctrl+shift+alt+right", "command": "cursorColumnSelectRight", "when": "textInputFocus" }, { "key": "ctrl+shift+alt+up", "command": "cursorColumnSelectUp", "when": "textInputFocus" }, { "key": "down", "command": "cursorDown", "when": "textInputFocus" }, { "key": "ctrl+shift+down", "command": "cursorDownSelect", "when": "textInputFocus" }, { "key": "shift+down", "command": "cursorDownSelect", "when": "textInputFocus" }, { "key": "end", "command": "cursorEnd", "when": "textInputFocus", "args": {"sticky":false} }, { "key": "shift+end", "command": "cursorEndSelect", "when": "textInputFocus", "args": {"sticky":false} }, { "key": "home", "command": "cursorHome", "when": "textInputFocus" }, { "key": "shift+home", "command": "cursorHomeSelect", "when": "textInputFocus" }, { "key": "left", "command": "cursorLeft", "when": "textInputFocus" }, { "key": "shift+left", "command": "cursorLeftSelect", "when": "textInputFocus" }, { "key": "pagedown", "command": "cursorPageDown", "when": "textInputFocus" }, { "key": "shift+pagedown", "command": "cursorPageDownSelect", "when": "textInputFocus" }, { "key": "pageup", "command": "cursorPageUp", "when": "textInputFocus" }, { "key": "shift+pageup", "command": "cursorPageUpSelect", "when": "textInputFocus" }, { "key": "right", "command": "cursorRight", "when": "textInputFocus" }, { "key": "shift+right", "command": "cursorRightSelect", "when": "textInputFocus" }, { "key": "ctrl+home", "command": "cursorTop", "when": "textInputFocus" }, { "key": "ctrl+shift+home", "command": "cursorTopSelect", "when": "textInputFocus" }, { "key": "up", "command": "cursorUp", "when": "textInputFocus" }, { "key": "ctrl+shift+up", "command": "cursorUpSelect", "when": "textInputFocus" }, { "key": "shift+up", "command": "cursorUpSelect", "when": "textInputFocus" }, { "key": "shift+backspace", "command": "deleteLeft", "when": "textInputFocus" }, { "key": "backspace", "command": "deleteLeft", "when": "textInputFocus" }, { "key": "delete", "command": "deleteRight", "when": "textInputFocus" }, { "key": "ctrl+a", "command": "editor.action.selectAll" }, { "key": "ctrl+l", "command": "expandLineSelection", "when": "textInputFocus" }, { "key": "shift+tab", "command": "outdent", "when": "editorTextFocus && !editorReadonly && !editorTabMovesFocus" }, { "key": "ctrl+shift+z", "command": "redo" }, { "key": "ctrl+y", "command": "redo" }, { "key": "ctrl+down", "command": "scrollLineDown", "when": "textInputFocus" }, { "key": "ctrl+up", "command": "scrollLineUp", "when": "textInputFocus" }, { "key": "alt+pagedown", "command": "scrollPageDown", "when": "textInputFocus" }, { "key": "alt+pageup", "command": "scrollPageUp", "when": "textInputFocus" }, { "key": "tab", "command": "tab", "when": "editorTextFocus && !editorReadonly && !editorTabMovesFocus" }, { "key": "ctrl+z", "command": "undo" }, { "key": "shift+down", "command": "cursorColumnSelectDown", "when": "editorColumnSelection && textInputFocus" }, { "key": "shift+left", "command": "cursorColumnSelectLeft", "when": "editorColumnSelection && textInputFocus" }, { "key": "shift+pagedown", "command": "cursorColumnSelectPageDown", "when": "editorColumnSelection && textInputFocus" }, { "key": "shift+pageup", "command": "cursorColumnSelectPageUp", "when": "editorColumnSelection && textInputFocus" }, { "key": "shift+right", "command": "cursorColumnSelectRight", "when": "editorColumnSelection && textInputFocus" }, { "key": "shift+up", "command": "cursorColumnSelectUp", "when": "editorColumnSelection && textInputFocus" }, { "key": "shift+escape", "command": "removeSecondaryCursors", "when": "editorHasMultipleSelections && textInputFocus" }, { "key": "escape", "command": "removeSecondaryCursors", "when": "editorHasMultipleSelections && textInputFocus" }, { "key": "f12", "command": "goToNextReference", "when": "inReferenceSearchEditor || referenceSearchVisible" }, { "key": "f4", "command": "goToNextReference", "when": "inReferenceSearchEditor || referenceSearchVisible" }, { "key": "shift+f12", "command": "goToPreviousReference", "when": "inReferenceSearchEditor || referenceSearchVisible" }, { "key": "shift+f4", "command": "goToPreviousReference", "when": "inReferenceSearchEditor || referenceSearchVisible" }, { "key": "shift+enter", "command": "refactorPreview.apply", "when": "refactorPreview.enabled && refactorPreview.enabled && refactorPreview.hasCheckedChanges && focusedView == 'refactorPreview'" }, { "key": "alt+enter", "command": "testing.editFocusedTest", "when": "focusedView == 'workbench.view.testing'" }, { "key": "escape", "command": "notebook.cell.quitEdit", "when": "inputFocus && notebookEditorFocused && !editorHasMultipleSelections && !editorHasSelection && !editorHoverVisible" }, { "key": "ctrl+alt+enter", "command": "notebook.cell.quitEdit", "when": "inputFocus && notebookEditorFocused && !editorHasMultipleSelections && !editorHasSelection && !editorHoverVisible && notebookCellType == 'markup'" }, { "key": "ctrl+f", "command": "actions.find", "when": "editorFocus || editorIsOpen" }, { "key": "enter", "command": "breakpointWidget.action.acceptInput", "when": "breakpointWidgetVisible && inBreakpointWidget" }, { "key": "shift+escape", "command": "closeBreakpointWidget", "when": "breakpointWidgetVisible && textInputFocus" }, { "key": "escape", "command": "closeBreakpointWidget", "when": "breakpointWidgetVisible && textInputFocus" }, { "key": "ctrl+u", "command": "cursorUndo", "when": "textInputFocus" }, { "key": "ctrl+right", "command": "cursorWordEndRight", "when": "textInputFocus && !accessibilityModeEnabled" }, { "key": "ctrl+shift+right", "command": "cursorWordEndRightSelect", "when": "textInputFocus && !accessibilityModeEnabled" }, { "key": "ctrl+left", "command": "cursorWordLeft", "when": "textInputFocus && !accessibilityModeEnabled" }, { "key": "ctrl+shift+left", "command": "cursorWordLeftSelect", "when": "textInputFocus && !accessibilityModeEnabled" }, { "key": "ctrl+backspace", "command": "deleteWordLeft", "when": "textInputFocus && !editorReadonly" }, { "key": "ctrl+delete", "command": "deleteWordRight", "when": "textInputFocus && !editorReadonly" }, { "key": "ctrl+k ctrl+c", "command": "editor.action.addCommentLine", "when": "editorTextFocus && !editorReadonly" }, { "key": "ctrl+d", "command": "editor.action.addSelectionToNextFindMatch", "when": "editorFocus" }, { "key": "shift+alt+.", "command": "editor.action.autoFix", "when": "editorTextFocus && !editorReadonly && supportedCodeAction =~ /(\\s|^)quickfix\\b/" }, { "key": "shift+alt+a", "command": "editor.action.blockComment", "when": "editorTextFocus && !editorReadonly" }, { "key": "escape", "command": "editor.action.cancelSelectionAnchor", "when": "editorTextFocus && selectionAnchorSet" }, { "key": "ctrl+f2", "command": "editor.action.changeAll", "when": "editorTextFocus && editorTextFocus && !editorReadonly" }, { "key": "ctrl+insert", "command": "editor.action.clipboardCopyAction" }, { "key": "ctrl+c", "command": "editor.action.clipboardCopyAction" }, { "key": "shift+delete", "command": "editor.action.clipboardCutAction" }, { "key": "ctrl+x", "command": "editor.action.clipboardCutAction" }, { "key": "shift+insert", "command": "editor.action.clipboardPasteAction" }, { "key": "ctrl+v", "command": "editor.action.clipboardPasteAction" }, { "key": "ctrl+/", "command": "editor.action.commentLine", "when": "editorTextFocus && !editorReadonly" }, { "key": "shift+alt+down", "command": "editor.action.copyLinesDownAction", "when": "editorTextFocus && !editorReadonly" }, { "key": "shift+alt+up", "command": "editor.action.copyLinesUpAction", "when": "editorTextFocus && !editorReadonly" }, { "key": "ctrl+k ctrl+k", "command": "editor.action.defineKeybinding", "when": "editorTextFocus && !editorReadonly && editorLangId == 'jsonc'" }, { "key": "ctrl+shift+k", "command": "editor.action.deleteLines", "when": "textInputFocus && !editorReadonly" }, { "key": "f7", "command": "editor.action.diffReview.next", "when": "isInDiffEditor" }, { "key": "shift+f7", "command": "editor.action.diffReview.prev", "when": "isInDiffEditor" }, { "key": "alt+f3", "command": "editor.action.dirtydiff.next", "when": "editorTextFocus" }, { "key": "shift+alt+f3", "command": "editor.action.dirtydiff.previous", "when": "editorTextFocus" }, { "key": "enter", "command": "editor.action.extensioneditor.findNext", "when": "webviewFindWidgetFocused && !editorFocus && activeEditor == 'workbench.editor.extension'" }, { "key": "shift+enter", "command": "editor.action.extensioneditor.findPrevious", "when": "webviewFindWidgetFocused && !editorFocus && activeEditor == 'workbench.editor.extension'" }, { "key": "ctrl+f", "command": "editor.action.extensioneditor.showfind", "when": "!editorFocus && activeEditor == 'workbench.editor.extension'" }, { "key": "shift+alt+f", "command": "editor.action.formatDocument", "when": "editorHasDocumentFormattingProvider && editorTextFocus && !editorReadonly && !inCompositeEditor" }, { "key": "shift+alt+f", "command": "editor.action.formatDocument.none", "when": "editorTextFocus && !editorHasDocumentFormattingProvider && !editorReadonly" }, { "key": "ctrl+k ctrl+f", "command": "editor.action.formatSelection", "when": "editorHasDocumentSelectionFormattingProvider && editorTextFocus && !editorReadonly" }, { "key": "ctrl+f12", "command": "editor.action.goToImplementation", "when": "editorHasImplementationProvider && editorTextFocus && !isInEmbeddedEditor" }, { "key": "shift+f12", "command": "editor.action.goToReferences", "when": "editorHasReferenceProvider && editorTextFocus && !inReferenceSearchEditor && !isInEmbeddedEditor" }, { "key": "ctrl+shift+.", "command": "editor.action.inPlaceReplace.down", "when": "editorTextFocus && !editorReadonly" }, { "key": "ctrl+shift+,", "command": "editor.action.inPlaceReplace.up", "when": "editorTextFocus && !editorReadonly" }, { "key": "ctrl+]", "command": "editor.action.indentLines", "when": "editorTextFocus && !editorReadonly" }, { "key": "escape", "command": "editor.action.inlineSuggest.hide", "when": "inlineSuggestionVisible" }, { "key": "alt+]", "command": "editor.action.inlineSuggest.showNext", "when": "inlineSuggestionVisible && !editorReadonly" }, { "key": "alt+[", "command": "editor.action.inlineSuggest.showPrevious", "when": "inlineSuggestionVisible && !editorReadonly" }, { "key": "ctrl+alt+up", "command": "editor.action.insertCursorAbove", "when": "editorTextFocus" }, { "key": "shift+alt+i", "command": "editor.action.insertCursorAtEndOfEachLineSelected", "when": "editorTextFocus" }, { "key": "ctrl+alt+down", "command": "editor.action.insertCursorBelow", "when": "editorTextFocus" }, { "key": "ctrl+enter", "command": "editor.action.insertLineAfter", "when": "editorTextFocus && !editorReadonly" }, { "key": "ctrl+shift+enter", "command": "editor.action.insertLineBefore", "when": "editorTextFocus && !editorReadonly" }, { "key": "ctrl+shift+\\", "command": "editor.action.jumpToBracket", "when": "editorTextFocus" }, { "key": "ctrl+shift+f2", "command": "editor.action.linkedEditing", "when": "editorHasRenameProvider && editorTextFocus && !editorReadonly" }, { "key": "alt+f8", "command": "editor.action.marker.next", "when": "editorFocus" }, { "key": "f8", "command": "editor.action.marker.nextInFiles", "when": "editorFocus" }, { "key": "shift+alt+f8", "command": "editor.action.marker.prev", "when": "editorFocus" }, { "key": "shift+f8", "command": "editor.action.marker.prevInFiles", "when": "editorFocus" }, { "key": "alt+down", "command": "editor.action.moveLinesDownAction", "when": "editorTextFocus && !editorReadonly" }, { "key": "alt+up", "command": "editor.action.moveLinesUpAction", "when": "editorTextFocus && !editorReadonly" }, { "key": "ctrl+k ctrl+d", "command": "editor.action.moveSelectionToNextFindMatch", "when": "editorFocus" }, { "key": "f3", "command": "editor.action.nextMatchFindAction", "when": "editorFocus" }, { "key": "enter", "command": "editor.action.nextMatchFindAction", "when": "editorFocus && findInputFocussed" }, { "key": "ctrl+f3", "command": "editor.action.nextSelectionMatchFindAction", "when": "editorFocus" }, { "key": "shift+alt+o", "command": "editor.action.organizeImports", "when": "editorTextFocus && !editorReadonly && supportedCodeAction =~ /(\\s|^)source\\.organizeImports\\b/" }, { "key": "ctrl+[", "command": "editor.action.outdentLines", "when": "editorTextFocus && !editorReadonly" }, { "key": "alt+f12", "command": "editor.action.peekDefinition", "when": "editorHasDefinitionProvider && editorTextFocus && !inReferenceSearchEditor && !isInEmbeddedEditor" }, { "key": "ctrl+shift+f12", "command": "editor.action.peekImplementation", "when": "editorHasImplementationProvider && editorTextFocus && !inReferenceSearchEditor && !isInEmbeddedEditor" }, { "key": "shift+f3", "command": "editor.action.previousMatchFindAction", "when": "editorFocus" }, { "key": "shift+enter", "command": "editor.action.previousMatchFindAction", "when": "editorFocus && findInputFocussed" }, { "key": "ctrl+shift+f3", "command": "editor.action.previousSelectionMatchFindAction", "when": "editorFocus" }, { "key": "ctrl+.", "command": "editor.action.quickFix", "when": "editorHasCodeActionsProvider && editorTextFocus && !editorReadonly" }, { "key": "ctrl+shift+r", "command": "editor.action.refactor", "when": "editorHasCodeActionsProvider && editorTextFocus && !editorReadonly" }, { "key": "ctrl+k ctrl+u", "command": "editor.action.removeCommentLine", "when": "editorTextFocus && !editorReadonly" }, { "key": "f2", "command": "editor.action.rename", "when": "editorHasRenameProvider && editorTextFocus && !editorReadonly" }, { "key": "f12", "command": "editor.action.revealDefinition", "when": "editorHasDefinitionProvider && editorTextFocus && !isInEmbeddedEditor" }, { "key": "ctrl+k f12", "command": "editor.action.revealDefinitionAside", "when": "editorHasDefinitionProvider && editorTextFocus && !isInEmbeddedEditor" }, { "key": "ctrl+k ctrl+k", "command": "editor.action.selectFromAnchorToCursor", "when": "editorTextFocus && selectionAnchorSet" }, { "key": "ctrl+shift+l", "command": "editor.action.selectHighlights", "when": "editorFocus" }, { "key": "ctrl+k ctrl+b", "command": "editor.action.setSelectionAnchor", "when": "editorTextFocus" }, { "key": "alt+f1", "command": "editor.action.showAccessibilityHelp" }, { "key": "shift+f10", "command": "editor.action.showContextMenu", "when": "textInputFocus" }, { "key": "ctrl+k ctrl+i", "command": "editor.action.showHover", "when": "editorTextFocus" }, { "key": "shift+alt+right", "command": "editor.action.smartSelect.expand", "when": "editorTextFocus" }, { "key": "shift+alt+left", "command": "editor.action.smartSelect.shrink", "when": "editorTextFocus" }, { "key": "ctrl+h", "command": "editor.action.startFindReplaceAction", "when": "editorFocus || editorIsOpen" }, { "key": "ctrl+m", "command": "editor.action.toggleTabFocusMode" }, { "key": "alt+z", "command": "editor.action.toggleWordWrap" }, { "key": "ctrl+shift+space", "command": "editor.action.triggerParameterHints", "when": "editorHasSignatureHelpProvider && editorTextFocus" }, { "key": "ctrl+i", "command": "editor.action.triggerSuggest", "when": "editorHasCompletionItemProvider && textInputFocus && !editorReadonly" }, { "key": "ctrl+space", "command": "editor.action.triggerSuggest", "when": "editorHasCompletionItemProvider && textInputFocus && !editorReadonly" }, { "key": "ctrl+k ctrl+x", "command": "editor.action.trimTrailingWhitespace", "when": "editorTextFocus && !editorReadonly" }, { "key": "enter", "command": "editor.action.webvieweditor.findNext", "when": "webviewFindWidgetFocused && !editorFocus && activeEditor == 'WebviewEditor'" }, { "key": "shift+enter", "command": "editor.action.webvieweditor.findPrevious", "when": "webviewFindWidgetFocused && !editorFocus && activeEditor == 'WebviewEditor'" }, { "key": "escape", "command": "editor.action.webvieweditor.hideFind", "when": "webviewFindWidgetVisible && !editorFocus && activeEditor == 'WebviewEditor'" }, { "key": "ctrl+f", "command": "editor.action.webvieweditor.showFind", "when": "webviewFindWidgetEnabled && !editorFocus && activeEditor == 'WebviewEditor'" }, { "key": "f7", "command": "editor.action.wordHighlight.next", "when": "editorTextFocus && hasWordHighlights" }, { "key": "shift+f7", "command": "editor.action.wordHighlight.prev", "when": "editorTextFocus && hasWordHighlights" }, { "key": "escape", "command": "editor.cancelOperation", "when": "cancellableOperation" }, { "key": "escape", "command": "editor.debug.action.closeExceptionWidget", "when": "exceptionWidgetVisible" }, { "key": "ctrl+k ctrl+i", "command": "editor.debug.action.showDebugHover", "when": "editorTextFocus && inDebugMode" }, { "key": "f9", "command": "editor.debug.action.toggleBreakpoint", "when": "debuggersAvailable && editorTextFocus" }, { "key": "tab", "command": "editor.emmet.action.expandAbbreviation", "when": "config.emmet.triggerExpansionOnTab && editorTextFocus && !editorReadonly && !editorTabMovesFocus" }, { "key": "ctrl+shift+[", "command": "editor.fold", "when": "editorTextFocus && foldingEnabled" }, { "key": "ctrl+k ctrl+0", "command": "editor.foldAll", "when": "editorTextFocus && foldingEnabled" }, { "key": "ctrl+k ctrl+/", "command": "editor.foldAllBlockComments", "when": "editorTextFocus && foldingEnabled" }, { "key": "ctrl+k ctrl+-", "command": "editor.foldAllExcept", "when": "editorTextFocus && foldingEnabled" }, { "key": "ctrl+k ctrl+8", "command": "editor.foldAllMarkerRegions", "when": "editorTextFocus && foldingEnabled" }, { "key": "ctrl+k ctrl+1", "command": "editor.foldLevel1", "when": "editorTextFocus && foldingEnabled" }, { "key": "ctrl+k ctrl+2", "command": "editor.foldLevel2", "when": "editorTextFocus && foldingEnabled" }, { "key": "ctrl+k ctrl+3", "command": "editor.foldLevel3", "when": "editorTextFocus && foldingEnabled" }, { "key": "ctrl+k ctrl+4", "command": "editor.foldLevel4", "when": "editorTextFocus && foldingEnabled" }, { "key": "ctrl+k ctrl+5", "command": "editor.foldLevel5", "when": "editorTextFocus && foldingEnabled" }, { "key": "ctrl+k ctrl+6", "command": "editor.foldLevel6", "when": "editorTextFocus && foldingEnabled" }, { "key": "ctrl+k ctrl+7", "command": "editor.foldLevel7", "when": "editorTextFocus && foldingEnabled" }, { "key": "ctrl+k ctrl+[", "command": "editor.foldRecursively", "when": "editorTextFocus && foldingEnabled" }, { "key": "f12", "command": "editor.gotoNextSymbolFromResult", "when": "hasSymbols" }, { "key": "escape", "command": "editor.gotoNextSymbolFromResult.cancel", "when": "hasSymbols" }, { "key": "ctrl+k ctrl+l", "command": "editor.toggleFold", "when": "editorTextFocus && foldingEnabled" }, { "key": "ctrl+shift+]", "command": "editor.unfold", "when": "editorTextFocus && foldingEnabled" }, { "key": "ctrl+k ctrl+j", "command": "editor.unfoldAll", "when": "editorTextFocus && foldingEnabled" }, { "key": "ctrl+k ctrl+=", "command": "editor.unfoldAllExcept", "when": "editorTextFocus && foldingEnabled" }, { "key": "ctrl+k ctrl+9", "command": "editor.unfoldAllMarkerRegions", "when": "editorTextFocus && foldingEnabled" }, { "key": "ctrl+k ctrl+]", "command": "editor.unfoldRecursively", "when": "editorTextFocus && foldingEnabled" }, { "key": "right", "command": "gettingStarted.next", "when": "inWelcome && activeEditor == 'gettingStartedPage'" }, { "key": "down", "command": "gettingStarted.next", "when": "inWelcome && activeEditor == 'gettingStartedPage'" }, { "key": "left", "command": "gettingStarted.prev", "when": "inWelcome && activeEditor == 'gettingStartedPage'" }, { "key": "up", "command": "gettingStarted.prev", "when": "inWelcome && activeEditor == 'gettingStartedPage'" }, { "key": "tab", "command": "insertSnippet", "when": "editorTextFocus && hasSnippetCompletions && !editorTabMovesFocus && !inSnippetMode" }, { "key": "ctrl+enter", "command": "interactive.execute", "when": "resourceScheme == 'vscode-interactive'" }, { "key": "ctrl+alt+enter", "command": "notebook.cell.execute", "when": "notebookCellListFocused && notebookMissingKernelExtension && !notebookCellExecuting && notebookCellType == 'code' || notebookCellListFocused && !notebookCellExecuting && notebookCellType == 'code' && notebookKernelCount > 0" }, { "key": "alt+enter", "command": "notebook.cell.executeAndInsertBelow", "when": "notebookCellListFocused && notebookMissingKernelExtension && !notebookCellExecuting && notebookCellType == 'code' || notebookCellListFocused && !notebookCellExecuting && notebookCellType == 'code' && notebookKernelCount > 0" }, { "key": "shift+enter", "command": "notebook.cell.executeAndSelectBelow", "when": "notebookCellListFocused && notebookCellType == 'markup' || notebookCellListFocused && notebookMissingKernelExtension && !notebookCellExecuting && notebookCellType == 'code' || notebookCellListFocused && !notebookCellExecuting && notebookCellType == 'code' && notebookKernelCount > 0" }, { "key": "ctrl+shift+v", "command": "notebook.cell.pasteAbove", "when": "notebookEditorFocused && !inputFocus" }, { "key": "down", "command": "notebook.focusNextEditor", "when": "config.notebook.navigation.allowNavigateToSurroundingCells && editorTextFocus && inputFocus && notebookEditorFocused && notebookEditorCursorAtBoundary != 'none' && notebookEditorCursorAtBoundary != 'top'" }, { "key": "up", "command": "notebook.focusPreviousEditor", "when": "config.notebook.navigation.allowNavigateToSurroundingCells && editorTextFocus && inputFocus && notebookEditorFocused && notebookEditorCursorAtBoundary != 'bottom' && notebookEditorCursorAtBoundary != 'none'" }, { "key": "shift+alt+f", "command": "notebook.formatCell", "when": "editorHasDocumentFormattingProvider && editorTextFocus && inCompositeEditor && notebookEditable && !editorReadonly && activeEditor == 'workbench.editor.notebook'" }, { "key": "ctrl+enter", "command": "openReferenceToSide", "when": "listFocus && referenceSearchVisible && !inputFocus" }, { "key": "enter", "command": "repl.action.acceptInput", "when": "inDebugRepl && textInputFocus" }, { "key": "ctrl+f", "command": "repl.action.filter", "when": "inDebugRepl && textInputFocus" }, { "key": "ctrl+shift+r", "command": "rerunSearchEditorSearch", "when": "inSearchEditor" }, { "key": "escape", "command": "search.action.focusQueryEditorWidget", "when": "inSearchEditor" }, { "key": "ctrl+shift+backspace", "command": "search.searchEditor.action.deleteFileResults", "when": "inSearchEditor" }, { "key": "escape", "command": "settings.action.clearSearchResults", "when": "inSettingsEditor && inSettingsSearch" }, { "key": "down", "command": "settings.action.focusSettingsFile", "when": "inSettingsSearch && !suggestWidgetVisible" }, { "key": "ctrl+f", "command": "settings.action.search", "when": "inSettingsEditor" }, { "key": "ctrl+/", "command": "toggleExplainMode", "when": "suggestWidgetVisible" }, { "key": "ctrl+k f2", "command": "togglePeekWidgetFocus", "when": "inReferenceSearchEditor || referenceSearchVisible" }, { "key": "escape", "command": "welcome.goBack", "when": "inWelcome && activeEditor == 'gettingStartedPage'" }, { "key": "alt+f5", "command": "workbench.action.editor.nextChange", "when": "editorTextFocus" }, { "key": "shift+alt+f5", "command": "workbench.action.editor.previousChange", "when": "editorTextFocus" }, { "key": "shift+escape", "command": "workbench.action.hideComment", "when": "commentEditorFocused" }, { "key": "escape", "command": "workbench.action.hideComment", "when": "commentEditorFocused" }, { "key": "ctrl+enter", "command": "workbench.action.submitComment", "when": "commentEditorFocused" }, { "key": "alt+f8", "command": "testing.goToNextMessage", "when": "editorFocus && testing.isPeekVisible" }, { "key": "shift+alt+f8", "command": "testing.goToPreviousMessage", "when": "editorFocus && testing.isPeekVisible" }, { "key": "shift+escape", "command": "closeFindWidget", "when": "editorFocus && findWidgetVisible && !isComposing" }, { "key": "escape", "command": "closeFindWidget", "when": "editorFocus && findWidgetVisible && !isComposing" }, { "key": "ctrl+alt+enter", "command": "editor.action.replaceAll", "when": "editorFocus && findWidgetVisible" }, { "key": "ctrl+shift+1", "command": "editor.action.replaceOne", "when": "editorFocus && findWidgetVisible" }, { "key": "enter", "command": "editor.action.replaceOne", "when": "editorFocus && findWidgetVisible && replaceInputFocussed" }, { "key": "alt+enter", "command": "editor.action.selectAllMatches", "when": "editorFocus && findWidgetVisible" }, { "key": "alt+c", "command": "toggleFindCaseSensitive", "when": "editorFocus" }, { "key": "alt+l", "command": "toggleFindInSelection", "when": "editorFocus" }, { "key": "alt+r", "command": "toggleFindRegex", "when": "editorFocus" }, { "key": "alt+w", "command": "toggleFindWholeWord", "when": "editorFocus" }, { "key": "alt+p", "command": "togglePreserveCase", "when": "editorFocus" }, { "key": "tab", "command": "jumpToNextSnippetPlaceholder", "when": "editorTextFocus && hasNextTabstop && inSnippetMode" }, { "key": "shift+tab", "command": "jumpToPrevSnippetPlaceholder", "when": "editorTextFocus && hasPrevTabstop && inSnippetMode" }, { "key": "escape", "command": "leaveEditorMessage", "when": "messageVisible" }, { "key": "shift+escape", "command": "leaveSnippet", "when": "editorTextFocus && inSnippetMode" }, { "key": "escape", "command": "leaveSnippet", "when": "editorTextFocus && inSnippetMode" }, { "key": "shift+escape", "command": "closeDirtyDiff", "when": "dirtyDiffVisible" }, { "key": "escape", "command": "closeDirtyDiff", "when": "dirtyDiffVisible" }, { "key": "shift+escape", "command": "closeMarkersNavigation", "when": "editorFocus && markersNavigationVisible" }, { "key": "escape", "command": "closeMarkersNavigation", "when": "editorFocus && markersNavigationVisible" }, { "key": "escape", "command": "notifications.hideToasts", "when": "notificationToastsVisible" }, { "key": "shift+escape", "command": "closeParameterHints", "when": "editorFocus && parameterHintsVisible" }, { "key": "escape", "command": "closeParameterHints", "when": "editorFocus && parameterHintsVisible" }, { "key": "alt+down", "command": "showNextParameterHint", "when": "editorFocus && parameterHintsMultipleSignatures && parameterHintsVisible" }, { "key": "down", "command": "showNextParameterHint", "when": "editorFocus && parameterHintsMultipleSignatures && parameterHintsVisible" }, { "key": "alt+up", "command": "showPrevParameterHint", "when": "editorFocus && parameterHintsMultipleSignatures && parameterHintsVisible" }, { "key": "up", "command": "showPrevParameterHint", "when": "editorFocus && parameterHintsMultipleSignatures && parameterHintsVisible" }, { "key": "shift+tab", "command": "acceptAlternativeSelectedSuggestion", "when": "suggestWidgetVisible && textInputFocus && textInputFocus" }, { "key": "shift+enter", "command": "acceptAlternativeSelectedSuggestion", "when": "suggestWidgetVisible && textInputFocus && textInputFocus" }, { "key": "tab", "command": "acceptSelectedSuggestion", "when": "suggestWidgetVisible && textInputFocus" }, { "key": "enter", "command": "acceptSelectedSuggestion", "when": "acceptSuggestionOnEnter && suggestWidgetVisible && suggestionMakesTextEdit && textInputFocus" }, { "key": "shift+escape", "command": "hideSuggestWidget", "when": "suggestWidgetVisible && textInputFocus" }, { "key": "escape", "command": "hideSuggestWidget", "when": "suggestWidgetVisible && textInputFocus" }, { "key": "tab", "command": "insertBestCompletion", "when": "atEndOfWord && textInputFocus && !hasOtherSuggestions && !inSnippetMode && !suggestWidgetVisible && config.editor.tabCompletion == 'on'" }, { "key": "tab", "command": "insertNextSuggestion", "when": "hasOtherSuggestions && textInputFocus && textInputFocus && !inSnippetMode && !suggestWidgetVisible && config.editor.tabCompletion == 'on'" }, { "key": "shift+tab", "command": "insertPrevSuggestion", "when": "hasOtherSuggestions && textInputFocus && textInputFocus && !inSnippetMode && !suggestWidgetVisible && config.editor.tabCompletion == 'on'" }, { "key": "ctrl+pagedown", "command": "selectNextPageSuggestion", "when": "suggestWidgetMultipleSuggestions && suggestWidgetVisible && textInputFocus" }, { "key": "pagedown", "command": "selectNextPageSuggestion", "when": "suggestWidgetMultipleSuggestions && suggestWidgetVisible && textInputFocus" }, { "key": "ctrl+down", "command": "selectNextSuggestion", "when": "suggestWidgetMultipleSuggestions && suggestWidgetVisible && textInputFocus" }, { "key": "down", "command": "selectNextSuggestion", "when": "suggestWidgetMultipleSuggestions && suggestWidgetVisible && textInputFocus" }, { "key": "ctrl+pageup", "command": "selectPrevPageSuggestion", "when": "suggestWidgetMultipleSuggestions && suggestWidgetVisible && textInputFocus" }, { "key": "pageup", "command": "selectPrevPageSuggestion", "when": "suggestWidgetMultipleSuggestions && suggestWidgetVisible && textInputFocus" }, { "key": "ctrl+up", "command": "selectPrevSuggestion", "when": "suggestWidgetMultipleSuggestions && suggestWidgetVisible && textInputFocus" }, { "key": "up", "command": "selectPrevSuggestion", "when": "suggestWidgetMultipleSuggestions && suggestWidgetVisible && textInputFocus" }, { "key": "ctrl+space", "command": "toggleSuggestionDetails", "when": "suggestWidgetVisible && textInputFocus" }, { "key": "ctrl+alt+space", "command": "toggleSuggestionFocus", "when": "suggestWidgetVisible && textInputFocus" }, { "key": "enter", "command": "acceptRenameInput", "when": "editorFocus && renameInputVisible" }, { "key": "shift+enter", "command": "acceptRenameInputWithPreview", "when": "config.editor.rename.enablePreview && editorFocus && renameInputVisible" }, { "key": "shift+escape", "command": "cancelLinkedEditingInput", "when": "LinkedEditingInputVisible && editorTextFocus" }, { "key": "escape", "command": "cancelLinkedEditingInput", "when": "LinkedEditingInputVisible && editorTextFocus" }, { "key": "shift+escape", "command": "cancelRenameInput", "when": "editorFocus && renameInputVisible" }, { "key": "escape", "command": "cancelRenameInput", "when": "editorFocus && renameInputVisible" }, { "key": "ctrl+shift+l", "command": "addCursorsAtSearchResults", "when": "fileMatchOrMatchFocus && searchViewletVisible" }, { "key": "ctrl+shift+;", "command": "breadcrumbs.focus", "when": "breadcrumbsPossible" }, { "key": "ctrl+shift+.", "command": "breadcrumbs.focusAndSelect", "when": "breadcrumbsPossible" }, { "key": "ctrl+right", "command": "breadcrumbs.focusNext", "when": "breadcrumbsActive && breadcrumbsVisible" }, { "key": "right", "command": "breadcrumbs.focusNext", "when": "breadcrumbsActive && breadcrumbsVisible" }, { "key": "ctrl+left", "command": "breadcrumbs.focusPrevious", "when": "breadcrumbsActive && breadcrumbsVisible" }, { "key": "left", "command": "breadcrumbs.focusPrevious", "when": "breadcrumbsActive && breadcrumbsVisible" }, { "key": "ctrl+enter", "command": "breadcrumbs.revealFocused", "when": "breadcrumbsActive && breadcrumbsVisible" }, { "key": "space", "command": "breadcrumbs.revealFocused", "when": "breadcrumbsActive && breadcrumbsVisible" }, { "key": "ctrl+enter", "command": "breadcrumbs.revealFocusedFromTreeAside", "when": "breadcrumbsActive && breadcrumbsVisible && listFocus && !inputFocus" }, { "key": "down", "command": "breadcrumbs.selectFocused", "when": "breadcrumbsActive && breadcrumbsVisible" }, { "key": "enter", "command": "breadcrumbs.selectFocused", "when": "breadcrumbsActive && breadcrumbsVisible" }, { "key": "ctrl+shift+.", "command": "breadcrumbs.toggleToOn", "when": "!config.breadcrumbs.enabled" }, { "key": "shift+escape", "command": "closeAccessibilityHelp", "when": "accessibilityHelpWidgetVisible && editorFocus" }, { "key": "escape", "command": "closeAccessibilityHelp", "when": "accessibilityHelpWidgetVisible && editorFocus" }, { "key": "escape", "command": "closeReplaceInFilesWidget", "when": "replaceInputBoxFocus && searchViewletVisible" }, { "key": "shift+alt+c", "command": "copyFilePath", "when": "!editorFocus" }, { "key": "ctrl+k ctrl+shift+c", "command": "copyRelativeFilePath", "when": "!editorFocus" }, { "key": "alt+enter", "command": "debug.openBreakpointToSide", "when": "breakpointsFocused" }, { "key": "ctrl+enter", "command": "debug.openBreakpointToSide", "when": "breakpointsFocused" }, { "key": "ctrl+f5", "command": "debug.openView", "when": "!debuggersAvailable" }, { "key": "f5", "command": "debug.openView", "when": "!debuggersAvailable" }, { "key": "delete", "command": "debug.removeBreakpoint", "when": "breakpointsFocused && !breakpointInputFocused" }, { "key": "delete", "command": "debug.removeWatchExpression", "when": "watchExpressionsFocused && !expressionSelected" }, { "key": "alt+-", "command": "decreaseSearchEditorContextLines", "when": "inSearchEditor" }, { "key": "tab", "command": "editor.action.inlineSuggest.commit", "when": "inlineSuggestionVisible && !editorTabMovesFocus && !inlineSuggestionHasIndentation" }, { "key": "shift+f9", "command": "editor.debug.action.toggleInlineBreakpoint", "when": "editorTextFocus" }, { "key": "shift+enter", "command": "editor.refocusCallHierarchy", "when": "callHierarchyVisible" }, { "key": "shift+alt+h", "command": "editor.showCallHierarchy", "when": "editorHasCallHierarchyProvider && editorTextFocus && !inReferenceSearchEditor" }, { "key": "shift+alt+h", "command": "editor.showIncomingCalls", "when": "callHierarchyVisible && callHierarchyDirection == 'outgoingCalls'" }, { "key": "shift+alt+h", "command": "editor.showOutgoingCalls", "when": "callHierarchyVisible && callHierarchyDirection == 'incomingCalls'" }, { "key": "ctrl+enter", "command": "explorer.openToSide", "when": "explorerViewletFocus && explorerViewletVisible && !inputFocus" }, { "key": "shift+alt+f", "command": "filesExplorer.findInFolder", "when": "explorerResourceIsFolder && explorerViewletVisible && filesExplorerFocus && !inputFocus" }, { "key": "alt+down", "command": "history.showNext", "when": "historyNavigationEnabled && historyNavigationWidget" }, { "key": "down", "command": "history.showNext", "when": "historyNavigationEnabled && historyNavigationWidget" }, { "key": "alt+up", "command": "history.showPrevious", "when": "historyNavigationEnabled && historyNavigationWidget" }, { "key": "up", "command": "history.showPrevious", "when": "historyNavigationEnabled && historyNavigationWidget" }, { "key": "alt+=", "command": "increaseSearchEditorContextLines", "when": "inSearchEditor" }, { "key": "down", "command": "interactive.history.next", "when": "!suggestWidgetVisible && resourceScheme == 'vscode-interactive' && interactiveInputCursorAtBoundary != 'none' && interactiveInputCursorAtBoundary != 'top'" }, { "key": "up", "command": "interactive.history.previous", "when": "!suggestWidgetVisible && resourceScheme == 'vscode-interactive' && interactiveInputCursorAtBoundary != 'bottom' && interactiveInputCursorAtBoundary != 'none'" }, { "key": "ctrl+k ctrl+a", "command": "keybindings.editor.addKeybinding", "when": "inKeybindings && keybindingFocus" }, { "key": "escape", "command": "keybindings.editor.clearSearchResults", "when": "inKeybindings && inKeybindingsSearch" }, { "key": "ctrl+c", "command": "keybindings.editor.copyKeybindingEntry", "when": "inKeybindings && keybindingFocus" }, { "key": "enter", "command": "keybindings.editor.defineKeybinding", "when": "inKeybindings && keybindingFocus" }, { "key": "ctrl+k ctrl+e", "command": "keybindings.editor.defineWhenExpression", "when": "inKeybindings && keybindingFocus" }, { "key": "ctrl+down", "command": "keybindings.editor.focusKeybindings", "when": "inKeybindings && inKeybindingsSearch" }, { "key": "alt+k", "command": "keybindings.editor.recordSearchKeys", "when": "inKeybindings && inKeybindingsSearch" }, { "key": "delete", "command": "keybindings.editor.removeKeybinding", "when": "inKeybindings && keybindingFocus && !inputFocus" }, { "key": "ctrl+f", "command": "keybindings.editor.searchKeybindings", "when": "inKeybindings" }, { "key": "alt+p", "command": "keybindings.editor.toggleSortByPrecedence", "when": "inKeybindings" }, { "key": "escape", "command": "list.clear", "when": "listFocus && listHasSelectionOrFocus && !inputFocus" }, { "key": "left", "command": "list.collapse", "when": "listFocus && !inputFocus" }, { "key": "ctrl+left", "command": "list.collapseAll", "when": "listFocus && !inputFocus" }, { "key": "right", "command": "list.expand", "when": "listFocus && !inputFocus" }, { "key": "shift+down", "command": "list.expandSelectionDown", "when": "listFocus && listSupportsMultiselect && !inputFocus" }, { "key": "shift+up", "command": "list.expandSelectionUp", "when": "listFocus && listSupportsMultiselect && !inputFocus" }, { "key": "down", "command": "list.focusDown", "when": "listFocus && !inputFocus" }, { "key": "home", "command": "list.focusFirst", "when": "listFocus && !inputFocus" }, { "key": "end", "command": "list.focusLast", "when": "listFocus && !inputFocus" }, { "key": "pagedown", "command": "list.focusPageDown", "when": "listFocus && !inputFocus" }, { "key": "pageup", "command": "list.focusPageUp", "when": "listFocus && !inputFocus" }, { "key": "up", "command": "list.focusUp", "when": "listFocus && !inputFocus" }, { "key": "ctrl+down", "command": "list.scrollDown", "when": "listFocus && !inputFocus" }, { "key": "ctrl+up", "command": "list.scrollUp", "when": "listFocus && !inputFocus" }, { "key": "enter", "command": "list.select", "when": "listFocus && !inputFocus" }, { "key": "ctrl+a", "command": "list.selectAll", "when": "listFocus && listSupportsMultiselect && !inputFocus" }, { "key": "space", "command": "list.toggleExpand", "when": "listFocus && !inputFocus" }, { "key": "ctrl+shift+enter", "command": "list.toggleSelection", "when": "listFocus && !inputFocus" }, { "key": "y", "command": "notebook.cell.changeToCode", "when": "notebookEditorFocused && !inputFocus && activeEditor == 'workbench.editor.notebook' && notebookCellType == 'markup'" }, { "key": "m", "command": "notebook.cell.changeToMarkdown", "when": "notebookEditorFocused && !inputFocus && activeEditor == 'workbench.editor.notebook' && notebookCellType == 'code'" }, { "key": "alt+delete", "command": "notebook.cell.clearOutputs", "when": "notebookCellEditable && notebookCellHasOutputs && notebookEditable && notebookEditorFocused && !inputFocus" }, { "key": "ctrl+k ctrl+c", "command": "notebook.cell.collapseCellInput", "when": "notebookCellListFocused && !inputFocus && !notebookCellInputIsCollapsed" }, { "key": "ctrl+k t", "command": "notebook.cell.collapseCellOutput", "when": "notebookCellHasOutputs && notebookCellListFocused && !inputFocus && !notebookCellOutputIsCollapsed" }, { "key": "shift+alt+down", "command": "notebook.cell.copyDown", "when": "notebookEditorFocused && !inputFocus" }, { "key": "shift+alt+up", "command": "notebook.cell.copyUp", "when": "notebookEditorFocused && !inputFocus" }, { "key": "delete", "command": "notebook.cell.delete", "when": "notebookEditable && notebookEditorFocused && !inputFocus" }, { "key": "enter", "command": "notebook.cell.edit", "when": "notebookCellListFocused && notebookEditable && !inputFocus" }, { "key": "ctrl+k ctrl+c", "command": "notebook.cell.expandCellInput", "when": "notebookCellInputIsCollapsed && notebookCellListFocused" }, { "key": "ctrl+k t", "command": "notebook.cell.expandCellOutput", "when": "notebookCellListFocused && notebookCellOutputIsCollapsed" }, { "key": "ctrl+down", "command": "notebook.cell.focusInOutput", "when": "notebookCellHasOutputs && notebookEditorFocused" }, { "key": "ctrl+up", "command": "notebook.cell.focusOutOutput", "when": "notebookEditorFocused" }, { "key": "ctrl+shift+enter", "command": "notebook.cell.insertCodeCellAbove", "when": "notebookCellListFocused && !inputFocus" }, { "key": "ctrl+enter", "command": "notebook.cell.insertCodeCellBelow", "when": "notebookCellListFocused && !inputFocus" }, { "key": "shift+alt+win+j", "command": "notebook.cell.joinAbove", "when": "notebookEditorFocused" }, { "key": "alt+win+j", "command": "notebook.cell.joinBelow", "when": "notebookEditorFocused" }, { "key": "alt+down", "command": "notebook.cell.moveDown", "when": "notebookEditorFocused && !inputFocus" }, { "key": "alt+up", "command": "notebook.cell.moveUp", "when": "notebookEditorFocused && !inputFocus" }, { "key": "ctrl+k ctrl+shift+\\", "command": "notebook.cell.split", "when": "notebookCellEditable && notebookEditable && notebookEditorFocused" }, { "key": "ctrl+l", "command": "notebook.centerActiveCell", "when": "notebookEditorFocused" }, { "key": "ctrl+f", "command": "notebook.find", "when": "notebookEditorFocused || !editorFocus && activeEditor == 'workbench.editor.notebook'" }, { "key": "ctrl+end", "command": "notebook.focusBottom", "when": "notebookEditorFocused && !inputFocus" }, { "key": "ctrl+down", "command": "notebook.focusNextEditor", "when": "notebookEditorFocused && notebookOutputFocused" }, { "key": "ctrl+home", "command": "notebook.focusTop", "when": "notebookEditorFocused && !inputFocus" }, { "key": "left", "command": "notebook.fold", "when": "notebookEditorFocused && !inputFocus && activeEditor == 'workbench.editor.notebook'" }, { "key": "ctrl+shift+[", "command": "notebook.fold", "when": "notebookEditorFocused && !inputFocus && activeEditor == 'workbench.editor.notebook'" }, { "key": "shift+alt+f", "command": "notebook.format", "when": "notebookEditable && !editorTextFocus && activeEditor == 'workbench.editor.notebook'" }, { "key": "escape", "command": "notebook.hideFind", "when": "notebookEditorFocused && notebookFindWidgetFocused" }, { "key": "right", "command": "notebook.unfold", "when": "notebookEditorFocused && !inputFocus && activeEditor == 'workbench.editor.notebook'" }, { "key": "ctrl+shift+]", "command": "notebook.unfold", "when": "notebookEditorFocused && !inputFocus && activeEditor == 'workbench.editor.notebook'" }, { "key": "delete", "command": "notification.clear", "when": "notificationFocus" }, { "key": "left", "command": "notification.collapse", "when": "notificationFocus" }, { "key": "right", "command": "notification.expand", "when": "notificationFocus" }, { "key": "enter", "command": "notification.toggle", "when": "notificationFocus" }, { "key": "space", "command": "notification.toggle", "when": "notificationFocus" }, { "key": "home", "command": "notifications.focusFirstToast", "when": "notificationFocus && notificationToastsVisible" }, { "key": "pageup", "command": "notifications.focusFirstToast", "when": "notificationFocus && notificationToastsVisible" }, { "key": "end", "command": "notifications.focusLastToast", "when": "notificationFocus && notificationToastsVisible" }, { "key": "pagedown", "command": "notifications.focusLastToast", "when": "notificationFocus && notificationToastsVisible" }, { "key": "down", "command": "notifications.focusNextToast", "when": "notificationFocus && notificationToastsVisible" }, { "key": "up", "command": "notifications.focusPreviousToast", "when": "notificationFocus && notificationToastsVisible" }, { "key": "ctrl+c", "command": "problems.action.copy", "when": "problemFocus" }, { "key": "ctrl+f", "command": "problems.action.focusFilter", "when": "focusedView == 'workbench.panel.markers.view'" }, { "key": "ctrl+down", "command": "problems.action.focusProblemsFromFilter", "when": "problemsFilterFocus" }, { "key": "enter", "command": "problems.action.open", "when": "problemFocus" }, { "key": "ctrl+enter", "command": "problems.action.openToSide", "when": "problemFocus" }, { "key": "ctrl+.", "command": "problems.action.showQuickFixes", "when": "problemFocus" }, { "key": "space", "command": "refactorPreview.toggleCheckedState", "when": "listFocus && refactorPreview.enabled && !inputFocus" }, { "key": "shift+alt+r", "command": "revealFileInOS", "when": "!editorFocus" }, { "key": "enter", "command": "revealReference", "when": "listFocus && referenceSearchVisible && !inputFocus" }, { "key": "ctrl+k s", "command": "saveAll" }, { "key": "ctrl+enter", "command": "scm.acceptInput", "when": "scmRepository" }, { "key": "alt+down", "command": "scm.forceViewNextCommit", "when": "scmRepository" }, { "key": "alt+up", "command": "scm.forceViewPreviousCommit", "when": "scmRepository" }, { "key": "down", "command": "scm.viewNextCommit", "when": "scmInputIsInLastPosition && scmRepository && !suggestWidgetVisible" }, { "key": "up", "command": "scm.viewPreviousCommit", "when": "scmInputIsInFirstPosition && scmRepository && !suggestWidgetVisible" }, { "key": "escape", "command": "search.action.cancel", "when": "listFocus && searchViewletVisible && !inputFocus && searchState != '0'" }, { "key": "ctrl+c", "command": "search.action.copyMatch", "when": "fileMatchOrMatchFocus" }, { "key": "shift+alt+c", "command": "search.action.copyPath", "when": "fileMatchOrFolderMatchWithResourceFocus" }, { "key": "f4", "command": "search.action.focusNextSearchResult", "when": "hasSearchResult || inSearchEditor" }, { "key": "shift+f4", "command": "search.action.focusPreviousSearchResult", "when": "hasSearchResult || inSearchEditor" }, { "key": "ctrl+up", "command": "search.action.focusSearchFromResults", "when": "firstMatchFocus && searchViewletVisible" }, { "key": "alt+enter", "command": "search.action.openInEditor", "when": "hasSearchResult && searchViewletFocus" }, { "key": "enter", "command": "search.action.openResult", "when": "fileMatchOrMatchFocus && searchViewletVisible" }, { "key": "ctrl+enter", "command": "search.action.openResultToSide", "when": "fileMatchOrMatchFocus && searchViewletVisible" }, { "key": "delete", "command": "search.action.remove", "when": "fileMatchOrMatchFocus && searchViewletVisible" }, { "key": "ctrl+shift+1", "command": "search.action.replace", "when": "matchFocus && replaceActive && searchViewletVisible" }, { "key": "ctrl+alt+enter", "command": "search.action.replaceAll", "when": "replaceActive && searchViewletVisible && !findWidgetVisible" }, { "key": "ctrl+shift+enter", "command": "search.action.replaceAllInFile", "when": "fileMatchFocus && replaceActive && searchViewletVisible" }, { "key": "ctrl+shift+1", "command": "search.action.replaceAllInFile", "when": "fileMatchFocus && replaceActive && searchViewletVisible" }, { "key": "ctrl+shift+enter", "command": "search.action.replaceAllInFolder", "when": "folderMatchFocus && replaceActive && searchViewletVisible" }, { "key": "ctrl+shift+1", "command": "search.action.replaceAllInFolder", "when": "folderMatchFocus && replaceActive && searchViewletVisible" }, { "key": "ctrl+down", "command": "search.focus.nextInputBox", "when": "inSearchEditor && inputBoxFocus || inputBoxFocus && searchViewletVisible" }, { "key": "ctrl+up", "command": "search.focus.previousInputBox", "when": "inSearchEditor && inputBoxFocus || inputBoxFocus && searchViewletVisible && !searchInputBoxFocus" }, { "key": "ctrl+shift+l", "command": "selectAllSearchEditorMatches", "when": "inSearchEditor" }, { "key": "escape", "command": "settings.action.focusLevelUp", "when": "inSettingsEditor && !inSettingsJSONEditor && !inSettingsSearch" }, { "key": "enter", "command": "settings.action.focusSettingControl", "when": "settingRowFocus" }, { "key": "down", "command": "settings.action.focusSettingsFromSearch", "when": "inSettingsSearch && !suggestWidgetVisible" }, { "key": "enter", "command": "settings.action.focusSettingsList", "when": "inSettingsEditor && settingsTocRowFocus" }, { "key": "left", "command": "settings.action.focusTOC", "when": "inSettingsEditor && settingRowFocus" }, { "key": "shift+f9", "command": "settings.action.showContextMenu", "when": "inSettingsEditor" }, { "key": "ctrl+; ctrl+x", "command": "testing.cancelRun" }, { "key": "ctrl+; ctrl+a", "command": "testing.debugAll" }, { "key": "ctrl+; ctrl+c", "command": "testing.debugAtCursor", "when": "editorTextFocus" }, { "key": "ctrl+; ctrl+f", "command": "testing.debugCurrentFile", "when": "editorTextFocus" }, { "key": "ctrl+; ctrl+e", "command": "testing.debugFailTests" }, { "key": "ctrl+; ctrl+l", "command": "testing.debugLastRun" }, { "key": "ctrl+; m", "command": "testing.openOutputPeek" }, { "key": "ctrl+; e", "command": "testing.reRunFailTests" }, { "key": "ctrl+; l", "command": "testing.reRunLastRun" }, { "key": "ctrl+; a", "command": "testing.runAll" }, { "key": "ctrl+; c", "command": "testing.runAtCursor", "when": "editorTextFocus" }, { "key": "ctrl+; f", "command": "testing.runCurrentFile", "when": "editorTextFocus" }, { "key": "ctrl+; ctrl+o", "command": "testing.showMostRecentOutput", "when": "testing.hasAnyResults" }, { "key": "alt+c", "command": "toggleSearchCaseSensitive", "when": "searchViewletFocus" }, { "key": "alt+c", "command": "toggleSearchEditorCaseSensitive", "when": "inSearchEditor && searchInputBoxFocus" }, { "key": "alt+l", "command": "toggleSearchEditorContextLines", "when": "inSearchEditor" }, { "key": "alt+r", "command": "toggleSearchEditorRegex", "when": "inSearchEditor && searchInputBoxFocus" }, { "key": "alt+w", "command": "toggleSearchEditorWholeWord", "when": "inSearchEditor && searchInputBoxFocus" }, { "key": "alt+p", "command": "toggleSearchPreserveCase", "when": "searchViewletFocus" }, { "key": "alt+r", "command": "toggleSearchRegex", "when": "searchViewletFocus" }, { "key": "alt+w", "command": "toggleSearchWholeWord", "when": "searchViewletFocus" }, { "key": "ctrl+alt+win+n", "command": "welcome.showNewFileEntries" }, { "key": "ctrl+w", "command": "workbench.action.closeActiveEditor" }, { "key": "ctrl+f4", "command": "workbench.action.closeActiveEditor" }, { "key": "ctrl+k ctrl+w", "command": "workbench.action.closeAllEditors" }, { "key": "ctrl+k ctrl+shift+w", "command": "workbench.action.closeAllGroups" }, { "key": "ctrl+k w", "command": "workbench.action.closeEditorsInGroup" }, { "key": "ctrl+k f", "command": "workbench.action.closeFolder", "when": "emptyWorkspaceSupport" }, { "key": "ctrl+w", "command": "workbench.action.closeGroup", "when": "activeEditorGroupEmpty && multipleEditorGroups" }, { "key": "ctrl+f4", "command": "workbench.action.closeGroup", "when": "activeEditorGroupEmpty && multipleEditorGroups" }, { "key": "shift+escape", "command": "workbench.action.closeQuickOpen", "when": "inQuickOpen" }, { "key": "escape", "command": "workbench.action.closeQuickOpen", "when": "inQuickOpen" }, { "key": "ctrl+k u", "command": "workbench.action.closeUnmodifiedEditors" }, { "key": "ctrl+shift+w", "command": "workbench.action.closeWindow" }, { "key": "alt+f4", "command": "workbench.action.closeWindow" }, { "key": "alt+f5", "command": "workbench.action.compareEditor.nextChange", "when": "textCompareEditorVisible" }, { "key": "shift+alt+f5", "command": "workbench.action.compareEditor.previousChange", "when": "textCompareEditorVisible" }, { "key": "shift+f5", "command": "workbench.action.debug.disconnect", "when": "focusedSessionIsAttach && inDebugMode" }, { "key": "ctrl+shift+f5", "command": "workbench.action.debug.restart", "when": "inDebugMode" }, { "key": "ctrl+f5", "command": "workbench.action.debug.run", "when": "debuggersAvailable && debugState != 'initializing'" }, { "key": "f5", "command": "workbench.action.debug.start", "when": "debuggersAvailable && debugState == 'inactive'" }, { "key": "shift+f11", "command": "workbench.action.debug.stepOut", "when": "debugState == 'stopped'" }, { "key": "f10", "command": "workbench.action.debug.stepOver", "when": "debugState == 'stopped'" }, { "key": "shift+f5", "command": "workbench.action.debug.stop", "when": "inDebugMode && !focusedSessionIsAttach" }, { "key": "ctrl+k m", "command": "workbench.action.editor.changeLanguageMode", "when": "!notebookEditorFocused" }, { "key": "ctrl+k p", "command": "workbench.action.files.copyPathOfActiveFile" }, { "key": "ctrl+n", "command": "workbench.action.files.newUntitledFile" }, { "key": "ctrl+o", "command": "workbench.action.files.openFile" }, { "key": "ctrl+k ctrl+o", "command": "workbench.action.files.openFolder" }, { "key": "ctrl+o", "command": "workbench.action.files.openLocalFile", "when": "remoteFileDialogVisible" }, { "key": "ctrl+k ctrl+o", "command": "workbench.action.files.openLocalFolder", "when": "remoteFileDialogVisible" }, { "key": "ctrl+k r", "command": "workbench.action.files.revealActiveFileInWindows" }, { "key": "ctrl+s", "command": "workbench.action.files.save" }, { "key": "ctrl+shift+s", "command": "workbench.action.files.saveAs" }, { "key": "ctrl+shift+s", "command": "workbench.action.files.saveLocalFile", "when": "remoteFileDialogVisible" }, { "key": "ctrl+k ctrl+shift+s", "command": "workbench.action.files.saveWithoutFormatting" }, { "key": "ctrl+k o", "command": "workbench.action.files.showOpenedFileInNewWindow", "when": "emptyWorkspaceSupport" }, { "key": "ctrl+shift+f", "command": "workbench.action.findInFiles" }, { "key": "ctrl+k ctrl+up", "command": "workbench.action.focusAboveGroup" }, { "key": "ctrl+k ctrl+down", "command": "workbench.action.focusBelowGroup" }, { "key": "ctrl+8", "command": "workbench.action.focusEighthEditorGroup" }, { "key": "ctrl+5", "command": "workbench.action.focusFifthEditorGroup" }, { "key": "ctrl+1", "command": "workbench.action.focusFirstEditorGroup" }, { "key": "ctrl+4", "command": "workbench.action.focusFourthEditorGroup" }, { "key": "ctrl+k ctrl+left", "command": "workbench.action.focusLeftGroup" }, { "key": "f6", "command": "workbench.action.focusNextPart" }, { "key": "shift+f6", "command": "workbench.action.focusPreviousPart" }, { "key": "ctrl+k ctrl+right", "command": "workbench.action.focusRightGroup" }, { "key": "ctrl+2", "command": "workbench.action.focusSecondEditorGroup" }, { "key": "ctrl+7", "command": "workbench.action.focusSeventhEditorGroup" }, { "key": "ctrl+0", "command": "workbench.action.focusSideBar" }, { "key": "ctrl+6", "command": "workbench.action.focusSixthEditorGroup" }, { "key": "ctrl+3", "command": "workbench.action.focusThirdEditorGroup" }, { "key": "ctrl+g", "command": "workbench.action.gotoLine" }, { "key": "ctrl+shift+o", "command": "workbench.action.gotoSymbol" }, { "key": "escape", "command": "workbench.action.hideInterfaceOverview", "when": "interfaceOverviewVisible" }, { "key": "down", "command": "workbench.action.interactivePlayground.arrowDown", "when": "interactivePlaygroundFocus && !editorTextFocus" }, { "key": "up", "command": "workbench.action.interactivePlayground.arrowUp", "when": "interactivePlaygroundFocus && !editorTextFocus" }, { "key": "pagedown", "command": "workbench.action.interactivePlayground.pageDown", "when": "interactivePlaygroundFocus && !editorTextFocus" }, { "key": "pageup", "command": "workbench.action.interactivePlayground.pageUp", "when": "interactivePlaygroundFocus && !editorTextFocus" }, { "key": "ctrl+k enter", "command": "workbench.action.keepEditor" }, { "key": "ctrl+k ctrl+r", "command": "workbench.action.keybindingsReference" }, { "key": "ctrl+9", "command": "workbench.action.lastEditorInGroup" }, { "key": "alt+0", "command": "workbench.action.lastEditorInGroup" }, { "key": "ctrl+k down", "command": "workbench.action.moveActiveEditorGroupDown" }, { "key": "ctrl+k left", "command": "workbench.action.moveActiveEditorGroupLeft" }, { "key": "ctrl+k right", "command": "workbench.action.moveActiveEditorGroupRight" }, { "key": "ctrl+k up", "command": "workbench.action.moveActiveEditorGroupUp" }, { "key": "ctrl+shift+pageup", "command": "workbench.action.moveEditorLeftInGroup" }, { "key": "ctrl+shift+pagedown", "command": "workbench.action.moveEditorRightInGroup" }, { "key": "shift+alt+1", "command": "workbench.action.moveEditorToFirstGroup" }, { "key": "shift+alt+9", "command": "workbench.action.moveEditorToLastGroup" }, { "key": "ctrl+alt+right", "command": "workbench.action.moveEditorToNextGroup" }, { "key": "ctrl+alt+left", "command": "workbench.action.moveEditorToPreviousGroup" }, { "key": "alt+left", "command": "workbench.action.navigateBack" }, { "key": "alt+right", "command": "workbench.action.navigateForward" }, { "key": "ctrl+k ctrl+q", "command": "workbench.action.navigateToLastEditLocation" }, { "key": "ctrl+shift+n", "command": "workbench.action.newWindow" }, { "key": "ctrl+pagedown", "command": "workbench.action.nextEditor" }, { "key": "ctrl+k ctrl+pagedown", "command": "workbench.action.nextEditorInGroup" }, { "key": "alt+1", "command": "workbench.action.openEditorAtIndex1" }, { "key": "alt+2", "command": "workbench.action.openEditorAtIndex2" }, { "key": "alt+3", "command": "workbench.action.openEditorAtIndex3" }, { "key": "alt+4", "command": "workbench.action.openEditorAtIndex4" }, { "key": "alt+5", "command": "workbench.action.openEditorAtIndex5" }, { "key": "alt+6", "command": "workbench.action.openEditorAtIndex6" }, { "key": "alt+7", "command": "workbench.action.openEditorAtIndex7" }, { "key": "alt+8", "command": "workbench.action.openEditorAtIndex8" }, { "key": "alt+9", "command": "workbench.action.openEditorAtIndex9" }, { "key": "ctrl+k ctrl+s", "command": "workbench.action.openGlobalKeybindings" }, { "key": "ctrl+r", "command": "workbench.action.openRecent" }, { "key": "ctrl+,", "command": "workbench.action.openSettings" }, { "key": "ctrl+shift+u", "command": "workbench.action.output.toggleOutput", "when": "workbench.panel.output.active" }, { "key": "ctrl+k shift+enter", "command": "workbench.action.pinEditor", "when": "!activeEditorIsPinned" }, { "key": "ctrl+pageup", "command": "workbench.action.previousEditor" }, { "key": "ctrl+k ctrl+pageup", "command": "workbench.action.previousEditorInGroup" }, { "key": "ctrl+e", "command": "workbench.action.quickOpen" }, { "key": "ctrl+p", "command": "workbench.action.quickOpen" }, { "key": "ctrl+shift+tab", "command": "workbench.action.quickOpenLeastRecentlyUsedEditorInGroup" }, { "key": "ctrl+tab", "command": "workbench.action.quickOpenPreviousRecentlyUsedEditorInGroup" }, { "key": "ctrl+q", "command": "workbench.action.quickOpenView" }, { "key": "ctrl+shift+t", "command": "workbench.action.reopenClosedEditor" }, { "key": "ctrl+shift+h", "command": "workbench.action.replaceInFiles" }, { "key": "ctrl+shift+j", "command": "workbench.action.search.toggleQueryDetails", "when": "inSearchEditor || searchViewletFocus" }, { "key": "ctrl+k ctrl+t", "command": "workbench.action.selectTheme" }, { "key": "ctrl+k ctrl+p", "command": "workbench.action.showAllEditors" }, { "key": "ctrl+t", "command": "workbench.action.showAllSymbols" }, { "key": "f1", "command": "workbench.action.showCommands" }, { "key": "ctrl+shift+p", "command": "workbench.action.showCommands" }, { "key": "ctrl+\\", "command": "workbench.action.splitEditor" }, { "key": "ctrl+k ctrl+\\", "command": "workbench.action.splitEditorOrthogonal" }, { "key": "ctrl+shift+b", "command": "workbench.action.tasks.build" }, { "key": "escape", "command": "workbench.action.terminal.clearSelection", "when": "terminalFocus && terminalProcessSupported && terminalTextSelected && !terminalFindVisible" }, { "key": "ctrl+shift+c", "command": "workbench.action.terminal.copySelection", "when": "terminalFocus && terminalProcessSupported && terminalTextSelected && terminalTextSelected" }, { "key": "ctrl+c", "command": "workbench.action.terminal.copySelection", "when": "terminalFocus && terminalProcessSupported && terminalTextSelected && terminalTextSelected" }, { "key": "f3", "command": "workbench.action.terminal.findNext", "when": "terminalFindFocused && terminalProcessSupported || terminalFocus && terminalProcessSupported" }, { "key": "shift+enter", "command": "workbench.action.terminal.findNext", "when": "terminalFindFocused && terminalProcessSupported" }, { "key": "shift+f3", "command": "workbench.action.terminal.findPrevious", "when": "terminalFindFocused && terminalProcessSupported || terminalFocus && terminalProcessSupported" }, { "key": "enter", "command": "workbench.action.terminal.findPrevious", "when": "terminalFindFocused && terminalProcessSupported" }, { "key": "ctrl+f", "command": "workbench.action.terminal.focusFind", "when": "terminalFindFocused && terminalProcessSupported || terminalFocus && terminalProcessSupported" }, { "key": "ctrl+pagedown", "command": "workbench.action.terminal.focusNext", "when": "terminalFocus && terminalProcessSupported && !terminalEditorFocus" }, { "key": "alt+down", "command": "workbench.action.terminal.focusNextPane", "when": "terminalFocus && terminalProcessSupported" }, { "key": "alt+right", "command": "workbench.action.terminal.focusNextPane", "when": "terminalFocus && terminalProcessSupported" }, { "key": "ctrl+pageup", "command": "workbench.action.terminal.focusPrevious", "when": "terminalFocus && terminalProcessSupported && !terminalEditorFocus" }, { "key": "alt+up", "command": "workbench.action.terminal.focusPreviousPane", "when": "terminalFocus && terminalProcessSupported" }, { "key": "alt+left", "command": "workbench.action.terminal.focusPreviousPane", "when": "terminalFocus && terminalProcessSupported" }, { "key": "ctrl+shift+\\", "command": "workbench.action.terminal.focusTabs", "when": "terminalFocus && terminalProcessSupported || terminalProcessSupported && terminalTabsFocus" }, { "key": "shift+escape", "command": "workbench.action.terminal.hideFind", "when": "terminalFindVisible && terminalFocus && terminalProcessSupported" }, { "key": "escape", "command": "workbench.action.terminal.hideFind", "when": "terminalFindVisible && terminalFocus && terminalProcessSupported" }, { "key": "ctrl+w", "command": "workbench.action.terminal.killEditor", "when": "terminalFocus && terminalProcessSupported && resourceScheme == 'vscode-terminal'" }, { "key": "ctrl+f4", "command": "workbench.action.terminal.killEditor", "when": "terminalFocus && terminalProcessSupported && resourceScheme == 'vscode-terminal'" }, { "key": "delete", "command": "workbench.action.terminal.killInstance", "when": "terminalIsOpen && terminalTabsFocus || terminalProcessSupported && terminalTabsFocus" }, { "key": "escape", "command": "workbench.action.terminal.navigationModeExit", "when": "accessibilityModeEnabled && terminalA11yTreeFocus && terminalProcessSupported" }, { "key": "ctrl+down", "command": "workbench.action.terminal.navigationModeFocusNext", "when": "accessibilityModeEnabled && terminalA11yTreeFocus && terminalProcessSupported || accessibilityModeEnabled && terminalFocus && terminalProcessSupported" }, { "key": "ctrl+up", "command": "workbench.action.terminal.navigationModeFocusPrevious", "when": "accessibilityModeEnabled && terminalA11yTreeFocus && terminalProcessSupported || accessibilityModeEnabled && terminalFocus && terminalProcessSupported" }, { "key": "ctrl+shift+`", "command": "workbench.action.terminal.new", "when": "terminalProcessSupported" }, { "key": "ctrl+shift+c", "command": "workbench.action.terminal.openNativeConsole", "when": "!terminalFocus" }, { "key": "ctrl+shift+v", "command": "workbench.action.terminal.paste", "when": "terminalFocus && terminalProcessSupported" }, { "key": "ctrl+v", "command": "workbench.action.terminal.paste", "when": "terminalFocus && terminalProcessSupported" }, { "key": "f2", "command": "workbench.action.terminal.renameInstance", "when": "terminalProcessSupported && terminalTabsFocus && terminalTabsSingularSelection" }, { "key": "ctrl+alt+pagedown", "command": "workbench.action.terminal.scrollDown", "when": "terminalFocus && terminalProcessSupported" }, { "key": "shift+pagedown", "command": "workbench.action.terminal.scrollDownPage", "when": "terminalFocus && terminalProcessSupported && !terminalAltBufferActive" }, { "key": "ctrl+end", "command": "workbench.action.terminal.scrollToBottom", "when": "terminalFocus && terminalProcessSupported" }, { "key": "ctrl+home", "command": "workbench.action.terminal.scrollToTop", "when": "terminalFocus && terminalProcessSupported" }, { "key": "ctrl+alt+pageup", "command": "workbench.action.terminal.scrollUp", "when": "terminalFocus && terminalProcessSupported" }, { "key": "shift+pageup", "command": "workbench.action.terminal.scrollUpPage", "when": "terminalFocus && terminalProcessSupported && !terminalAltBufferActive" }, { "key": "ctrl+v", "command": "workbench.action.terminal.sendSequence", "when": "terminalFocus && !accessibilityModeEnabled && terminalShellType == 'pwsh'", "args": {"text":"\u0016"} }, { "key": "ctrl+backspace", "command": "workbench.action.terminal.sendSequence", "when": "terminalFocus", "args": {"text":"\u0017"} }, { "key": "ctrl+backspace", "command": "workbench.action.terminal.sendSequence", "when": "terminalFocus && terminalShellType == 'cmd'", "args": {"text":"\b"} }, { "key": "ctrl+delete", "command": "workbench.action.terminal.sendSequence", "when": "terminalFocus", "args": {"text":"\u001bd"} }, { "key": "ctrl+shift+5", "command": "workbench.action.terminal.split", "when": "terminalFocus && terminalProcessSupported" }, { "key": "ctrl+shift+5", "command": "workbench.action.terminal.splitInstance", "when": "terminalProcessSupported && terminalTabsFocus" }, { "key": "alt+c", "command": "workbench.action.terminal.toggleFindCaseSensitive", "when": "terminalFindFocused && terminalProcessSupported || terminalFocus && terminalProcessSupported" }, { "key": "alt+r", "command": "workbench.action.terminal.toggleFindRegex", "when": "terminalFindFocused && terminalProcessSupported || terminalFocus && terminalProcessSupported" }, { "key": "alt+w", "command": "workbench.action.terminal.toggleFindWholeWord", "when": "terminalFindFocused && terminalProcessSupported || terminalFocus && terminalProcessSupported" }, { "key": "ctrl+`", "command": "workbench.action.terminal.toggleTerminal", "when": "terminal.active" }, { "key": "shift+alt+0", "command": "workbench.action.toggleEditorGroupLayout" }, { "key": "f11", "command": "workbench.action.toggleFullScreen", "when": "!isIOS" }, { "key": "ctrl+j", "command": "workbench.action.togglePanel" }, { "key": "ctrl+b", "command": "workbench.action.toggleSidebarVisibility" }, { "key": "ctrl+k z", "command": "workbench.action.toggleZenMode" }, { "key": "ctrl+k shift+enter", "command": "workbench.action.unpinEditor", "when": "activeEditorIsPinned" }, { "key": "ctrl+numpad_add", "command": "workbench.action.zoomIn" }, { "key": "ctrl+shift+=", "command": "workbench.action.zoomIn" }, { "key": "ctrl+=", "command": "workbench.action.zoomIn" }, { "key": "ctrl+numpad_subtract", "command": "workbench.action.zoomOut" }, { "key": "ctrl+shift+-", "command": "workbench.action.zoomOut" }, { "key": "ctrl+-", "command": "workbench.action.zoomOut" }, { "key": "ctrl+numpad0", "command": "workbench.action.zoomReset" }, { "key": "ctrl+shift+m", "command": "workbench.actions.view.problems", "when": "workbench.panel.markers.view.active" }, { "key": "escape", "command": "workbench.banner.focusBanner", "when": "bannerFocused" }, { "key": "down", "command": "workbench.banner.focusNextAction", "when": "bannerFocused" }, { "key": "right", "command": "workbench.banner.focusNextAction", "when": "bannerFocused" }, { "key": "up", "command": "workbench.banner.focusPreviousAction", "when": "bannerFocused" }, { "key": "left", "command": "workbench.banner.focusPreviousAction", "when": "bannerFocused" }, { "key": "ctrl+shift+y", "command": "workbench.debug.action.toggleRepl", "when": "workbench.panel.repl.view.active" }, { "key": "ctrl+k ctrl+m", "command": "workbench.extensions.action.showRecommendedKeymapExtensions" }, { "key": "ctrl+k c", "command": "workbench.files.action.compareWithClipboard" }, { "key": "ctrl+k d", "command": "workbench.files.action.compareWithSaved" }, { "key": "ctrl+k e", "command": "workbench.files.action.focusOpenEditorsView", "when": "workbench.explorer.openEditorsView.active" }, { "key": "escape", "command": "workbench.statusBar.clearFocus", "when": "statusBarFocused" }, { "key": "home", "command": "workbench.statusBar.focusFirst", "when": "statusBarFocused" }, { "key": "end", "command": "workbench.statusBar.focusLast", "when": "statusBarFocused" }, { "key": "down", "command": "workbench.statusBar.focusNext", "when": "statusBarFocused" }, { "key": "right", "command": "workbench.statusBar.focusNext", "when": "statusBarFocused" }, { "key": "up", "command": "workbench.statusBar.focusPrevious", "when": "statusBarFocused" }, { "key": "left", "command": "workbench.statusBar.focusPrevious", "when": "statusBarFocused" }, { "key": "ctrl+shift+d", "command": "workbench.view.debug", "when": "viewContainer.workbench.view.debug.enabled" }, { "key": "ctrl+shift+e", "command": "workbench.view.explorer", "when": "viewContainer.workbench.view.explorer.enabled" }, { "key": "ctrl+shift+x", "command": "workbench.view.extensions", "when": "viewContainer.workbench.view.extensions.enabled" }, { "key": "ctrl+shift+g", "command": "workbench.view.scm", "when": "workbench.scm.active" }, { "key": "ctrl+shift+f", "command": "workbench.view.search", "when": "workbench.view.search.active && neverMatch =~ /doesNotMatch/" }, { "key": "ctrl+right", "command": "breadcrumbs.focusNextWithPicker", "when": "breadcrumbsActive && breadcrumbsVisible && listFocus && !inputFocus" }, { "key": "ctrl+left", "command": "breadcrumbs.focusPreviousWithPicker", "when": "breadcrumbsActive && breadcrumbsVisible && listFocus && !inputFocus" }, { "key": "escape", "command": "breadcrumbs.selectEditor", "when": "breadcrumbsActive && breadcrumbsVisible" }, { "key": "ctrl+k down", "command": "views.moveViewDown", "when": "focusedView != ''" }, { "key": "ctrl+k left", "command": "views.moveViewLeft", "when": "focusedView != ''" }, { "key": "ctrl+k right", "command": "views.moveViewRight", "when": "focusedView != ''" }, { "key": "ctrl+k up", "command": "views.moveViewUp", "when": "focusedView != ''" }, { "key": "f6", "command": "workbench.action.debug.pause", "when": "debugState == 'running'" }, { "key": "f2", "command": "debug.renameWatchExpression", "when": "watchExpressionsFocused" }, { "key": "f2", "command": "debug.setVariable", "when": "variablesFocused" }, { "key": "space", "command": "debug.toggleBreakpoint", "when": "breakpointsFocused && !inputFocus" }, { "key": "shift+delete", "command": "deleteFile", "when": "explorerViewletVisible && filesExplorerFocus && !explorerResourceReadonly && !inputFocus" }, { "key": "delete", "command": "deleteFile", "when": "explorerViewletVisible && filesExplorerFocus && !explorerResourceMoveableToTrash && !explorerResourceReadonly && !inputFocus" }, { "key": "escape", "command": "editor.closeCallHierarchy", "when": "callHierarchyVisible && !config.editor.stablePeek" }, { "key": "enter", "command": "explorer.openAndPassFocus", "when": "explorerViewletVisible && filesExplorerFocus && !explorerResourceIsFolder && !inputFocus" }, { "key": "escape", "command": "filesExplorer.cancelCut", "when": "explorerResourceCut && explorerViewletVisible && filesExplorerFocus && !inputFocus" }, { "key": "ctrl+c", "command": "filesExplorer.copy", "when": "explorerViewletVisible && filesExplorerFocus && !explorerResourceIsRoot && !inputFocus" }, { "key": "ctrl+x", "command": "filesExplorer.cut", "when": "explorerViewletVisible && filesExplorerFocus && !explorerResourceIsRoot && !explorerResourceReadonly && !inputFocus" }, { "key": "space", "command": "filesExplorer.openFilePreserveFocus", "when": "explorerViewletVisible && filesExplorerFocus && !explorerResourceIsFolder && !inputFocus" }, { "key": "ctrl+v", "command": "filesExplorer.paste", "when": "explorerViewletVisible && filesExplorerFocus && !explorerResourceReadonly && !inputFocus" }, { "key": "home", "command": "firstCompressedFolder", "when": "explorerViewletCompressedFocus && explorerViewletVisible && filesExplorerFocus && !explorerViewletCompressedFirstFocus && !inputFocus" }, { "key": "end", "command": "lastCompressedFolder", "when": "explorerViewletCompressedFocus && explorerViewletVisible && filesExplorerFocus && !explorerViewletCompressedLastFocus && !inputFocus" }, { "key": "delete", "command": "moveFileToTrash", "when": "explorerResourceMoveableToTrash && explorerViewletVisible && filesExplorerFocus && !explorerResourceReadonly && !inputFocus" }, { "key": "right", "command": "nextCompressedFolder", "when": "explorerViewletCompressedFocus && explorerViewletVisible && filesExplorerFocus && !explorerViewletCompressedLastFocus && !inputFocus" }, { "key": "left", "command": "previousCompressedFolder", "when": "explorerViewletCompressedFocus && explorerViewletVisible && filesExplorerFocus && !explorerViewletCompressedFirstFocus && !inputFocus" }, { "key": "delete", "command": "remote.tunnel.closeInline", "when": "tunnelCloseable && tunnelViewFocus" }, { "key": "ctrl+c", "command": "remote.tunnel.copyAddressInline", "when": "tunnelViewFocus && tunnelType == 'Detected' && tunnelViewMultiSelection == 'undefined' || tunnelViewFocus && tunnelType == 'Forwarded' && tunnelViewMultiSelection == 'undefined'" }, { "key": "f2", "command": "remote.tunnel.label", "when": "tunnelViewFocus && tunnelType == 'Forwarded' && tunnelViewMultiSelection == 'undefined'" }, { "key": "f2", "command": "renameFile", "when": "explorerViewletVisible && filesExplorerFocus && !explorerResourceIsRoot && !explorerResourceReadonly && !inputFocus" }, { "key": "f5", "command": "workbench.action.debug.continue", "when": "debugState == 'stopped'" }, { "key": "f11", "command": "workbench.action.debug.stepInto", "when": "debugState != 'inactive'" }, { "key": "shift+escape", "command": "closeReferenceSearch", "when": "referenceSearchVisible && !config.editor.stablePeek" }, { "key": "escape", "command": "closeReferenceSearch", "when": "referenceSearchVisible && !config.editor.stablePeek" }, { "key": "escape", "command": "notifications.hideList", "when": "notificationCenterVisible" }, { "key": "alt+left", "command": "workbench.action.quickInputBack", "when": "inQuickOpen" }, { "key": "ctrl+tab", "command": "workbench.action.quickOpenNavigateNextInEditorPicker", "when": "inEditorsPicker && inQuickOpen" }, { "key": "ctrl+e", "command": "workbench.action.quickOpenNavigateNextInFilePicker", "when": "inFilesPicker && inQuickOpen" }, { "key": "ctrl+p", "command": "workbench.action.quickOpenNavigateNextInFilePicker", "when": "inFilesPicker && inQuickOpen" }, { "key": "ctrl+r", "command": "workbench.action.quickOpenNavigateNextInRecentFilesPicker", "when": "inQuickOpen && inRecentFilesPicker" }, { "key": "ctrl+q", "command": "workbench.action.quickOpenNavigateNextInViewPicker", "when": "inQuickOpen && inViewsPicker" }, { "key": "ctrl+shift+tab", "command": "workbench.action.quickOpenNavigatePreviousInEditorPicker", "when": "inEditorsPicker && inQuickOpen" }, { "key": "ctrl+shift+e", "command": "workbench.action.quickOpenNavigatePreviousInFilePicker", "when": "inFilesPicker && inQuickOpen" }, { "key": "ctrl+shift+p", "command": "workbench.action.quickOpenNavigatePreviousInFilePicker", "when": "inFilesPicker && inQuickOpen" }, { "key": "ctrl+shift+r", "command": "workbench.action.quickOpenNavigatePreviousInRecentFilesPicker", "when": "inQuickOpen && inRecentFilesPicker" }, { "key": "ctrl+shift+q", "command": "workbench.action.quickOpenNavigatePreviousInViewPicker", "when": "inQuickOpen && inViewsPicker" }, { "key": "ctrl+r", "command": "workbench.action.reloadWindow", "when": "isDevelopment" }, { "key": "ctrl+shift+f", "command": "workbench.action.terminal.searchWorkspace", "when": "terminalFocus && terminalProcessSupported && terminalProcessSupported && terminalTextSelected" }, { "key": "ctrl+shift+i", "command": "workbench.action.toggleDevTools", "when": "isDevelopment" }, { "key": "escape", "command": "notifications.hideToasts", "when": "notificationFocus && notificationToastsVisible" }, { "key": "alt+o", "command": "clangd.switchheadersource", "when": "editorTextFocus" }, { "key": "ctrl+shift+v", "command": "markdown.showPreview", "when": "!notebookEditorFocused && editorLangId == 'markdown'" }, { "key": "shift+alt+f12", "command": "references-view.findReferences", "when": "editorHasReferenceProvider" }, { "key": "shift+alt+t", "command": "clangd.typeHierarchy", "when": "editorTextFocus" }, { "key": "ctrl+k v", "command": "markdown.showPreviewToSide", "when": "!notebookEditorFocused && editorLangId == 'markdown'" }, { "key": "f4", "command": "references-view.next", "when": "reference-list.hasResult && references-view.canNavigate" }, { "key": "shift+f4", "command": "references-view.prev", "when": "reference-list.hasResult && references-view.canNavigate" }, { "key": "shift+alt+h", "command": "references-view.showCallHierarchy", "when": "editorHasCallHierarchyProvider" } ] ''') def main(): keys = set() for binding in DEFAULT_KEYBINDINGS: key = binding['key'] if key in [ 'ctrl+a', 'ctrl+x', 'ctrl+c', 'ctrl+v', 'ctrl+z', 'ctrl+y', 'ctrl+insert', 'shift+insert', 'shift+delete', 'ctrl+-', 'ctrl+=', 'ctrl+numpad0', 'ctrl+numpad_add', 'ctrl+numpad_subtract', ]: continue when = binding.get('when') if when is None: pass # add keys without conditions elif when in [ 'workbench.panel.output.active', 'terminal.active', 'workbench.panel.markers.view.active', 'workbench.panel.repl.view.active', 'workbench.explorer.openEditorsView.active', 'workbench.scm.active', 'terminalProcessSupported', 'viewContainer.workbench.view.debug.enabled', 'viewContainer.workbench.view.explorer.enabled', 'viewContainer.workbench.view.extensions.enabled', ]: pass # add keys with these conditions elif when.startswith('!') and ' ' not in when: pass # add simple negation conditions else: continue assert key not in keys keys.add(key) new_keybindings = [] for key in sorted(keys): new_keybindings.append({ 'key': key, 'command': '', 'when': '!config.windhawk.editedModId' }) print(json.dumps(new_keybindings, indent=4)) if __name__ == '__main__': main() ================================================ FILE: src/vscode-windhawk/package.json ================================================ { "name": "windhawk", "displayName": "Windhawk", "description": "Part of the Windhawk Windows customization tool", "version": "1.7.3", "icon": "assets/main-icon.png", "publisher": "m417z", "engines": { "vscode": "^1.74.2" }, "categories": [ "Other" ], "activationEvents": [ "*" ], "repository": { "type": "git", "url": "https://github.com/ramensoftware/windhawk-vscode-extension" }, "main": "./dist/extension.js", "contributes": { "viewsContainers": { "activitybar": [ { "id": "windhawk", "title": "Windhawk", "icon": "assets/tab-icon-white.svg" } ] }, "views": { "windhawk": [ { "type": "webview", "id": "windhawk.sidebar", "name": "Windhawk", "icon": "assets/tab-icon-white.svg" } ] }, "commands": [ { "command": "windhawk.start", "title": "%commands.start.title%", "category": "%commands.category%" } ], "keybindings": [ { "key": "ctrl+b", "command": "windhawk.compileMod", "when": "config.windhawk.editedModId" }, { "key": "ctrl+shift+alt+p", "command": "workbench.action.showCommands", "when": "!config.windhawk.editedModId" }, { "key": "alt+0", "command": "", "when": "!config.windhawk.editedModId" }, { "key": "alt+1", "command": "", "when": "!config.windhawk.editedModId" }, { "key": "alt+2", "command": "", "when": "!config.windhawk.editedModId" }, { "key": "alt+3", "command": "", "when": "!config.windhawk.editedModId" }, { "key": "alt+4", "command": "", "when": "!config.windhawk.editedModId" }, { "key": "alt+5", "command": "", "when": "!config.windhawk.editedModId" }, { "key": "alt+6", "command": "", "when": "!config.windhawk.editedModId" }, { "key": "alt+7", "command": "", "when": "!config.windhawk.editedModId" }, { "key": "alt+8", "command": "", "when": "!config.windhawk.editedModId" }, { "key": "alt+9", "command": "", "when": "!config.windhawk.editedModId" }, { "key": "alt+f1", "command": "", "when": "!config.windhawk.editedModId" }, { "key": "alt+f4", "command": "", "when": "!config.windhawk.editedModId" }, { "key": "alt+left", "command": "", "when": "!config.windhawk.editedModId" }, { "key": "alt+right", "command": "", "when": "!config.windhawk.editedModId" }, { "key": "alt+z", "command": "", "when": "!config.windhawk.editedModId" }, { "key": "ctrl+,", "command": "", "when": "!config.windhawk.editedModId" }, { "key": "ctrl+0", "command": "", "when": "!config.windhawk.editedModId" }, { "key": "ctrl+1", "command": "", "when": "!config.windhawk.editedModId" }, { "key": "ctrl+2", "command": "", "when": "!config.windhawk.editedModId" }, { "key": "ctrl+3", "command": "", "when": "!config.windhawk.editedModId" }, { "key": "ctrl+4", "command": "", "when": "!config.windhawk.editedModId" }, { "key": "ctrl+5", "command": "", "when": "!config.windhawk.editedModId" }, { "key": "ctrl+6", "command": "", "when": "!config.windhawk.editedModId" }, { "key": "ctrl+7", "command": "", "when": "!config.windhawk.editedModId" }, { "key": "ctrl+8", "command": "", "when": "!config.windhawk.editedModId" }, { "key": "ctrl+9", "command": "", "when": "!config.windhawk.editedModId" }, { "key": "ctrl+; a", "command": "", "when": "!config.windhawk.editedModId" }, { "key": "ctrl+; ctrl+a", "command": "", "when": "!config.windhawk.editedModId" }, { "key": "ctrl+; ctrl+e", "command": "", "when": "!config.windhawk.editedModId" }, { "key": "ctrl+; ctrl+l", "command": "", "when": "!config.windhawk.editedModId" }, { "key": "ctrl+; ctrl+x", "command": "", "when": "!config.windhawk.editedModId" }, { "key": "ctrl+; e", "command": "", "when": "!config.windhawk.editedModId" }, { "key": "ctrl+; l", "command": "", "when": "!config.windhawk.editedModId" }, { "key": "ctrl+; m", "command": "", "when": "!config.windhawk.editedModId" }, { "key": "ctrl+\\", "command": "", "when": "!config.windhawk.editedModId" }, { "key": "ctrl+`", "command": "", "when": "!config.windhawk.editedModId" }, { "key": "ctrl+alt+left", "command": "", "when": "!config.windhawk.editedModId" }, { "key": "ctrl+alt+right", "command": "", "when": "!config.windhawk.editedModId" }, { "key": "ctrl+alt+win+n", "command": "", "when": "!config.windhawk.editedModId" }, { "key": "ctrl+b", "command": "", "when": "!config.windhawk.editedModId" }, { "key": "ctrl+e", "command": "", "when": "!config.windhawk.editedModId" }, { "key": "ctrl+f4", "command": "", "when": "!config.windhawk.editedModId" }, { "key": "ctrl+f5", "command": "", "when": "!config.windhawk.editedModId" }, { "key": "ctrl+g", "command": "", "when": "!config.windhawk.editedModId" }, { "key": "ctrl+j", "command": "", "when": "!config.windhawk.editedModId" }, { "key": "ctrl+k c", "command": "", "when": "!config.windhawk.editedModId" }, { "key": "ctrl+k ctrl+\\", "command": "", "when": "!config.windhawk.editedModId" }, { "key": "ctrl+k ctrl+down", "command": "", "when": "!config.windhawk.editedModId" }, { "key": "ctrl+k ctrl+left", "command": "", "when": "!config.windhawk.editedModId" }, { "key": "ctrl+k ctrl+m", "command": "", "when": "!config.windhawk.editedModId" }, { "key": "ctrl+k ctrl+o", "command": "", "when": "!config.windhawk.editedModId" }, { "key": "ctrl+k ctrl+p", "command": "", "when": "!config.windhawk.editedModId" }, { "key": "ctrl+k ctrl+pagedown", "command": "", "when": "!config.windhawk.editedModId" }, { "key": "ctrl+k ctrl+pageup", "command": "", "when": "!config.windhawk.editedModId" }, { "key": "ctrl+k ctrl+q", "command": "", "when": "!config.windhawk.editedModId" }, { "key": "ctrl+k ctrl+r", "command": "", "when": "!config.windhawk.editedModId" }, { "key": "ctrl+k ctrl+right", "command": "", "when": "!config.windhawk.editedModId" }, { "key": "ctrl+k ctrl+s", "command": "", "when": "!config.windhawk.editedModId" }, { "key": "ctrl+k ctrl+shift+c", "command": "", "when": "!config.windhawk.editedModId" }, { "key": "ctrl+k ctrl+shift+s", "command": "", "when": "!config.windhawk.editedModId" }, { "key": "ctrl+k ctrl+shift+w", "command": "", "when": "!config.windhawk.editedModId" }, { "key": "ctrl+k ctrl+t", "command": "", "when": "!config.windhawk.editedModId" }, { "key": "ctrl+k ctrl+up", "command": "", "when": "!config.windhawk.editedModId" }, { "key": "ctrl+k ctrl+w", "command": "", "when": "!config.windhawk.editedModId" }, { "key": "ctrl+k d", "command": "", "when": "!config.windhawk.editedModId" }, { "key": "ctrl+k down", "command": "", "when": "!config.windhawk.editedModId" }, { "key": "ctrl+k e", "command": "", "when": "!config.windhawk.editedModId" }, { "key": "ctrl+k enter", "command": "", "when": "!config.windhawk.editedModId" }, { "key": "ctrl+k left", "command": "", "when": "!config.windhawk.editedModId" }, { "key": "ctrl+k m", "command": "", "when": "!config.windhawk.editedModId" }, { "key": "ctrl+k p", "command": "", "when": "!config.windhawk.editedModId" }, { "key": "ctrl+k r", "command": "", "when": "!config.windhawk.editedModId" }, { "key": "ctrl+k right", "command": "", "when": "!config.windhawk.editedModId" }, { "key": "ctrl+k s", "command": "", "when": "!config.windhawk.editedModId" }, { "key": "ctrl+k shift+enter", "command": "", "when": "!config.windhawk.editedModId" }, { "key": "ctrl+k u", "command": "", "when": "!config.windhawk.editedModId" }, { "key": "ctrl+k up", "command": "", "when": "!config.windhawk.editedModId" }, { "key": "ctrl+k w", "command": "", "when": "!config.windhawk.editedModId" }, { "key": "ctrl+k z", "command": "", "when": "!config.windhawk.editedModId" }, { "key": "ctrl+m", "command": "", "when": "!config.windhawk.editedModId" }, { "key": "ctrl+n", "command": "", "when": "!config.windhawk.editedModId" }, { "key": "ctrl+o", "command": "", "when": "!config.windhawk.editedModId" }, { "key": "ctrl+p", "command": "", "when": "!config.windhawk.editedModId" }, { "key": "ctrl+pagedown", "command": "", "when": "!config.windhawk.editedModId" }, { "key": "ctrl+pageup", "command": "", "when": "!config.windhawk.editedModId" }, { "key": "ctrl+q", "command": "", "when": "!config.windhawk.editedModId" }, { "key": "ctrl+r", "command": "", "when": "!config.windhawk.editedModId" }, { "key": "ctrl+s", "command": "", "when": "!config.windhawk.editedModId" }, { "key": "ctrl+shift+-", "command": "", "when": "!config.windhawk.editedModId" }, { "key": "ctrl+shift+.", "command": "", "when": "!config.windhawk.editedModId" }, { "key": "ctrl+shift+=", "command": "", "when": "!config.windhawk.editedModId" }, { "key": "ctrl+shift+`", "command": "", "when": "!config.windhawk.editedModId" }, { "key": "ctrl+shift+b", "command": "", "when": "!config.windhawk.editedModId" }, { "key": "ctrl+shift+c", "command": "", "when": "!config.windhawk.editedModId" }, { "key": "ctrl+shift+d", "command": "", "when": "!config.windhawk.editedModId" }, { "key": "ctrl+shift+e", "command": "", "when": "!config.windhawk.editedModId" }, { "key": "ctrl+shift+f", "command": "", "when": "!config.windhawk.editedModId" }, { "key": "ctrl+shift+g", "command": "", "when": "!config.windhawk.editedModId" }, { "key": "ctrl+shift+h", "command": "", "when": "!config.windhawk.editedModId" }, { "key": "ctrl+shift+m", "command": "", "when": "!config.windhawk.editedModId" }, { "key": "ctrl+shift+n", "command": "", "when": "!config.windhawk.editedModId" }, { "key": "ctrl+shift+o", "command": "", "when": "!config.windhawk.editedModId" }, { "key": "ctrl+shift+p", "command": "", "when": "!config.windhawk.editedModId" }, { "key": "ctrl+shift+pagedown", "command": "", "when": "!config.windhawk.editedModId" }, { "key": "ctrl+shift+pageup", "command": "", "when": "!config.windhawk.editedModId" }, { "key": "ctrl+shift+s", "command": "", "when": "!config.windhawk.editedModId" }, { "key": "ctrl+shift+t", "command": "", "when": "!config.windhawk.editedModId" }, { "key": "ctrl+shift+tab", "command": "", "when": "!config.windhawk.editedModId" }, { "key": "ctrl+shift+u", "command": "", "when": "!config.windhawk.editedModId" }, { "key": "ctrl+shift+w", "command": "", "when": "!config.windhawk.editedModId" }, { "key": "ctrl+shift+x", "command": "", "when": "!config.windhawk.editedModId" }, { "key": "ctrl+shift+y", "command": "", "when": "!config.windhawk.editedModId" }, { "key": "ctrl+shift+z", "command": "", "when": "!config.windhawk.editedModId" }, { "key": "ctrl+t", "command": "", "when": "!config.windhawk.editedModId" }, { "key": "ctrl+tab", "command": "", "when": "!config.windhawk.editedModId" }, { "key": "ctrl+w", "command": "", "when": "!config.windhawk.editedModId" }, { "key": "f1", "command": "", "when": "!config.windhawk.editedModId" }, { "key": "f11", "command": "", "when": "!config.windhawk.editedModId" }, { "key": "f5", "command": "", "when": "!config.windhawk.editedModId" }, { "key": "f6", "command": "", "when": "!config.windhawk.editedModId" }, { "key": "shift+alt+0", "command": "", "when": "!config.windhawk.editedModId" }, { "key": "shift+alt+1", "command": "", "when": "!config.windhawk.editedModId" }, { "key": "shift+alt+9", "command": "", "when": "!config.windhawk.editedModId" }, { "key": "shift+alt+c", "command": "", "when": "!config.windhawk.editedModId" }, { "key": "shift+alt+r", "command": "", "when": "!config.windhawk.editedModId" }, { "key": "shift+f6", "command": "", "when": "!config.windhawk.editedModId" } ], "configuration": { "title": "Windhawk", "properties": { "windhawk.editedModId": { "type": [ "string", "null" ], "default": null, "description": "The edited mod id, if any." }, "windhawk.editedModWasModified": { "type": [ "boolean" ], "default": false, "description": "A flag indicating whether the edited mod was modified." } } }, "grammars": [ { "injectTo": [ "source.cpp" ], "scopeName": "source.cpp.windhawk", "path": "./syntaxes/cpp.injection.json", "embeddedLanguages": { "meta.embedded.block.yaml": "yaml", "meta.embedded.block.markdown": "markdown" } } ] }, "scripts": { "vscode:prepublish": "npm run clean && npm run webpack-prod", "electron-rebuild": ".\\node_modules\\.bin\\electron-rebuild.cmd -v 19.1.8", "webpack": "npm run electron-rebuild && webpack --mode development", "webpack-dev": "npm run electron-rebuild && webpack --mode development --watch", "webpack-prod": "npm run electron-rebuild -- -a ia32 && webpack --mode production", "compile": "npm run electron-rebuild -- -a ia32 && tsc -p ./ --sourceMap false", "lint": "eslint . --ext .ts,.tsx", "watch": "npm run electron-rebuild && tsc -w -p ./", "clean": "rimraf ./out/ ./dist/ ./prebuilds/" }, "dependencies": { "fs-ext": "^2.1.1", "ini-win": "^3.0.4", "js-yaml": "^4.1.1", "jsonschema": "^1.5.0", "native-reg": "^1.1.1", "node-fetch": "^2.7.0", "semver": "^7.7.3", "vscode-nls-i18n": "^0.2.4" }, "devDependencies": { "@types/fs-ext": "^2.0.3", "@types/js-yaml": "^4.0.9", "@types/node": "^22.19.1", "@types/node-fetch": "^2.6.13", "@types/semver": "^7.7.1", "@types/vscode": "^1.74.2", "@typescript-eslint/eslint-plugin": "^8.47.0", "@typescript-eslint/parser": "^8.47.0", "copy-webpack-plugin": "^13.0.1", "@electron/rebuild": "^3.7.2", "eslint": "^9.39.1", "node-loader": "^2.1.0", "rimraf": "^6.1.2", "ts-loader": "^9.5.4", "typescript": "^5.9.3", "webpack": "^5.103.0", "webpack-cli": "^6.0.1" } } ================================================ FILE: src/vscode-windhawk/package.nls.json ================================================ { "extensionName": "Windhawk", "commands.category": "Windhawk", "commands.start.title": "Open Windhawk Tab" } ================================================ FILE: src/vscode-windhawk/src/config.ts ================================================ // https://stackoverflow.com/a/45074641 declare const v8debug: any; const debug = typeof v8debug === 'object' || /--debug|--inspect/.test(process.execArgv.join(' ')); export default { urls: { modsUrlRoot: 'https://mods.windhawk.net/', modsFolder: 'https://mods.windhawk.net/mods/', }, debug: debug ? { reactProjectBuildPath: String.raw`C:\Windhawk-dev\vscode-windhawk-ui\dist\apps\vscode-windhawk-ui`, appRootPath: String.raw`C:\Windhawk-dev\Windhawk`, disableMinimalMode: true, disableEnvVarCheck: true, } : { reactProjectBuildPath: null, appRootPath: null, disableMinimalMode: false, disableEnvVarCheck: false, }, }; ================================================ FILE: src/vscode-windhawk/src/extension.ts ================================================ import * as fs from 'fs'; import fetch from 'node-fetch'; import * as path from 'path'; import * as semver from 'semver'; import * as vscode from 'vscode'; import * as i18n from 'vscode-nls-i18n'; import config from './config'; import { WindhawkLogOutput } from './logOutputChannel'; import * as storagePaths from './storagePaths'; import { AppSettings, AppSettingsUtils, AppSettingsUtilsNonPortable, AppSettingsUtilsPortable } from './utils/appSettingsUtils'; import CompilerUtils, { CompilerError, CompilerKilled } from './utils/compilerUtils'; import EditorWorkspaceUtils from './utils/editorWorkspaceUtils'; import { ModConfigUtils, ModConfigUtilsNonPortable, ModConfigUtilsPortable } from './utils/modConfigUtils'; import ModFilesUtils from './utils/modFilesUtils'; import ModSourceUtils from './utils/modSourceUtils'; import TrayProgramUtils from './utils/trayProgramUtils'; import { UpdateUtils } from './utils/updateUtils'; import UserProfileUtils, { UserProfile } from './utils/userProfileUtils'; import * as webviewIPC from './webviewIPC'; import { AppUISettings, CompileEditedModData, CompileModData, CompileModReplyData, DeleteModData, EditModData, EnableEditedModData, EnableEditedModLoggingData, EnableModData, ExitEditorModeData, ForkModData, GetFeaturedModsReplyData, GetInstalledModsReplyData, GetModConfigData, GetModSettingsData, GetModSourceDataData, GetModVersionsData, GetModVersionsReplyData, GetRepositoryModSourceDataData, GetRepositoryModsReplyData, InitialSettings, InstallModData, InstallModReplyData, ModConfig, ModMetadata, SetModSettingsData, StartUpdateReplyData, UpdateAppSettingsData, UpdateInstalledModsDetailsData, UpdateModConfigData, UpdateModRatingData } from './webviewIPCMessages'; type AppUtils = { modSource: ModSourceUtils, modConfig: ModConfigUtils, modFiles: ModFilesUtils, compiler: CompilerUtils, editorWorkspace: EditorWorkspaceUtils, trayProgram: TrayProgramUtils, userProfile: UserProfileUtils, appSettings: AppSettingsUtils, update: UpdateUtils }; // Set to a local folder to use a dev environment. // Set to null to use the 'webview' folder. const baseDebugReactUiPath: string | null = config.debug.reactProjectBuildPath; const currentWindhawkVersion = semver.coerce( vscode.extensions.getExtension('m417z.windhawk')?.packageJSON.version ); let windhawkLogOutput: WindhawkLogOutput | null = null; let windhawkCompilerOutput: vscode.OutputChannel | null = null; export function activate(context: vscode.ExtensionContext) { if (!config.debug.disableEnvVarCheck && !process.env.WINDHAWK_UI_PATH) { vscode.window.showErrorMessage('Windhawk: Unsupported environment, perhaps VSCode was launched directly'); return; } try { i18n.init(context.extensionPath); windhawkLogOutput = new WindhawkLogOutput(path.join(context.extensionPath, 'files', 'DbgViewMini.exe')); windhawkCompilerOutput = vscode.window.createOutputChannel('Windhawk Compiler'); const arm64Enabled = process.env.WINDHAWK_ARM64_ENABLED === '1'; const paths = storagePaths.getStoragePaths(); const { appRootPath, appDataPath, enginePath, compilerPath } = paths.fsPaths; const utils: AppUtils = { modSource: new ModSourceUtils(appDataPath), modConfig: paths.portable ? new ModConfigUtilsPortable(appDataPath) : new ModConfigUtilsNonPortable(paths.regKey, paths.regSubKey, appDataPath), modFiles: new ModFilesUtils(appDataPath, arm64Enabled, currentWindhawkVersion), compiler: new CompilerUtils(compilerPath, enginePath, appDataPath, arm64Enabled), editorWorkspace: new EditorWorkspaceUtils(), trayProgram: new TrayProgramUtils(appRootPath), userProfile: new UserProfileUtils(appDataPath), appSettings: paths.portable ? new AppSettingsUtilsPortable(appDataPath) : new AppSettingsUtilsNonPortable(paths.regKey, paths.regSubKey), update: new UpdateUtils(paths.portable, appRootPath) }; const sidebarWebviewViewProvider = new WindhawkViewProvider(context.extensionUri, context.extensionPath, utils); context.subscriptions.push( vscode.window.registerWebviewViewProvider(WindhawkViewProvider.viewType, sidebarWebviewViewProvider) ); context.subscriptions.push( vscode.workspace.onDidChangeTextDocument(({ contentChanges, document }) => { if (contentChanges.length > 0) { sidebarWebviewViewProvider.fileWasModified(document); } }) ); const onEnterEditorMode = (modId: string, modWasModified = false) => { sidebarWebviewViewProvider.setEditedMod(modId, modWasModified); }; const onAppSettingsUpdated = () => { sidebarWebviewViewProvider.appSettingsUpdated(); }; context.subscriptions.push( vscode.commands.registerCommand('windhawk.start', (options?: WindhawkPanelOptions) => { WindhawkPanel.createOrShow(context.extensionUri, context.extensionPath, utils, { onEnterEditorMode, onAppSettingsUpdated }, paths.portable, { title: '', ...options }); }), vscode.commands.registerCommand('windhawk.compileMod', () => { sidebarWebviewViewProvider.compileMod(); }), ); utils.editorWorkspace.restoreEditorMode().then(({ modId, modWasModified }) => { if (modId) { sidebarWebviewViewProvider.setEditedMod(modId, !!modWasModified); } }).catch(e => reportException(e)); const onUserProfileModified = () => { const { mtimeMs } = fs.statSync(utils.userProfile.getFilePath()); if (mtimeMs !== utils.userProfile.getLastModifiedByUserMtimeMs()) { WindhawkPanel.userProfileChanged(); } }; const userProfileWatcher = vscode.workspace.createFileSystemWatcher( new vscode.RelativePattern(vscode.Uri.file(utils.userProfile.getFilePath()), '*')); userProfileWatcher.onDidCreate(onUserProfileModified); userProfileWatcher.onDidChange(onUserProfileModified); context.subscriptions.push(userProfileWatcher); } catch (e) { reportException(e); } } type RepositoryModsType = Record; type WindhawkPanelCallbacks = { onEnterEditorMode: (modId: string, modWasModified: boolean) => void, onAppSettingsUpdated: () => void }; type WindhawkPanelParams = { previewModId?: string }; type WindhawkPanelOptions = { title: string, createColumn?: vscode.ViewColumn, params?: WindhawkPanelParams }; /** * Manages Windhawk webview panels. */ class WindhawkPanel { /** * Track the currently panel. Only allow a single panel to exist at a time. */ public static currentPanel: WindhawkPanel | undefined; public static readonly viewType = 'windhawk'; private readonly _panel: vscode.WebviewPanel; private readonly _extensionUri: vscode.Uri; private readonly _extensionPath: string; private readonly _utils: AppUtils; private readonly _callbacks: WindhawkPanelCallbacks; private readonly _portable: boolean; private _disposables: vscode.Disposable[] = []; private _language = 'en'; private _checkForUpdates = true; private _alwaysCompileModsLocally = false; public static createOrShow( extensionUri: vscode.Uri, extensionPath: string, utils: AppUtils, callbacks: WindhawkPanelCallbacks, portable: boolean, options: WindhawkPanelOptions ) { const column = vscode.window.activeTextEditor ? vscode.window.activeTextEditor.viewColumn : undefined; // If we already have a panel, refresh and show it. if (WindhawkPanel.currentPanel) { WindhawkPanel.currentPanel.refresh(options.title, options.params); WindhawkPanel.currentPanel._panel.reveal(); return; } // Otherwise, create a new panel. const localResourceRoots = [vscode.Uri.joinPath(extensionUri, 'webview')]; if (baseDebugReactUiPath) { localResourceRoots.push(vscode.Uri.file(baseDebugReactUiPath)); } const panel = vscode.window.createWebviewPanel( WindhawkPanel.viewType, options.title, options.createColumn || column || vscode.ViewColumn.One, { // Enable javascript in the webview. enableScripts: true, // And restrict the webview to only loading content from our extension's `webview` directory. localResourceRoots } ); WindhawkPanel.currentPanel = new WindhawkPanel(panel, extensionUri, extensionPath, utils, callbacks, portable, options.params); } public static refreshIfExists(title: string, params?: WindhawkPanelParams) { WindhawkPanel.currentPanel?.refresh(title, params); } private constructor( panel: vscode.WebviewPanel, extensionUri: vscode.Uri, extensionPath: string, utils: AppUtils, callbacks: WindhawkPanelCallbacks, portable: boolean, params?: WindhawkPanelParams ) { this._panel = panel; this._extensionUri = extensionUri; this._extensionPath = extensionPath; this._utils = utils; this._callbacks = callbacks; this._portable = portable; // Set the webview initial html content and icon. this._panel.webview.html = this._getHtmlForWebview(this._panel.webview, params); this._panel.iconPath = { light: vscode.Uri.joinPath(extensionUri, 'assets', 'tab-icon-black.svg'), dark: vscode.Uri.joinPath(extensionUri, 'assets', 'tab-icon-white.svg') }; // Listen for when the panel is disposed. // This happens when the user closes the panel or when the panel is closed programmatically. this._panel.onDidDispose(() => this.dispose(), null, this._disposables); // Handle messages from the webview. this._panel.webview.onDidReceiveMessage( message => this._handleMessage(message), null, this._disposables ); } public refresh(title: string, params?: WindhawkPanelParams) { this._panel.title = title; // To refresh, first clear the html. this._panel.webview.html = ''; this._panel.webview.html = this._getHtmlForWebview(this._panel.webview, params); } public static userProfileChanged() { // If we don't already have a panel, there's nothing to update. if (!WindhawkPanel.currentPanel) { return; } WindhawkPanel.currentPanel._userProfileChanged(); } public dispose() { WindhawkPanel.currentPanel = undefined; // Clean up our resources. this._panel.dispose(); while (this._disposables.length) { const x = this._disposables.pop(); if (x) { x.dispose(); } } } private _getHtmlForWebview(webview: vscode.Webview, params?: WindhawkPanelParams) { const webviewPathOnDisk = baseDebugReactUiPath ? vscode.Uri.file(baseDebugReactUiPath) : vscode.Uri.joinPath(this._extensionUri, 'webview'); const baseWebviewUri = webview.asWebviewUri(webviewPathOnDisk); let html = fs.readFileSync(vscode.Uri.joinPath(webviewPathOnDisk, 'index.html').fsPath, 'utf8'); const csp = [ `default-src 'none'`, `style-src 'unsafe-inline' ${webview.cspSource}`, `img-src ${webview.cspSource} data: https://i.imgur.com https://raw.githubusercontent.com https://mods.windhawk.net`, `script-src ${webview.cspSource} blob:`, `connect-src ${webview.cspSource} https://mods.windhawk.net https://ramensoftware.com`, `font-src ${webview.cspSource}` ]; html = html.replace('', ` `); const dataParams = params ? ` data-params="${escapeHtml(JSON.stringify(params))}"` : ''; html = html.replace(/]*)>/, ``); return html; } private _getAppUISettings(appSettings: AppSettings, userProfile?: UserProfile): AppUISettings { let updateIsAvailable = false; if (!appSettings.disableUpdateCheck) { try { const currentVersion = currentWindhawkVersion; const latestVersion = semver.coerce((userProfile || this._utils.userProfile.read()).getAppLatestVersion()); updateIsAvailable = !!(currentVersion && latestVersion && semver.lt(currentVersion, latestVersion)); } catch (e) { reportException(e); } } return { language: appSettings.language, devModeOptOut: appSettings.devModeOptOut, devModeUsedAtLeastOnce: appSettings.devModeUsedAtLeastOnce, loggingEnabled: ( appSettings.loggingVerbosity > 0 || appSettings.engine.loggingVerbosity > 0 ), updateIsAvailable, safeMode: appSettings.safeMode }; } private _userProfileChanged() { try { const userProfile = this._utils.userProfile.read(); // First, recalculate UI settings, since the update availability value // depends on the user profile. const appSettings = this._utils.appSettings.getAppSettings(); this._language = appSettings.language; this._checkForUpdates = !appSettings.disableUpdateCheck; this._alwaysCompileModsLocally = appSettings.alwaysCompileModsLocally; webviewIPC.setNewAppSettings(this._panel.webview, { appUISettings: this._getAppUISettings(appSettings, userProfile) }); // Next, recalculate mod values which depend on the user profile. const details: UpdateInstalledModsDetailsData['details'] = {}; const modsMetadata = this._utils.modSource.getMetadataOfInstalled(this._language, (modId, error) => { vscode.window.showErrorMessage(`Failed to load mod ${modId}: ${error}`); }); const modsConfig = this._utils.modConfig.getConfigOfInstalled(); for (const modId of new Set([...Object.keys(modsMetadata), ...Object.keys(modsConfig)])) { const modLatestVersion = this._checkForUpdates && userProfile.getModLatestVersion(modId); const updateAvailable = !!(modLatestVersion && modLatestVersion !== (modsMetadata[modId]?.version || '')); const userRating = userProfile.getModRating(modId) || 0; details[modId] = { updateAvailable, userRating: userRating }; } webviewIPC.updateInstalledModsDetails(this._panel.webview, { details }); } catch (e) { reportException(e); } } private readonly _handleMessageMap: Record void> = { getInitialAppSettings: message => { let appUISettings: Partial = {}; try { const appSettings = this._utils.appSettings.getAppSettings(); this._language = appSettings.language; this._checkForUpdates = !appSettings.disableUpdateCheck; this._alwaysCompileModsLocally = appSettings.alwaysCompileModsLocally; appUISettings = this._getAppUISettings(appSettings); } catch (e) { reportException(e); } webviewIPC.getInitialAppSettingsReply(this._panel.webview, message.messageId, { appUISettings }); }, getInstalledMods: message => { const installedMods: GetInstalledModsReplyData['installedMods'] = {}; try { const userProfile = this._utils.userProfile.read(); const modsMetadata = this._utils.modSource.getMetadataOfInstalled(this._language, (modId, error) => { vscode.window.showErrorMessage(`Failed to load mod ${modId}: ${error}`); }); const modsConfig = this._utils.modConfig.getConfigOfInstalled(); let userProfileUpdated = false; for (const modId of new Set([...Object.keys(modsMetadata), ...Object.keys(modsConfig)])) { const version = modsMetadata[modId]?.version || ''; const disabled = modsConfig[modId]?.disabled || false; if (!modId.startsWith('local@') && userProfile.updateModDetails(modId, version, disabled)) { userProfileUpdated = true; } const latestVersion = this._checkForUpdates && userProfile.getModLatestVersion(modId); const updateAvailable = !!(latestVersion && latestVersion !== version); const userRating = userProfile.getModRating(modId) || 0; installedMods[modId] = { metadata: modsMetadata[modId] || null, config: modsConfig[modId] || null, updateAvailable, userRating: userRating }; } const nonLocalModIds = Object.keys(installedMods).filter(modId => !modId.startsWith('local@')); if (userProfile.cleanupRemovedMods(new Set(nonLocalModIds))) { userProfileUpdated = true; } if (userProfileUpdated) { // Set asExternalUpdate so that the file watcher will send // the updated data to the UI. const asExternalUpdate = true; userProfile.write(asExternalUpdate); } } catch (e) { reportException(e); } webviewIPC.getInstalledModsReply(this._panel.webview, message.messageId, { installedMods }); }, getFeaturedMods: async message => { let featuredMods: GetFeaturedModsReplyData['featuredMods'] = null; try { const repositoryMods = await this._fetchRepositoryMods(this._language); featuredMods = Object.fromEntries( Object.entries(repositoryMods).filter(([k, v]) => v.featured)); } catch (e) { reportException(e); } webviewIPC.getFeaturedModsReply(this._panel.webview, message.messageId, { featuredMods }); }, getRepositoryMods: async message => { let mods: GetRepositoryModsReplyData['mods'] = null; try { const repositoryMods = await this._fetchRepositoryMods(this._language); mods = {}; for (const [modId, value] of Object.entries(repositoryMods)) { mods[modId] = { repository: value }; } const userProfile = this._utils.userProfile.read(); const modsMetadata = this._utils.modSource.getMetadataOfInstalled(this._language, (modId, error) => { vscode.window.showErrorMessage(`Failed to load mod ${modId}: ${error}`); }); const modsConfig = this._utils.modConfig.getConfigOfInstalled(); for (const modId of new Set([...Object.keys(modsMetadata), ...Object.keys(modsConfig)])) { if (mods[modId]) { const userRating = userProfile.getModRating(modId) || 0; mods[modId].installed = { metadata: modsMetadata[modId] || null, config: modsConfig[modId] || null, userRating }; } } } catch (e) { reportException(e); } webviewIPC.getRepositoryModsReply(this._panel.webview, message.messageId, { mods }); }, getModSourceData: message => { const data: GetModSourceDataData = message.data; let source: string | null = null; try { source = this._utils.modSource.getSource(data.modId); } catch (e) { reportException(e); } let metadata: ModMetadata | null = null; let readme: string | null = null; let initialSettings: InitialSettings | null = null; if (source) { try { metadata = this._utils.modSource.extractMetadata(source, this._language); } catch (e) { reportException(e); } try { readme = this._utils.modSource.extractReadme(source); } catch (e) { reportException(e); } try { initialSettings = this._utils.modSource.extractInitialSettings(source, this._language); } catch (e) { reportException(e); } } webviewIPC.getModSourceDataReply(this._panel.webview, message.messageId, { modId: data.modId, data: { source, metadata, readme, initialSettings } }); }, getRepositoryModSourceData: async message => { const data: GetRepositoryModSourceDataData = message.data; // Construct URL: if version is provided, use versioned path, // otherwise use latest. const url = data.version ? `${config.urls.modsFolder}${data.modId}/${data.version}.wh.cpp` : `${config.urls.modsFolder}${data.modId}.wh.cpp`; let source: string | null = null; try { const response = await fetch(url); if (!response.ok) { throw Error('Server error: ' + (response.statusText || response.status)); } source = await response.text(); // Make sure the source code has CRLF newlines. source = source.replace(/\r\n|\r|\n/g, '\r\n'); } catch (e) { reportException(e); } let metadata: ModMetadata | null = null; let readme: string | null = null; let initialSettings: InitialSettings | null = null; if (source) { try { metadata = this._utils.modSource.extractMetadata(source, this._language); } catch (e) { reportException(e); } try { readme = this._utils.modSource.extractReadme(source); } catch (e) { reportException(e); } try { initialSettings = this._utils.modSource.extractInitialSettings(source, this._language); } catch (e) { reportException(e); } } webviewIPC.getRepositoryModSourceDataReply(this._panel.webview, message.messageId, { modId: data.modId, version: data.version, data: { source, metadata, readme, initialSettings } }); }, getModVersions: async message => { const data: GetModVersionsData = message.data; const { modId } = data; const url = `${config.urls.modsFolder}${modId}/versions.json`; let versions: GetModVersionsReplyData['versions'] = []; try { const response = await fetch(url); if (!response.ok) { throw Error('Server error: ' + (response.statusText || response.status)); } const jsonData = await response.json(); versions = jsonData.map((v: any) => ({ version: v.version, timestamp: v.timestamp, isPreRelease: v.version.includes('-') })); } catch (e) { reportException(e); } webviewIPC.getModVersionsReply(this._panel.webview, message.messageId, { modId, versions }); }, getModSettings: message => { const data: GetModSettingsData = message.data; let settings: Record = {}; try { settings = this._utils.modConfig.getModSettings(data.modId); } catch (e) { reportException(e); } webviewIPC.getModSettingsReply(this._panel.webview, message.messageId, { modId: data.modId, settings }); }, setModSettings: message => { const data: SetModSettingsData = message.data; let succeeded = false; try { this._utils.modConfig.setModSettings(data.modId, data.settings); succeeded = true; } catch (e) { reportException(e); } webviewIPC.setModSettingsReply(this._panel.webview, message.messageId, { modId: data.modId, succeeded }); }, getModConfig: message => { const data: GetModConfigData = message.data; let config: ModConfig | null = null; try { config = this._utils.modConfig.getModConfig(data.modId); } catch (e) { reportException(e); } webviewIPC.getModConfigReply(this._panel.webview, message.messageId, { modId: data.modId, config }); }, updateModConfig: message => { const data: UpdateModConfigData = message.data; let succeeded = false; try { this._utils.modConfig.setModConfig(data.modId, data.config); webviewIPC.setNewModConfig(this._panel.webview, { modId: data.modId, config: data.config }); succeeded = true; } catch (e) { reportException(e); } webviewIPC.updateModConfigReply(this._panel.webview, message.messageId, { modId: data.modId, succeeded }); }, installMod: async message => { const data: InstallModData = message.data; let installedModDetails: InstallModReplyData['installedModDetails'] = null; try { windhawkCompilerOutput?.clear(); windhawkCompilerOutput?.hide(); const modId = data.modId; const modSource = data.modSource; const disabled = !!data.disabled; const metadata = this._utils.modSource.extractMetadata(modSource, this._language); if (!metadata.id) { throw new Error('Mod id must be specified in the source code'); } else if (metadata.id !== modId) { throw new Error('Mod id specified in the source code doesn\'t match'); } const initialSettings = this._utils.modSource.extractInitialSettingsForEngine(modSource); let previousInitialSettings: Record | undefined; try { const prev = this._utils.modSource.extractInitialSettingsForEngine( this._utils.modSource.getSource(modId) ); if (prev) { previousInitialSettings = prev; } } catch (e) { if (e.code !== 'ENOENT') { console.error('Failed to extract previous initial settings for engine:', e); } } let targetDllName: string; if (this._alwaysCompileModsLocally) { const result = await this._utils.compiler.compileMod( modId, metadata.version || '', metadata.include || [], modSource, metadata.architecture || [], metadata.compilerOptions ); targetDllName = result.targetDllName; } else { const result = await this._utils.modFiles.downloadPrecompiledMod( modId, metadata.version || '', metadata.architecture || [], config.urls.modsFolder ); targetDllName = result.targetDllName; } this._utils.modConfig.setModConfig(modId, { libraryFileName: targetDllName, disabled, // loggingEnabled: false, // debugLoggingEnabled: false, include: metadata.include || [], exclude: metadata.exclude || [], // includeCustom: [], // excludeCustom: [], // includeExcludeCustomOnly: false, // patternsMatchCriticalSystemProcesses: false, architecture: metadata.architecture || [], version: metadata.version || '' }, { initialSettings: initialSettings || {}, previousInitialSettings }); this._utils.modSource.setSource(modId, modSource); this._utils.modFiles.deleteOldModFiles(modId, metadata.architecture || [], targetDllName); const userProfile = this._utils.userProfile.read(); userProfile.setModVersion(modId, metadata.version || ''); userProfile.write(); const modConfig = this._utils.modConfig.getModConfig(modId); if (!modConfig) { throw new Error('Failed to query installed mod details'); } installedModDetails = { metadata, config: modConfig }; } catch (e) { reportCompilerException(e, true); } webviewIPC.installModReply(this._panel.webview, message.messageId, { modId: data.modId, installedModDetails }); }, compileMod: async message => { const data: CompileModData = message.data; let compiledModDetails: CompileModReplyData['compiledModDetails'] = null; try { windhawkCompilerOutput?.clear(); windhawkCompilerOutput?.hide(); const modId = data.modId; const modSource = this._utils.modSource.getSource(modId); const disabled = !!data.disabled; const metadata = this._utils.modSource.extractMetadata(modSource, this._language); if (!metadata.id) { throw new Error('Mod id must be specified in the source code'); } else if (metadata.id !== modId.replace(/^local@/, '')) { throw new Error('Mod id specified in the source code doesn\'t match'); } const { targetDllName } = await this._utils.compiler.compileMod( modId, metadata.version || '', metadata.include || [], modSource, metadata.architecture || [], metadata.compilerOptions ); this._utils.modConfig.setModConfig(modId, { libraryFileName: targetDllName, disabled, // loggingEnabled: false, // debugLoggingEnabled: false, include: metadata.include || [], exclude: metadata.exclude || [], // includeCustom: [], // excludeCustom: [], // includeExcludeCustomOnly: false, // patternsMatchCriticalSystemProcesses: false, architecture: metadata.architecture || [], version: metadata.version || '' }); this._utils.modFiles.deleteOldModFiles(modId, metadata.architecture || [], targetDllName); const config = this._utils.modConfig.getModConfig(modId); if (!config) { throw new Error('Failed to query compiled mod details'); } compiledModDetails = { metadata, config }; } catch (e) { reportCompilerException(e, true); } webviewIPC.compileModReply(this._panel.webview, message.messageId, { modId: data.modId, compiledModDetails }); }, enableMod: message => { const data: EnableModData = message.data; let succeeded = false; try { const modId: string = data.modId; const enable: boolean = data.enable; this._utils.modConfig.enableMod(modId, enable); if (!modId.startsWith('local@')) { const userProfile = this._utils.userProfile.read(); userProfile.setModDisabled(modId, !enable); userProfile.write(); } succeeded = true; } catch (e) { reportException(e); } webviewIPC.enableModReply(this._panel.webview, message.messageId, { modId: data.modId, enabled: data.enable, succeeded }); }, createNewMod: async message => { try { const modSourcePath = path.join(this._extensionPath, 'files', 'mod_template.wh.cpp'); let modSource = fs.readFileSync(modSourcePath, 'utf8'); const metadata = this._utils.modSource.extractMetadata(modSource, this._language); if (!metadata.id) { throw new Error('Mod id must be specified in the source code'); } let newModId = metadata.id; let localModId = 'local@' + newModId; if (this._utils.modSource.doesSourceExist(localModId) || this._utils.modConfig.doesConfigExist(localModId)) { let counter = 2; let modIdSuffix; for (; ;) { modIdSuffix = '-' + counter; newModId = metadata.id + modIdSuffix; localModId = 'local@' + newModId; const exists = this._utils.modSource.doesSourceExist(localModId) || this._utils.modConfig.doesConfigExist(localModId); if (!exists) { break; } counter++; } const modNameSuffix = ` (${counter})`; modSource = this._utils.modSource.appendToIdAndName(modSource, modIdSuffix, modNameSuffix); } this._utils.editorWorkspace.initializeFromModSource(modSource); this._callbacks.onEnterEditorMode(newModId, false); await this._utils.editorWorkspace.enterEditorMode(newModId); } catch (e) { reportException(e); } }, editMod: async message => { const data: EditModData = message.data; try { const modSource = this._utils.modSource.getSource(data.modId); const metadata = this._utils.modSource.extractMetadata(modSource, this._language); if (!metadata.id) { throw new Error('Mod id must be specified in the source code'); } const modSourceFromDrafts = this._utils.editorWorkspace.loadModFromDrafts(metadata.id); if (modSourceFromDrafts) { this._utils.editorWorkspace.deleteModFromDrafts(metadata.id); } this._utils.editorWorkspace.initializeFromModSource(modSource, modSourceFromDrafts); this._callbacks.onEnterEditorMode(metadata.id, !!modSourceFromDrafts); await this._utils.editorWorkspace.enterEditorMode(metadata.id, !!modSourceFromDrafts); } catch (e) { reportException(e); } }, forkMod: async message => { const data: ForkModData = message.data; try { let modSource = data.modSource || this._utils.modSource.getSource(data.modId); const metadata = this._utils.modSource.extractMetadata(modSource, this._language); if (!metadata.id) { throw new Error('Mod id must be specified in the source code'); } else if (metadata.id !== data.modId.replace(/^local@/, '')) { throw new Error('Mod id specified in the source code doesn\'t match'); } let modIdSuffix = '-fork'; let forkModId = metadata.id + modIdSuffix; let localModId = 'local@' + forkModId; let modNameSuffix = ' - Fork'; if (this._utils.modSource.doesSourceExist(localModId) || this._utils.modConfig.doesConfigExist(localModId)) { let counter = 2; for (; ;) { modIdSuffix = '-fork' + counter; forkModId = metadata.id + modIdSuffix; localModId = 'local@' + forkModId; const exists = this._utils.modSource.doesSourceExist(localModId) || this._utils.modConfig.doesConfigExist(localModId); if (!exists) { break; } counter++; } modNameSuffix = ` - Fork (${counter})`; } modSource = this._utils.modSource.appendToIdAndName(modSource, modIdSuffix, modNameSuffix); this._utils.editorWorkspace.initializeFromModSource(modSource); this._callbacks.onEnterEditorMode(forkModId, false); await this._utils.editorWorkspace.enterEditorMode(forkModId); } catch (e) { reportException(e); } }, deleteMod: message => { const data: DeleteModData = message.data; let succeeded = false; try { const modId: string = data.modId; this._utils.modConfig.deleteMod(modId); this._utils.modSource.deleteSource(modId); this._utils.modFiles.deleteModFiles(modId); if (modId.startsWith('local@')) { this._utils.editorWorkspace.deleteModFromDrafts(modId.replace(/^local@/, '')); } else { const userProfile = this._utils.userProfile.read(); userProfile.deleteMod(modId); userProfile.write(); } succeeded = true; } catch (e) { reportException(e); } webviewIPC.deleteModReply(this._panel.webview, message.messageId, { modId: data.modId, succeeded }); }, updateModRating: message => { const data: UpdateModRatingData = message.data; let succeeded = false; try { const userProfile = this._utils.userProfile.read(); userProfile.setModRating(data.modId, data.rating); userProfile.write(); succeeded = true; } catch (e) { reportException(e); } webviewIPC.updateModRatingReply(this._panel.webview, message.messageId, { modId: data.modId, rating: data.rating, succeeded }); }, getAppSettings: message => { let appSettings: Partial = {}; try { appSettings = this._utils.appSettings.getAppSettings(); } catch (e) { reportException(e); } webviewIPC.getAppSettingsReply(this._panel.webview, message.messageId, { appSettings }); }, updateAppSettings: message => { const data: UpdateAppSettingsData = message.data; let succeeded = false; try { const appSettings: Partial = data.appSettings; this._utils.appSettings.updateAppSettings(appSettings); const newAppSettings = this._utils.appSettings.getAppSettings(); this._language = newAppSettings.language; this._checkForUpdates = !newAppSettings.disableUpdateCheck; this._alwaysCompileModsLocally = newAppSettings.alwaysCompileModsLocally; webviewIPC.setNewAppSettings(this._panel.webview, { appUISettings: this._getAppUISettings(newAppSettings) }); this._callbacks.onAppSettingsUpdated(); if (this._utils.appSettings.shouldRestartApp(appSettings)) { this._utils.trayProgram.postAppRestartBg(); vscode.window.showInformationMessage('Windhawk was restarted'); } else if (this._utils.appSettings.shouldNotifyTrayProgram(appSettings)) { this._utils.trayProgram.postAppSettingsChanged(); } succeeded = true; } catch (e) { reportException(e); } webviewIPC.updateAppSettingsReply(this._panel.webview, message.messageId, { appSettings: data.appSettings, succeeded }); }, showAdvancedDebugLogOutput: message => { try { windhawkLogOutput?.createOrShow(); } catch (e) { reportException(e); } }, startUpdate: async message => { let result: StartUpdateReplyData = { succeeded: false, error: 'Update failed' }; try { result = await this._utils.update.startUpdate({ onProgress: (data) => { webviewIPC.updateDownloadProgress(this._panel.webview, data); }, onInstalling: () => { webviewIPC.updateInstalling(this._panel.webview, {}); } }); } catch (e) { reportException(e); } webviewIPC.startUpdateReply(this._panel.webview, message.messageId, result); }, cancelUpdate: message => { let succeeded = false; try { if (this._utils.update.cancelUpdate()) { succeeded = true; } } catch (e) { reportException(e); } webviewIPC.cancelUpdateReply(this._panel.webview, message.messageId, { succeeded }); } }; private _handleMessage(message: any) { const { command, ...rest } = message; this._handleMessageMap[command](rest); } private async _fetchRepositoryMods(language: string) { const version = currentWindhawkVersion?.version || 'unknown'; const userAgent = `Windhawk/${version}${this._portable ? ' (portable)' : ''}`; const headers = { 'User-Agent': userAgent }; const languageCatalogUrl = config.urls.modsUrlRoot + 'catalogs/' + language + '.json'; let response = await fetch(languageCatalogUrl, { headers }); if (response.status === 404) { // Fallback to the default catalog if the language one is not available. const defaultCatalogUrl = config.urls.modsUrlRoot + 'catalog.json'; response = await fetch(defaultCatalogUrl, { headers }); } if (!response.ok) { throw Error('Server error: ' + (response.statusText || response.status)); } const data = await response.json(); this._updateUserProfileJson(data); return data.mods as RepositoryModsType; } private _updateUserProfileJson(data: any) { const userProfile = this._utils.userProfile.read(); const appLatestVersion = data.app.version; const repositoryMods: RepositoryModsType = data.mods; const modLatestVersion: Record = {}; for (const [modId, value] of Object.entries(repositoryMods)) { const { version } = value.metadata; if (version) { modLatestVersion[modId] = version; } } if (userProfile.updateLatestVersions(appLatestVersion, modLatestVersion)) { // Set asExternalUpdate so that the file watcher will send the // updated data to the UI. const asExternalUpdate = true; userProfile.write(asExternalUpdate); if (this._checkForUpdates) { this._utils.trayProgram.postNewUpdatesFound(); } } } } class WindhawkViewProvider implements vscode.WebviewViewProvider { public static readonly viewType = 'windhawk.sidebar'; private _view?: vscode.WebviewView; private readonly _extensionUri: vscode.Uri; private readonly _extensionPath: string; private readonly _utils: AppUtils; private _language = 'en'; private _editedModId?: string; private _editedModWasModified = false; private _editedModModifiedCounter = 0; private _editedModBeingCompiled = false; private _editedModCompilationFailed = false; constructor( extensionUri: vscode.Uri, extensionPath: string, utils: AppUtils ) { this._extensionUri = extensionUri; this._extensionPath = extensionPath; this._utils = utils; } public resolveWebviewView( webviewView: vscode.WebviewView, context: vscode.WebviewViewResolveContext, _token: vscode.CancellationToken, ) { this._view = webviewView; const localResourceRoots = [vscode.Uri.joinPath(this._extensionUri, 'webview')]; if (baseDebugReactUiPath) { localResourceRoots.push(vscode.Uri.file(baseDebugReactUiPath)); } webviewView.webview.options = { // Allow scripts in the webview. enableScripts: true, // And restrict the webview to only loading content from our extension's `webview` directory. localResourceRoots }; webviewView.webview.html = this._getHtmlForWebview(webviewView.webview); webviewView.webview.onDidReceiveMessage( message => this._handleMessage(message) ); webviewView.onDidChangeVisibility(() => { if (!webviewView.visible && this._editedModId) { vscode.window.showInformationMessage( 'The Windhawk sidebar was closed, perhaps accidentally. ' + 'Restore sidebar? You can also restore it with Ctrl+B.', 'Restore sidebar' ).then(value => { if (value === 'Restore sidebar') { webviewView.show(); } }); } }); } private _getHtmlForWebview(webview: vscode.Webview) { const webviewPathOnDisk = baseDebugReactUiPath ? vscode.Uri.file(baseDebugReactUiPath) : vscode.Uri.joinPath(this._extensionUri, 'webview'); const baseWebviewUri = webview.asWebviewUri(webviewPathOnDisk); let html = fs.readFileSync(vscode.Uri.joinPath(webviewPathOnDisk, 'index.html').fsPath, 'utf8'); const csp = [ `default-src 'none'`, `style-src 'unsafe-inline' ${webview.cspSource}`, `img-src ${webview.cspSource} data:`, `script-src ${webview.cspSource} blob:`, `connect-src ${webview.cspSource}`, `font-src ${webview.cspSource}` ]; html = html.replace('', ` `); html = html.replace(/]*)>/, ''); return html; } public fileWasModified(doc: vscode.TextDocument) { const modSourcePath = this._utils.editorWorkspace.getModSourcePath(); if (doc.uri.toString(true) !== vscode.Uri.file(modSourcePath).toString(true)) { return; } this._editedModModifiedCounter++; if (!this._editedModWasModified || this._editedModCompilationFailed) { this._editedModWasModified = true; this._editedModCompilationFailed = false; this._utils.editorWorkspace.markEditorModeModAsModified(true); webviewIPC.editedModWasModified(this._view?.webview); } } public compileMod() { this._view?.show(true); webviewIPC.compileEditedModStart(this._view?.webview); } private _postEditedModDetails() { if (this._editedModId) { const localModId = 'local@' + this._editedModId; const modConfig = this._utils.modConfig.getModConfig(localModId); webviewIPC.setEditedModDetails(this._view?.webview, { modId: this._editedModId, modDetails: modConfig, modWasModified: this._editedModWasModified }); } } public setEditedMod(modId: string, modWasModified: boolean) { this._editedModId = modId; this._editedModWasModified = modWasModified; this._editedModCompilationFailed = false; this._postEditedModDetails(); } public appSettingsUpdated() { const newAppSettings = this._utils.appSettings.getAppSettings(); this._language = newAppSettings.language; webviewIPC.setNewAppSettings(this._view?.webview, { appUISettings: { language: this._language } }); } private readonly _handleMessageMap: Record void> = { getInitialAppSettings: message => { try { const appSettings = this._utils.appSettings.getAppSettings(); this._language = appSettings.language; } catch (e) { reportException(e); } webviewIPC.getInitialAppSettingsReply(this._view?.webview, message.messageId, { appUISettings: { language: this._language } }); }, getInitialSidebarParams: message => { this._postEditedModDetails(); }, enableEditedMod: message => { const data: EnableEditedModData = message.data; let succeeded = false; try { if (!this._editedModId) { throw new Error('No mod is being edited'); } const localModId = 'local@' + this._editedModId; this._utils.modConfig.enableMod(localModId, data.enable); succeeded = true; } catch (e) { reportException(e); } webviewIPC.enableEditedModReply(this._view?.webview, message.messageId, { enabled: data.enable, succeeded }); }, enableEditedModLogging: message => { const data: EnableEditedModLoggingData = message.data; let succeeded = false; try { if (!this._editedModId) { throw new Error('No mod is being edited'); } const localModId = 'local@' + this._editedModId; this._utils.modConfig.enableLogging(localModId, data.enable); succeeded = true; } catch (e) { reportException(e); } webviewIPC.enableEditedModLoggingReply(this._view?.webview, message.messageId, { enabled: data.enable, succeeded }); }, compileEditedMod: async message => { const data: CompileEditedModData = message.data; if (this._editedModBeingCompiled) { return; } this._editedModBeingCompiled = true; const modifiedCounterStart = this._editedModModifiedCounter; let succeeded = false; let clearModified = false; try { windhawkCompilerOutput?.clear(); if (!this._editedModId) { throw new Error('No mod is being edited'); } const oldModId = this._editedModId; const localOldModId = 'local@' + this._editedModId; const modSourcePath = this._utils.editorWorkspace.getModSourcePath(); const modSourceUri = vscode.Uri.file(modSourcePath); // Get text from open editor if available, otherwise read from disk. const openEditor = vscode.window.visibleTextEditors.find( editor => editor.document.uri.toString(true) === modSourceUri.toString(true) ); let modSource: string; if (openEditor) { modSource = openEditor.document.getText(); } else { modSource = fs.readFileSync(modSourcePath, 'utf8'); } const metadata = this._utils.modSource.extractMetadata(modSource, this._language); if (!metadata.id) { throw new Error('Mod id must be specified in the source code'); } const modId = metadata.id; const localModId = 'local@' + modId; if (modId !== oldModId) { if (this._utils.modSource.doesSourceExist(localModId) || this._utils.modConfig.doesConfigExist(localModId)) { throw new Error('Mod id specified in the source code already exists'); } } const initialSettings = this._utils.modSource.extractInitialSettingsForEngine(modSource); let previousInitialSettings: Record | undefined; try { const prev = this._utils.modSource.extractInitialSettingsForEngine( this._utils.modSource.getSource(localModId) ); if (prev) { previousInitialSettings = prev; } } catch (e) { if (e.code !== 'ENOENT') { console.error('Failed to extract previous initial settings for engine:', e); } } const { targetDllName } = await this._utils.compiler.compileMod( localModId, metadata.version || '', metadata.include || [], modSource, metadata.architecture || [], metadata.compilerOptions, this._utils.editorWorkspace.getWorkspaceFolder() ); if (modId !== oldModId) { this._utils.modConfig.changeModId(localOldModId, localModId); } this._utils.modConfig.setModConfig(localModId, { libraryFileName: targetDllName, disabled: data.disabled, loggingEnabled: data.loggingEnabled, // debugLoggingEnabled: false, include: metadata.include || [], exclude: metadata.exclude || [], // includeCustom: [], // excludeCustom: [], // includeExcludeCustomOnly: false, // patternsMatchCriticalSystemProcesses: false, architecture: metadata.architecture || [], version: metadata.version || '' }, { initialSettings: initialSettings || {}, previousInitialSettings }); this._utils.modSource.setSource(localModId, modSource); if (modId !== oldModId) { this._utils.modSource.deleteSource(localOldModId); this._utils.editorWorkspace.setEditorModeModId(modId); this._editedModId = modId; webviewIPC.setEditedModId(this._view?.webview, { modId }); } this._utils.modFiles.deleteOldModFiles(localModId, metadata.architecture || [], targetDllName); if (data.loggingEnabled) { windhawkLogOutput?.createOrShow(true); } else { windhawkCompilerOutput?.hide(); } WindhawkPanel.refreshIfExists('Preview', { previewModId: localModId }); this._editedModCompilationFailed = false; clearModified = (modifiedCounterStart === this._editedModModifiedCounter); if (clearModified) { this._editedModWasModified = false; this._utils.editorWorkspace.markEditorModeModAsModified(false); } succeeded = true; } catch (e) { reportCompilerException(e); this._editedModCompilationFailed = true; } webviewIPC.compileEditedModReply(this._view?.webview, message.messageId, { succeeded, clearModified }); this._editedModBeingCompiled = false; }, stopCompileEditedMod: async message => { try { if (this._editedModBeingCompiled) { this._utils.compiler.cancelCompilation(); } } catch (e) { reportException(e); } }, previewEditedMod: async message => { try { if (!this._editedModId) { throw new Error('No mod is being edited'); } const localModId = 'local@' + this._editedModId; await vscode.commands.executeCommand('windhawk.start', { title: 'Preview', createColumn: vscode.ViewColumn.Beside, params: { previewModId: localModId } }); } catch (e) { reportException(e); } }, showLogOutput: message => { try { windhawkLogOutput?.createOrShow(); } catch (e) { reportException(e); } }, exitEditorMode: async message => { const data: ExitEditorModeData = message.data; let succeeded = false; try { if (!await vscode.workspace.saveAll(true)) { throw new Error('Failed to save all files'); } windhawkLogOutput?.dispose(); if (this._editedModId) { if (this._editedModWasModified && data.saveToDrafts) { this._utils.editorWorkspace.saveModToDrafts(this._editedModId); } else { this._utils.editorWorkspace.deleteModFromDrafts(this._editedModId); } } this._editedModId = undefined; this._editedModWasModified = false; this._editedModCompilationFailed = false; await this._utils.editorWorkspace.exitEditorMode(); succeeded = true; } catch (e) { reportException(e); } webviewIPC.exitEditorModeReply(this._view?.webview, message.messageId, { succeeded }); } }; private _handleMessage(message: any) { const { command, ...rest } = message; this._handleMessageMap[command](rest); } } function reportException(e: any) { console.error(e); vscode.window.showErrorMessage(e.message); } function reportCompilerException(e: any, treatCompilationErrorAsException = false) { if (e instanceof CompilerKilled) { windhawkCompilerOutput?.append(e.message + '\n'); windhawkCompilerOutput?.show(); return; } if (!(e instanceof CompilerError)) { reportException(e); return; } try { let log = ''; const stdout = e.stdout.trim(); const stderr = e.stderr.trim(); if ((stdout === '' && stderr === '') || e.exitCode !== 1) { const exitCodeStr = e.exitCode !== null ? `0x${e.exitCode.toString(16)}` : 'unknown'; log = `Exit code: ${exitCodeStr}\n`; } if (stdout !== '') { if (log !== '') { log += '\n'; } log += stdout + '\n'; } if (stderr !== '') { if (log !== '') { log += '\n'; } log += stderr + '\n'; } windhawkCompilerOutput?.append(log); windhawkCompilerOutput?.show(); if (treatCompilationErrorAsException) { reportException(e); } } catch (e) { reportException(e); } } // https://stackoverflow.com/a/6234804 function escapeHtml(unsafe: string) { return unsafe .replace(/&/g, "&") .replace(//g, ">") .replace(/"/g, """) .replace(/'/g, "'"); } ================================================ FILE: src/vscode-windhawk/src/ini.ts ================================================ import * as fs from 'fs'; import * as fsExt from 'fs-ext'; import * as ini from 'ini-win'; export type iniValue = { [key: string]: { [key: string]: string } }; export function fromFile(filePath: string) { const fd = fs.openSync(filePath, 'r'); fsExt.flockSync(fd, 'sh'); const buffer = fs.readFileSync(fd); fsExt.flockSync(fd, 'un'); fs.closeSync(fd); let contents: string; if (buffer[0] === 0xFF && buffer[1] === 0xFE) { contents = buffer.slice(2).toString('utf16le'); } else { contents = buffer.toString('utf8'); } const parsed = ini.parse(contents); const result: iniValue = {}; for (const [sectionName, section] of Object.entries(parsed)) { for (const [key, value] of Object.entries(section)) { if (typeof value === 'string') { result[sectionName] = result[sectionName] || {}; result[sectionName][key] = value; } } } return result; } export function fromFileOrDefault(filePath: string, defaultValue: iniValue = {}) { try { return fromFile(filePath); } catch (e) { // Ignore if file doesn't exist. if (e.code !== 'ENOENT') { throw e; } return defaultValue; } } export function toFile(filePath: string, value: iniValue) { const fd = fs.openSync(filePath, 'w'); fsExt.flockSync(fd, 'ex'); fs.writeFileSync(fd, '\uFEFF' + ini.stringify(value), 'utf16le'); fsExt.flockSync(fd, 'un'); fs.closeSync(fd); } ================================================ FILE: src/vscode-windhawk/src/logOutputChannel.ts ================================================ import * as child_process from 'child_process'; import * as vscode from 'vscode'; export class WindhawkLogOutput { private _logOutputProcessPath: string; private _logOutputChannel?: vscode.OutputChannel; private _logOutputProcess?: child_process.ChildProcessWithoutNullStreams; private _incompleteStdoutBuffer: Buffer = Buffer.alloc(0); private _incompleteStderrBuffer: Buffer = Buffer.alloc(0); constructor(logOutputProcessPath: string) { this._logOutputProcessPath = logOutputProcessPath; } public createOrShow(preserveFocus?: boolean) { if (!this._logOutputChannel) { this._logOutputChannel = vscode.window.createOutputChannel('Windhawk Log'); } this._logOutputChannel.show(preserveFocus); if (!this._logOutputProcess) { const args = [ '--pattern', '[WH] *', '--no-buffering', ]; const ps = child_process.spawn(this._logOutputProcessPath, args); this._logOutputProcess = ps; ps.stdout.on('data', data => { const dataWithIncompleteBuffer = Buffer.concat([this._incompleteStdoutBuffer, data]); const index = incompleteUTF8Index(dataWithIncompleteBuffer); const dataToOutput = dataWithIncompleteBuffer.subarray(0, index); this._incompleteStdoutBuffer = dataWithIncompleteBuffer.subarray(index); this._logOutputChannel?.append(dataToOutput.toString()); }); ps.stderr.on('data', data => { const dataWithIncompleteBuffer = Buffer.concat([this._incompleteStderrBuffer, data]); const index = incompleteUTF8Index(dataWithIncompleteBuffer); const dataToOutput = dataWithIncompleteBuffer.subarray(0, index); this._incompleteStderrBuffer = dataWithIncompleteBuffer.subarray(index); this._logOutputChannel?.append(dataToOutput.toString()); }); let gotError = false; ps.on('error', err => { //console.log('Oh no, the error: ' + err); this._logOutputProcess = undefined; gotError = true; vscode.window.showErrorMessage(err.message); }); ps.on('close', code => { //console.log(`ps process exited with code ${code}`); if (!gotError) { this._logOutputProcess = undefined; } }); } } public dispose() { if (this._logOutputProcess) { this._logOutputProcess.kill(); this._logOutputProcess = undefined; } if (this._logOutputChannel) { this._logOutputChannel.dispose(); this._logOutputChannel = undefined; } } } // Inspired by https://stackoverflow.com/a/27587200 function incompleteUTF8Index(buf: Buffer) { for (let i = Math.max(buf.length - 3, 0); i < buf.length; i++) { const ch = buf[i]; if ((ch & 0xc0) === 0x80) { // 10xxxxxx continue; } const leadIndex = i; if ((ch & 0x80) === 0) { // 0xxxxxxx } else if ((ch & 0xe0) === 0xc0) { // 110xxxxx i++; } else if ((ch & 0xf0) === 0xe0) { // 1110xxxx i += 2; } else if ((ch & 0xf8) === 0xf0) { // 11110xxx i += 3; } else { // Unrecognized. break; } if (i >= buf.length) { return leadIndex; } } return buf.length; } ================================================ FILE: src/vscode-windhawk/src/storagePaths.ts ================================================ import * as reg from 'native-reg'; import * as path from 'path'; import * as vscode from 'vscode'; import config from './config'; import * as ini from './ini'; type FileSystemPaths = { appRootPath: string, appDataPath: string, enginePath: string, compilerPath: string, uiPath: string }; type StoragePathsPortable = { portable: true, fsPaths: FileSystemPaths }; type StoragePathsNonPortable = { portable: false, fsPaths: FileSystemPaths, regKey: reg.HKEY, regSubKey: string }; export type StoragePaths = | StoragePathsPortable | StoragePathsNonPortable; function getAppRootPath() { const debugAppRootPath = config.debug.appRootPath; if (debugAppRootPath) { return debugAppRootPath; } const vscodeInstallPath = vscode.env.appRoot; // returns \resources\app return path.dirname(path.dirname(path.dirname(vscodeInstallPath))); } function getStorageConfig(appRootPath: string) { const iniFilePath = path.join(appRootPath, 'windhawk.ini'); return ini.fromFile(iniFilePath); } function expandEnvironmentVariables(path: string) { // https://stackoverflow.com/a/21363956 return path.replace(/%([^%]+)%/g, (original, matched) => { return process.env[matched] ?? original; }); } export function getStoragePaths(): StoragePaths { const appRootPath = getAppRootPath(); const storageConfig = getStorageConfig(appRootPath); const portable = !!parseInt(storageConfig.Storage.Portable, 10); const processPath = (p: string) => path.resolve(appRootPath, expandEnvironmentVariables(p)); const appDataPath = processPath(storageConfig.Storage.AppDataPath); const enginePath = processPath(storageConfig.Storage.EnginePath); const compilerPath = processPath(storageConfig.Storage.CompilerPath); const uiPath = processPath(storageConfig.Storage.UIPath); if (portable) { return { portable, fsPaths: { appRootPath, appDataPath, enginePath, compilerPath, uiPath } }; } const registryKey = storageConfig.Storage.RegistryKey; let i = registryKey.indexOf('\\'); if (i === -1) { i = registryKey.length; } let regKey: reg.HKEY; switch (registryKey.slice(0, i)) { case 'HKEY_CURRENT_USER': case 'HKCU': regKey = reg.HKCU; break; case 'HKEY_USERS': case 'HKU': regKey = reg.HKU; break; case 'HKEY_LOCAL_MACHINE': case 'HKLM': regKey = reg.HKLM; break; default: throw new Error('Unsupported registry path'); } const regSubKey = registryKey.slice(i + 1); return { portable, fsPaths: { appRootPath, appDataPath, enginePath, compilerPath, uiPath }, regKey, regSubKey }; } ================================================ FILE: src/vscode-windhawk/src/utils/appSettingsUtils.ts ================================================ import * as child_process from 'child_process'; import * as reg from 'native-reg'; import * as path from 'path'; import * as vscode from 'vscode'; import * as ini from '../ini'; // ============================================================================ // Field Descriptors - Single source of truth // ============================================================================ type FieldDescriptor = { readonly name: string; readonly storageName: string; readonly type: 'string' | 'boolean' | 'number' | 'boolean-nullable' | 'string-array'; readonly location: 'app' | 'engine'; readonly defaultValue?: string | number | boolean | null | string[]; readonly portableOnly?: boolean; readonly nonPortableOnly?: boolean; }; const APP_SETTINGS_FIELDS = [ { name: 'language', storageName: 'Language', type: 'string', location: 'app', defaultValue: 'en' }, { name: 'disableUpdateCheck', storageName: 'DisableUpdateCheck', type: 'boolean', location: 'app' }, { name: 'disableRunUIScheduledTask', storageName: 'DisableRunUIScheduledTask', type: 'boolean-nullable', location: 'app', nonPortableOnly: true }, { name: 'devModeOptOut', storageName: 'DevModeOptOut', type: 'boolean', location: 'app' }, { name: 'devModeUsedAtLeastOnce', storageName: 'DevModeUsedAtLeastOnce', type: 'boolean', location: 'app' }, { name: 'hideTrayIcon', storageName: 'HideTrayIcon', type: 'boolean', location: 'app' }, { name: 'alwaysCompileModsLocally', storageName: 'AlwaysCompileModsLocally', type: 'boolean', location: 'app' }, { name: 'dontAutoShowToolkit', storageName: 'DontAutoShowToolkit', type: 'boolean', location: 'app' }, { name: 'modTasksDialogDelay', storageName: 'ModTasksDialogDelay', type: 'number', location: 'app', defaultValue: 2000 }, { name: 'safeMode', storageName: 'SafeMode', type: 'boolean', location: 'app' }, { name: 'loggingVerbosity', storageName: 'LoggingVerbosity', type: 'number', location: 'app' }, { name: 'loggingVerbosity', storageName: 'LoggingVerbosity', type: 'number', location: 'engine' }, { name: 'include', storageName: 'Include', type: 'string-array', location: 'engine' }, { name: 'exclude', storageName: 'Exclude', type: 'string-array', location: 'engine' }, { name: 'injectIntoCriticalProcesses', storageName: 'InjectIntoCriticalProcesses', type: 'boolean', location: 'engine' }, { name: 'injectIntoIncompatiblePrograms', storageName: 'InjectIntoIncompatiblePrograms', type: 'boolean', location: 'engine' }, { name: 'injectIntoGames', storageName: 'InjectIntoGames', type: 'boolean', location: 'engine' }, ] as const satisfies readonly FieldDescriptor[]; type StorageFieldName = typeof APP_SETTINGS_FIELDS[number]['storageName']; type StorageLocation = typeof APP_SETTINGS_FIELDS[number]['location']; // Derive AppSettings type from field descriptors type FieldTypeMap = { 'string': string; 'boolean': boolean; 'number': number; 'boolean-nullable': boolean | null; 'string-array': string[]; }; type AppFieldsDescriptor = Extract; type EngineFieldsDescriptor = Extract; export type AppSettings = { [K in AppFieldsDescriptor as K['name']]: FieldTypeMap[K['type']] } & { engine: { [K in EngineFieldsDescriptor as K['name']]: FieldTypeMap[K['type']] } }; export interface AppSettingsUtils { getAppSettings(): AppSettings; updateAppSettings(appSettings: Partial): void; shouldRestartApp(appSettings: Partial): boolean; shouldNotifyTrayProgram(appSettings: Partial): boolean; } // ============================================================================ // Storage Backend Abstraction // ============================================================================ interface AppSettingsBackend { readAllFields(location: StorageLocation): Partial> | null; writeAllFields(location: StorageLocation, fields: Partial>): void; } // ============================================================================ // Unified Codec - Converts between AppSettings and storage format // ============================================================================ function splitPipeDelimited(value: string): string[] { return !value ? [] : value.split('|'); } function getDefaultValue(field: FieldDescriptor): any { if (field.defaultValue !== undefined) { return field.defaultValue; } // Fallback defaults by type if not specified if (field.type === 'string') { return ''; } else if (field.type === 'boolean') { return false; } else if (field.type === 'boolean-nullable') { return null; } else if (field.type === 'number') { return 0; } else if (field.type === 'string-array') { return []; } return undefined; } class AppSettingsCodec { static parse(backend: AppSettingsBackend): AppSettings { const appRawFields = backend.readAllFields('app') || {}; const engineRawFields = backend.readAllFields('engine') || {}; const result: any = { engine: {} }; for (const field of APP_SETTINGS_FIELDS) { const rawFields = field.location === 'app' ? appRawFields : engineRawFields; const rawValue = rawFields[field.storageName]; let parsedValue: any; if (rawValue !== undefined) { // Value exists in storage - parse it if (field.type === 'string') { parsedValue = rawValue as string; } else if (field.type === 'boolean' || field.type === 'boolean-nullable') { parsedValue = !!rawValue; } else if (field.type === 'number') { parsedValue = rawValue as number; } else { // field.type === 'string-array' parsedValue = splitPipeDelimited(rawValue as string); } } else { // Value missing from storage - use default parsedValue = getDefaultValue(field); } // Set the value in the result object if (field.location === 'engine') { result.engine[field.name] = parsedValue; } else { result[field.name] = parsedValue; } } return result; } static serialize(backend: AppSettingsBackend, appSettings: Partial): void { const appFieldsToWrite: Partial> = {}; const engineFieldsToWrite: Partial> = {}; for (const field of APP_SETTINGS_FIELDS) { const value = field.location === 'engine' ? appSettings.engine?.[field.name] : appSettings[field.name]; if (value === undefined) { continue; } // Convert TypeScript value to storage format let storageValue: string | number; if (field.type === 'string') { storageValue = value as string; } else if (field.type === 'boolean' || field.type === 'boolean-nullable') { storageValue = value ? 1 : 0; } else if (field.type === 'number') { storageValue = value as number; } else { // field.type === 'string-array' storageValue = (value as string[]).join('|'); } if (field.location === 'app') { appFieldsToWrite[field.storageName] = storageValue; } else { engineFieldsToWrite[field.storageName] = storageValue; } } if (Object.keys(appFieldsToWrite).length > 0) { backend.writeAllFields('app', appFieldsToWrite); } if (Object.keys(engineFieldsToWrite).length > 0) { backend.writeAllFields('engine', engineFieldsToWrite); } } } // ============================================================================ // INI Storage Backend (Portable mode) // ============================================================================ class IniAppSettingsBackend implements AppSettingsBackend { private settingsIniPath: string; private engineSettingsIniPath: string; constructor(appDataPath: string) { this.settingsIniPath = path.join(appDataPath, 'settings.ini'); this.engineSettingsIniPath = path.join(appDataPath, 'engine', 'settings.ini'); } readAllFields(location: StorageLocation): Partial> | null { const iniPath = location === 'app' ? this.settingsIniPath : this.engineSettingsIniPath; const iniFileParsed = ini.fromFileOrDefault(iniPath); const settings = iniFileParsed.Settings; if (!settings) { return {}; } const result: Partial> = {}; // Use field descriptors to determine correct type for each field for (const field of APP_SETTINGS_FIELDS) { if (field.location !== location) { continue; } // Skip fields that are only for non-portable mode if ((field as FieldDescriptor).nonPortableOnly) { continue; } const value = settings[field.storageName]; if (value === undefined) { continue; } // Determine type based on field descriptor if (field.type === 'string' || field.type === 'string-array') { result[field.storageName] = value; } else { // number, boolean, boolean-nullable - all stored as numbers in INI const numValue = parseInt(value, 10); result[field.storageName] = isNaN(numValue) ? 0 : numValue; } } return result; } writeAllFields(location: StorageLocation, fields: Partial>): void { const iniPath = location === 'app' ? this.settingsIniPath : this.engineSettingsIniPath; const iniFileParsed = ini.fromFileOrDefault(iniPath); iniFileParsed.Settings = iniFileParsed.Settings || {}; for (const [key, value] of Object.entries(fields)) { iniFileParsed.Settings[key] = typeof value === 'number' ? value.toString() : value; } ini.toFile(iniPath, iniFileParsed); } } // ============================================================================ // Registry Storage Backend (Non-portable mode) // ============================================================================ class RegistryAppSettingsBackend implements AppSettingsBackend { private regKey: reg.HKEY; private regSubKey: string; private engineRegSubKey: string; constructor(regKey: reg.HKEY, regSubKey: string) { this.regKey = regKey; this.regSubKey = regSubKey + '\\Settings'; this.engineRegSubKey = regSubKey + '\\Engine\\Settings'; } readAllFields(location: StorageLocation): Partial> | null { const subKey = location === 'app' ? this.regSubKey : this.engineRegSubKey; const key = reg.createKey(this.regKey, subKey, reg.Access.QUERY_VALUE | reg.Access.WOW64_64KEY); try { const result: Partial> = {}; // Use field descriptors to determine correct type for each field for (const field of APP_SETTINGS_FIELDS) { if (field.location !== location) { continue; } // Skip fields that are only for portable mode if ((field as FieldDescriptor).portableOnly) { continue; } // Read with the correct type based on field descriptor if (field.type === 'string' || field.type === 'string-array') { const value = reg.getValue(key, null, field.storageName, reg.GetValueFlags.RT_REG_SZ); if (value !== null) { result[field.storageName] = value as string; } } else { // number, boolean, boolean-nullable - all stored as DWORD const value = reg.getValue(key, null, field.storageName, reg.GetValueFlags.RT_REG_DWORD); if (value !== null) { result[field.storageName] = value as number; } } } return result; } finally { reg.closeKey(key); } } writeAllFields(location: StorageLocation, fields: Partial>): void { const subKey = location === 'app' ? this.regSubKey : this.engineRegSubKey; const key = reg.createKey(this.regKey, subKey, reg.Access.SET_VALUE | reg.Access.WOW64_64KEY); try { for (const [fieldName, value] of Object.entries(fields)) { if (typeof value === 'number') { reg.setValueDWORD(key, fieldName, value); } else { reg.setValueSZ(key, fieldName, value); } } } finally { reg.closeKey(key); } } } // ============================================================================ // Base Implementation - Common logic for both modes // ============================================================================ class AppSettingsUtilsBase implements AppSettingsUtils { protected backend: AppSettingsBackend; protected constructor(backend: AppSettingsBackend) { this.backend = backend; } public getAppSettings(): AppSettings { return AppSettingsCodec.parse(this.backend); } public updateAppSettings(appSettings: Partial): void { AppSettingsCodec.serialize(this.backend, appSettings); } public shouldRestartApp(appSettings: Partial): boolean { return appSettings.safeMode !== undefined || appSettings.loggingVerbosity !== undefined || (appSettings.engine !== undefined && Object.keys(appSettings.engine).length > 0); } public shouldNotifyTrayProgram(appSettings: Partial): boolean { return appSettings.language !== undefined || appSettings.disableUpdateCheck !== undefined || appSettings.hideTrayIcon !== undefined || appSettings.dontAutoShowToolkit !== undefined || appSettings.modTasksDialogDelay !== undefined; } } // ============================================================================ // Portable Implementation // ============================================================================ export class AppSettingsUtilsPortable extends AppSettingsUtilsBase { public constructor(appDataPath: string) { super(new IniAppSettingsBackend(appDataPath)); } public updateAppSettings(appSettings: Partial): void { // Special validation for portable mode if (appSettings.disableRunUIScheduledTask !== undefined && appSettings.disableRunUIScheduledTask !== null) { throw new Error('Cannot set disableRunUIScheduledTask in portable mode'); } super.updateAppSettings(appSettings); } } // ============================================================================ // Non-Portable Implementation // ============================================================================ export class AppSettingsUtilsNonPortable extends AppSettingsUtilsBase { public constructor(regKey: reg.HKEY, regSubKey: string) { super(new RegistryAppSettingsBackend(regKey, regSubKey)); } public updateAppSettings(appSettings: Partial): void { // Handle special side effects for non-portable mode if (appSettings.language !== undefined) { try { this.setInstallerLanguage(appSettings.language); } catch (e) { console.warn('Failed to set installer language', e); } } if (appSettings.disableUpdateCheck !== undefined) { this.enableScheduledTask('WindhawkUpdateTask', !appSettings.disableUpdateCheck); } if (appSettings.disableRunUIScheduledTask !== undefined) { this.enableScheduledTask('WindhawkRunUITask', !appSettings.disableRunUIScheduledTask); } super.updateAppSettings(appSettings); } private setInstallerLanguage(language: string) { // https://github.com/sindresorhus/lcid/blob/958d38ff2b812d6854cbba2cae611e86a1d5ddf3/lcid.json const lcidMap = { "4": "zh_CHS", "1025": "ar_SA", "1026": "bg_BG", "1027": "ca_ES", "1028": "zh_TW", "1029": "cs_CZ", "1030": "da_DK", "1031": "de_DE", "1032": "el_GR", "1033": "en_US", "1034": "es_ES", "1035": "fi_FI", "1036": "fr_FR", "1037": "he_IL", "1038": "hu_HU", "1039": "is_IS", "1040": "it_IT", "1041": "ja_JP", "1042": "ko_KR", "1043": "nl_NL", "1044": "nb_NO", "1045": "pl_PL", "1046": "pt_BR", "1047": "rm_CH", "1048": "ro_RO", "1049": "ru_RU", "1050": "hr_HR", "1051": "sk_SK", "1052": "sq_AL", "1053": "sv_SE", "1054": "th_TH", "1055": "tr_TR", "1056": "ur_PK", "1057": "id_ID", "1058": "uk_UA", "1059": "be_BY", "1060": "sl_SI", "1061": "et_EE", "1062": "lv_LV", "1063": "lt_LT", "1064": "tg_TJ", "1065": "fa_IR", "1066": "vi_VN", "1067": "hy_AM", "1069": "eu_ES", "1070": "wen_DE", "1071": "mk_MK", "1072": "st_ZA", "1073": "ts_ZA", "1074": "tn_ZA", "1075": "ven_ZA", "1076": "xh_ZA", "1077": "zu_ZA", "1078": "af_ZA", "1079": "ka_GE", "1080": "fo_FO", "1081": "hi_IN", "1082": "mt_MT", "1083": "se_NO", "1084": "gd_GB", "1085": "yi", "1086": "ms_MY", "1087": "kk_KZ", "1088": "ky_KG", "1089": "sw_KE", "1090": "tk_TM", "1092": "tt_RU", "1093": "bn_IN", "1094": "pa_IN", "1095": "gu_IN", "1096": "or_IN", "1097": "ta_IN", "1098": "te_IN", "1099": "kn_IN", "1100": "ml_IN", "1101": "as_IN", "1102": "mr_IN", "1103": "sa_IN", "1104": "mn_MN", "1105": "bo_CN", "1106": "cy_GB", "1107": "kh_KH", "1108": "lo_LA", "1109": "my_MM", "1110": "gl_ES", "1111": "kok_IN", "1113": "sd_IN", "1114": "syr_SY", "1115": "si_LK", "1116": "chr_US", "1118": "am_ET", "1119": "tmz", "1121": "ne_NP", "1122": "fy_NL", "1123": "ps_AF", "1124": "fil_PH", "1125": "div_MV", "1126": "bin_NG", "1127": "fuv_NG", "1128": "ha_NG", "1129": "ibb_NG", "1130": "yo_NG", "1131": "quz_BO", "1132": "ns_ZA", "1133": "ba_RU", "1134": "lb_LU", "1135": "kl_GL", "1144": "ii_CN", "1146": "arn_CL", "1148": "moh_CA", "1150": "br_FR", "1152": "ug_CN", "1153": "mi_NZ", "1154": "oc_FR", "1155": "co_FR", "1156": "gsw_FR", "1157": "sah_RU", "1158": "qut_GT", "1159": "rw_RW", "1160": "wo_SN", "1164": "gbz_AF", "2049": "ar_IQ", "2052": "zh_CN", "2055": "de_CH", "2057": "en_GB", "2058": "es_MX", "2060": "fr_BE", "2064": "it_CH", "2067": "nl_BE", "2068": "nn_NO", "2070": "pt_PT", "2072": "ro_MD", "2073": "ru_MD", "2077": "sv_FI", "2080": "ur_IN", "2092": "az_AZ", "2094": "dsb_DE", "2107": "se_SE", "2108": "ga_IE", "2110": "ms_BN", "2115": "uz_UZ", "2128": "mn_CN", "2129": "bo_BT", "2141": "iu_CA", "2143": "tmz_DZ", "2145": "ne_IN", "2155": "quz_EC", "2163": "ti_ET", "3073": "ar_EG", "3076": "zh_HK", "3079": "de_AT", "3081": "en_AU", "3082": "es_ES", "3084": "fr_CA", "3098": "sr_SP", "3131": "se_FI", "3179": "quz_PE", "4097": "ar_LY", "4100": "zh_SG", "4103": "de_LU", "4105": "en_CA", "4106": "es_GT", "4108": "fr_CH", "4122": "hr_BA", "4155": "smj_NO", "5121": "ar_DZ", "5124": "zh_MO", "5127": "de_LI", "5129": "en_NZ", "5130": "es_CR", "5132": "fr_LU", "5179": "smj_SE", "6145": "ar_MA", "6153": "en_IE", "6154": "es_PA", "6156": "fr_MC", "6203": "sma_NO", "7169": "ar_TN", "7177": "en_ZA", "7178": "es_DO", "7180": "fr_029", "7194": "sr_BA", "7227": "sma_SE", "8193": "ar_OM", "8201": "en_JA", "8202": "es_VE", "8204": "fr_RE", "8218": "bs_BA", "8251": "sms_FI", "9217": "ar_YE", "9225": "en_CB", "9226": "es_CO", "9228": "fr_CG", "9275": "smn_FI", "10241": "ar_SY", "10249": "en_BZ", "10250": "es_PE", "10252": "fr_SN", "11265": "ar_JO", "11273": "en_TT", "11274": "es_AR", "11276": "fr_CM", "12289": "ar_LB", "12297": "en_ZW", "12298": "es_EC", "12300": "fr_CI", "13313": "ar_KW", "13321": "en_PH", "13322": "es_CL", "13324": "fr_ML", "14337": "ar_AE", "14345": "en_ID", "14346": "es_UR", "14348": "fr_MA", "15361": "ar_BH", "15369": "en_HK", "15370": "es_PY", "15372": "fr_HT", "16385": "ar_QA", "16393": "en_IN", "16394": "es_BO", "17417": "en_MY", "17418": "es_SV", "18441": "en_SG", "18442": "es_HN", "19466": "es_NI", "20490": "es_PR", "21514": "es_US", "31748": "zh_CHT" }; const languageParts = language.split('-'); let languageLcid: number | undefined; for (const [lcid, iterLanguage] of Object.entries(lcidMap)) { const iterLanguageParts = iterLanguage.split('_'); if (languageParts.length <= iterLanguageParts.length && languageParts.every((part, index) => part === iterLanguageParts[index])) { languageLcid = parseInt(lcid, 10); break; } } if (languageLcid === undefined) { return; } // Special case: Use Spanish International. if (languageLcid === 1034) { languageLcid = 3082; } const key = reg.createKey(reg.HKLM, 'SOFTWARE\\Windhawk', reg.Access.SET_VALUE | reg.Access.WOW64_32KEY); try { reg.setValueDWORD(key, 'language', languageLcid); } finally { reg.closeKey(key); } } private enableScheduledTask(taskName: string, enable: boolean) { try { const ps = child_process.spawn('schtasks.exe', [ '/change', '/tn', taskName, enable ? '/enable' : '/disable' ]); let gotError = false; let stderr = ''; ps.stderr.on('data', data => { //console.log(`ps stderr: ${data}`); stderr += data; }); ps.on('error', err => { //console.log('Oh no, the error: ' + err); gotError = true; vscode.window.showErrorMessage(err.message); }); ps.on('close', code => { //console.log(`ps process exited with code ${code}`); if (!gotError && code !== 0) { let message = 'schtasks.exe error'; const stderrFiltered = stderr.trim().replace(/^ERROR:\s*/, ''); if (stderrFiltered !== '') { message += ': ' + stderrFiltered; } vscode.window.showWarningMessage(message); } }); } catch (e) { vscode.window.showErrorMessage(e.message); } } } ================================================ FILE: src/vscode-windhawk/src/utils/compilerUtils.ts ================================================ import * as child_process from 'child_process'; import * as fs from 'fs'; import * as path from 'path'; import * as vscode from 'vscode'; type CompilationTarget = | 'i686-w64-mingw32' | 'x86_64-w64-mingw32' | 'aarch64-w64-mingw32'; type CompilationResult = { exitCode: number | null; stdout: string; stderr: string; }; export class CompilerError extends Error { public exitCode: number | null; public stdout: string; public stderr: string; constructor(target: CompilationTarget, result: number | null, stdout: string, stderr: string) { let msg = 'Compilation failed'; if (result === 1) { msg += ', the mod might require a newer Windhawk version'; if (target === 'aarch64-w64-mingw32') { msg += ', or perhaps the mod isn\'t compatible with ARM64 yet'; } } else if (result === 0xC0000135) { msg += ', some files are missing, please reinstall Windhawk and ' + 'make sure files aren\'t being removed by an antivirus'; } else { const exitCodeStr = result !== null ? `0x${result.toString(16)}` : 'unknown'; msg += `, error code: ${exitCodeStr}, please reinstall Windhawk ` + 'and make sure files aren\'t being removed by an antivirus'; } super(msg); this.exitCode = result; this.stdout = stdout; this.stderr = stderr; } } export class CompilerKilled extends Error { constructor() { super('Compilation was aborted'); } } export default class CompilerUtils { private compilerPath: string; private enginePath: string; private engineModsPath: string; private arm64Enabled: boolean; private supportedCompilationTargets: CompilationTarget[]; private activeProcesses: Set = new Set(); private canceledProcesses: Set = new Set(); public constructor(compilerPath: string, enginePath: string, appDataPath: string, arm64Enabled: boolean) { this.compilerPath = compilerPath; this.enginePath = enginePath; this.engineModsPath = path.join(appDataPath, 'Engine', 'Mods'); this.arm64Enabled = arm64Enabled; this.supportedCompilationTargets = [ 'i686-w64-mingw32', 'x86_64-w64-mingw32', ]; if (arm64Enabled) { this.supportedCompilationTargets.push('aarch64-w64-mingw32'); } for (const target of this.supportedCompilationTargets) { try { this.copyCompilerLibs(target); } catch (e: unknown) { const message = e instanceof Error ? e.message : String(e); vscode.window.showErrorMessage(`Failed to copy compiler libs for target ${target}: ${message}`); } } } private subfolderFromCompilationTarget(target: CompilationTarget) { switch (target) { case 'i686-w64-mingw32': return '32'; case 'x86_64-w64-mingw32': return '64'; case 'aarch64-w64-mingw32': return 'arm64'; } } private compilationTargetsFromArchitecture(architectures: string[], modTargets: string[]) { if (architectures.length === 0) { architectures = ['x86', 'x86-64']; } // Keep in lowercase. const commonSystemModTargets = [ 'startmenuexperiencehost.exe', 'searchhost.exe', 'explorer.exe', 'shellexperiencehost.exe', 'shellhost.exe', 'dwm.exe', 'notepad.exe', 'regedit.exe' ]; const targets: CompilationTarget[] = []; for (const architecture of architectures) { if (architecture === 'x86') { targets.push('i686-w64-mingw32'); continue; } if (architecture === 'x86-64') { if (this.arm64Enabled) { targets.push('aarch64-w64-mingw32'); if (modTargets.length == 0 || !modTargets.every(target => commonSystemModTargets.includes(target.toLowerCase()))) { targets.push('x86_64-w64-mingw32'); } } else { targets.push('x86_64-w64-mingw32'); } continue; } if (architecture === 'amd64') { targets.push('x86_64-w64-mingw32'); continue; } if (architecture === 'arm64') { if (this.arm64Enabled) { targets.push('aarch64-w64-mingw32'); } continue; } throw new Error(`Unsupported architecture: ${architecture}`); } if (targets.length === 0) { throw new Error('The current architecture is not supported'); } return targets; } private doesCompiledModExist(fileName: string, target: CompilationTarget) { const compiledModPath = path.join(this.engineModsPath, this.subfolderFromCompilationTarget(target), fileName); return fs.existsSync(compiledModPath); } private async makePrecompiledHeaders( pchHeaderPath: string, targetPchPath: string, target: CompilationTarget, modId: string, modVersion: string, extraArgs: string[], ): Promise { const clangPath = path.join(this.compilerPath, 'bin', 'clang++.exe'); const args = [ '-std=c++23', '-O2', '-DUNICODE', '-D_UNICODE', '-DWINVER=0x0A00', '-D_WIN32_WINNT=0x0A00', '-D_WIN32_IE=0x0A00', '-DNTDDI_VERSION=0x0A000008', '-D__USE_MINGW_ANSI_STDIO=0', '-DWH_MOD', '-DWH_MOD_ID=L"' + modId.replace(/"/g, '\\"') + '"', '-DWH_MOD_VERSION=L"' + modVersion.replace(/"/g, '\\"') + '"', '-x', 'c++-header', pchHeaderPath, '-target', target, '-o', targetPchPath, ...extraArgs.filter(arg => arg.startsWith('-D')) ]; const ps = child_process.spawn(clangPath, args, { cwd: this.compilerPath }); this.activeProcesses.add(ps); const stdoutBuffers: Buffer[] = []; const stderrBuffers: Buffer[] = []; ps.stdout.on('data', data => { stdoutBuffers.push(data); }); ps.stderr.on('data', data => { stderrBuffers.push(data); }); return new Promise((resolve, reject) => { ps.on('error', err => { this.activeProcesses.delete(ps); this.canceledProcesses.delete(ps); reject(err); }); ps.on('close', code => { this.activeProcesses.delete(ps); const wasCanceled = this.canceledProcesses.delete(ps); if (wasCanceled) { reject(new CompilerKilled()); return; } const stdout = Buffer.concat(stdoutBuffers).toString('utf8'); const stderr = Buffer.concat(stderrBuffers).toString('utf8'); resolve({ exitCode: code, stdout, stderr }); }); }); } private async compileModInternal( modSourceCode: string, targetDllName: string, target: CompilationTarget, modId: string, modVersion: string, extraArgs: string[], pchPath?: string ): Promise { const clangPath = path.join(this.compilerPath, 'bin', 'clang++.exe'); const subfolder = this.subfolderFromCompilationTarget(target); const engineLibPath = path.join(this.enginePath, subfolder, 'windhawk.lib'); const compiledModDllPath = path.join(this.engineModsPath, subfolder, targetDllName); fs.mkdirSync(path.dirname(compiledModDllPath), { recursive: true }); const windowsVersionFlags = [ 'classic-taskdlg-fix\n1.1.0', ].includes(`${modId}\n${modVersion}`) ? [] : [ '-DWINVER=0x0A00', '-D_WIN32_WINNT=0x0A00', '-D_WIN32_IE=0x0A00', '-DNTDDI_VERSION=0x0A000008', ]; const backwardCompatibilityFlags: string[] = []; if ([ 'chrome-ui-tweaks\n1.0.0', ].includes(`${modId}\n${modVersion}`)) { backwardCompatibilityFlags.push('-include', 'atomic', '-include', 'optional'); } if ([ 'sib-plusplus-tweaker\n0.7.1', ].includes(`${modId}\n${modVersion}`)) { backwardCompatibilityFlags.push('-include', 'atomic'); } if ([ 'classic-explorer-treeview\n1.1.3', 'sysdm-general-tab\n1.1', ].includes(`${modId}\n${modVersion}`)) { backwardCompatibilityFlags.push('-include', 'cmath'); } if ([ 'ce-disable-process-button-flashing\n1.0.1', 'windows-7-clock-spacing\n1.0.0', ].includes(`${modId}\n${modVersion}`)) { backwardCompatibilityFlags.push('-include', 'vector'); } const args = [ '-std=c++23', '-O2', '-shared', '-DUNICODE', '-D_UNICODE', ...windowsVersionFlags, '-D__USE_MINGW_ANSI_STDIO=0', '-DWH_MOD', '-DWH_MOD_ID=L"' + modId.replace(/"/g, '\\"') + '"', '-DWH_MOD_VERSION=L"' + modVersion.replace(/"/g, '\\"') + '"', engineLibPath, '-x', 'c++', '-', '-include', 'windhawk_api.h', '-target', target, '-Wl,--export-all-symbols', '-o', compiledModDllPath, ...(pchPath ? ['-include-pch', pchPath] : []), ...extraArgs, ...backwardCompatibilityFlags, ]; const ps = child_process.spawn(clangPath, args, { cwd: this.compilerPath }); this.activeProcesses.add(ps); const stdoutBuffers: Buffer[] = []; const stderrBuffers: Buffer[] = []; ps.stdout.on('data', data => { stdoutBuffers.push(data); }); ps.stderr.on('data', data => { stderrBuffers.push(data); }); ps.stdin.write(modSourceCode); ps.stdin.end(); return new Promise((resolve, reject) => { ps.on('error', err => { this.activeProcesses.delete(ps); this.canceledProcesses.delete(ps); reject(err); }); ps.on('close', code => { this.activeProcesses.delete(ps); const wasCanceled = this.canceledProcesses.delete(ps); if (wasCanceled) { reject(new CompilerKilled()); return; } const stdout = Buffer.concat(stdoutBuffers).toString('utf8'); const stderr = Buffer.concat(stderrBuffers).toString('utf8'); resolve({ exitCode: code, stdout, stderr }); }); }); } private copyCompilerLibs(target: CompilationTarget) { const libsDir = path.join(this.compilerPath, target, 'bin'); const targetModsDir = path.join(this.engineModsPath, this.subfolderFromCompilationTarget(target)); fs.mkdirSync(targetModsDir, { recursive: true }); const filesToCopy = [ ['libc++.dll', 'libc++.whl'], ['libunwind.dll', 'libunwind.whl'], ['windhawk-mod-shim.dll', 'windhawk-mod-shim.dll'], ]; // Make sure libc++.dll from previous Windhawk versions is also // up-to-date to address the "Not enough space for thread data" error. if (fs.existsSync(path.join(targetModsDir, 'libc++.dll'))) { filesToCopy.push(['libc++.dll', 'libc++.dll']); } // Do the same for libunwind.dll. if (fs.existsSync(path.join(targetModsDir, 'libunwind.dll'))) { filesToCopy.push(['libunwind.dll', 'libunwind.dll']); } for (const [fileFrom, fileTo] of filesToCopy) { const libPath = path.join(libsDir, fileFrom); const libPathDest = path.join(targetModsDir, fileTo); if (fs.existsSync(libPathDest) && fs.statSync(libPathDest).mtimeMs === fs.statSync(libPath).mtimeMs) { continue; } try { fs.copyFileSync(libPath, libPathDest); } catch (e: unknown) { if (!fs.existsSync(libPathDest)) { throw e; } // The lib file already exists, perhaps it's in use. // Try to rename it to a temporary name. const libPathDestExt = path.extname(libPathDest); const libPathDestBaseName = path.basename(libPathDest, libPathDestExt); for (let i = 1; ; i++) { const tempFilename = libPathDestBaseName + '_temp' + i + libPathDestExt; const libPathDestTemp = path.join(targetModsDir, tempFilename); try { fs.renameSync(libPathDest, libPathDestTemp); break; } catch (e: unknown) { if (!fs.existsSync(libPathDestTemp)) { throw e; } } } fs.copyFileSync(libPath, libPathDest); } } } public async compileMod( modId: string, modVersion: string, modTargets: string[], modSourceCode: string, architectures: string[], compilerOptions: string | undefined, precompiledHeadersFolder?: string ) { let targetDllName: string; for (; ;) { targetDllName = modId + '_' + modVersion + '_' + randomIntFromInterval(100000, 999999) + '.dll'; if (this.supportedCompilationTargets.every(target => !this.doesCompiledModExist(targetDllName, target))) { break; } } let compilerOptionsArray: string[] = []; if (compilerOptions && compilerOptions.trim() !== '') { compilerOptionsArray = splitargs(compilerOptions); } for (const target of this.compilationTargetsFromArchitecture(architectures, modTargets)) { let pchPath: string | undefined = undefined; if (precompiledHeadersFolder) { const pchHeaderPath = path.join(precompiledHeadersFolder, 'windhawk_pch.h'); if (fs.existsSync(pchHeaderPath)) { pchPath = path.join(precompiledHeadersFolder, `windhawk_t_${target}.pch`); if (!fs.existsSync(pchPath) || fs.statSync(pchPath).mtimeMs < fs.statSync(pchHeaderPath).mtimeMs) { const { exitCode, stdout, stderr } = await this.makePrecompiledHeaders( pchHeaderPath, pchPath, target, modId, modVersion, compilerOptionsArray ); if (exitCode !== 0) { throw new CompilerError( target, exitCode, stdout, stderr ); } if (stdout) { console.log(`Precompiled headers stdout for target ${target}:\n${stdout}`); } if (stderr) { console.log(`Precompiled headers stderr for target ${target}:\n${stderr}`); } } } } const { exitCode, stdout, stderr } = await this.compileModInternal( modSourceCode, targetDllName, target, modId, modVersion, compilerOptionsArray, pchPath ); if (exitCode !== 0) { throw new CompilerError( target, exitCode, stdout, stderr ); } if (stdout) { console.log(`Compiler stdout for target ${target}:\n${stdout}`); } if (stderr) { console.log(`Compiler stderr for target ${target}:\n${stderr}`); } } return { targetDllName, }; } public cancelCompilation() { for (const process of this.activeProcesses) { this.canceledProcesses.add(process); try { // Needed for Windows: https://stackoverflow.com/a/77421143 process.stdout?.destroy(); process.stdin?.destroy(); process.stderr?.destroy(); process.kill(); } catch (e: unknown) { console.error('Failed to kill compilation process:', e); } } this.activeProcesses.clear(); } } // https://stackoverflow.com/a/7228322 // min and max included function randomIntFromInterval(min: number, max: number) { return Math.floor(Math.random() * (max - min + 1) + min); } // https://github.com/elgs/splitargs function splitargs(input: string, sep?: RegExp, keepQuotes?: boolean) { const separator = sep || /\s/g; let singleQuoteOpen = false; let doubleQuoteOpen = false; let tokenBuffer = []; const ret = []; const arr = input.split(''); for (let i = 0; i < arr.length; ++i) { const element = arr[i]; const matches = element.match(separator); if (element === "'" && !doubleQuoteOpen) { if (keepQuotes === true) { tokenBuffer.push(element); } singleQuoteOpen = !singleQuoteOpen; continue; } else if (element === '"' && !singleQuoteOpen) { if (keepQuotes === true) { tokenBuffer.push(element); } doubleQuoteOpen = !doubleQuoteOpen; continue; } if (!singleQuoteOpen && !doubleQuoteOpen && matches) { if (tokenBuffer.length > 0) { ret.push(tokenBuffer.join('')); tokenBuffer = []; } else if (sep) { ret.push(element); } } else { tokenBuffer.push(element); } } if (tokenBuffer.length > 0) { ret.push(tokenBuffer.join('')); } else if (sep) { ret.push(''); } return ret; } ================================================ FILE: src/vscode-windhawk/src/utils/editorWorkspaceUtils.ts ================================================ import * as fs from 'fs'; import * as path from 'path'; import * as child_process from 'child_process'; import * as vscode from 'vscode'; import config from '../config'; export default class EditorWorkspaceUtils { private workspacePath: string; public constructor() { const firstWorkspaceFolder = vscode.workspace.workspaceFolders?.[0]; if (!firstWorkspaceFolder) { vscode.commands.executeCommand('workbench.action.files.openFolder'); throw new Error('No workspace folder'); } this.workspacePath = firstWorkspaceFolder.uri.fsPath; } public getFilePath(fileName: string) { return path.join(this.workspacePath, fileName); } public getWorkspaceFolder() { return this.workspacePath; } public getModSourcePath() { return this.getFilePath('mod.wh.cpp'); } public getDraftsPath() { return path.join(this.workspacePath, 'Drafts'); } private initializeEditorSettings() { // Flags for clangd. const compileFlags = [ '-x', 'c++', '-std=c++23', '-target', 'x86_64-w64-mingw32', '-DUNICODE', '-D_UNICODE', '-DWINVER=0x0A00', '-D_WIN32_WINNT=0x0A00', '-D_WIN32_IE=0x0A00', '-DNTDDI_VERSION=0x0A000008', '-D__USE_MINGW_ANSI_STDIO=0', '-DWH_MOD', '-DWH_EDITING', '-include', 'windhawk_api.h', '-Wall', '-Wextra', '-Wno-unused-parameter', '-Wno-missing-field-initializers', '-Wno-cast-function-type-mismatch', ]; fs.writeFileSync(this.getFilePath('compile_flags.txt'), compileFlags.join('\n') + '\n'); const clangFormatConfig = [ '# To override, create a .clang-format.windhawk file with the desired settings.', 'BasedOnStyle: Chromium', 'IndentWidth: 4', 'CommentPragmas: ^[ \\t]+@[a-zA-Z]+', ]; if (fs.existsSync(this.getFilePath('.clang-format.windhawk'))) { fs.copyFileSync(this.getFilePath('.clang-format.windhawk'), this.getFilePath('.clang-format')); } else { fs.writeFileSync(this.getFilePath('.clang-format'), clangFormatConfig.join('\n') + '\n'); } if (!fs.existsSync(this.getFilePath('.git'))) { child_process.spawnSync('git', ['init'], { cwd: this.workspacePath, stdio: 'ignore' }); } if (fs.existsSync(this.getFilePath('.git'))) { child_process.spawnSync('git', ['add', 'mod.wh.cpp'], { cwd: this.workspacePath, stdio: 'ignore' }); } } public initializeFromModSource(modSource: string, modSourceFromDrafts?: string | null) { fs.writeFileSync(this.getFilePath('mod.wh.cpp'), modSource); // Remove windhawk_api.h from older versions, it now resides in the // compiler include folder. try { fs.unlinkSync(this.getFilePath('windhawk_api.h')); } catch (e) { // Ignore if file doesn't exist. if (e.code !== 'ENOENT') { throw e; } } this.initializeEditorSettings(); if (modSourceFromDrafts) { // Write the new content after initializing, so that git won't stage the draft changes. fs.writeFileSync(this.getFilePath('mod.wh.cpp'), modSourceFromDrafts); } } public saveModToDrafts(modId: string) { const draftsDir = this.getDraftsPath(); fs.mkdirSync(draftsDir, { recursive: true }); fs.copyFileSync(this.getFilePath('mod.wh.cpp'), path.join(draftsDir, modId + '.wh.cpp')); } public loadModFromDrafts(modId: string) { const draftsPath = this.getDraftsPath(); const modSourcePath = path.join(draftsPath, modId + '.wh.cpp'); if (fs.existsSync(modSourcePath)) { return fs.readFileSync(modSourcePath, 'utf8'); } return null; } public deleteModFromDrafts(modId: string) { const draftsPath = this.getDraftsPath(); const modSourcePath = path.join(draftsPath, modId + '.wh.cpp'); try { fs.unlinkSync(modSourcePath); } catch (e) { // Ignore if file doesn't exist. if (e.code !== 'ENOENT') { throw e; } } } private async toggleMinimalLayout(minimal: boolean) { const vscodeConfig = vscode.workspace.getConfiguration(); const thenableArray: Thenable[] = []; if (minimal) { thenableArray.push(vscode.commands.executeCommand('workbench.action.closeSidebar')); thenableArray.push(vscode.commands.executeCommand('workbench.action.closePanel')); thenableArray.push(vscodeConfig.update('workbench.activityBar.visible', false)); } thenableArray.push(vscodeConfig.update('workbench.editor.showTabs', !minimal)); thenableArray.push(vscodeConfig.update('workbench.statusBar.visible', !minimal)); return Promise.all(thenableArray); } public async enterEditorMode(modId: string, modWasModified = false) { const vscodeConfig = vscode.workspace.getConfiguration(); await Promise.all([ vscodeConfig.update('windhawk.editedModId', modId), vscodeConfig.update('windhawk.editedModWasModified', modWasModified), vscodeConfig.update('git.enabled', true) ]); await vscode.commands.executeCommand('vscode.open', vscode.Uri.file(this.getFilePath('mod.wh.cpp')), { preview: false }); await vscode.commands.executeCommand('workbench.action.closeEditorsInOtherGroups'); await vscode.commands.executeCommand('workbench.action.closeOtherEditors'); await vscode.commands.executeCommand('windhawk.sidebar.focus', { preserveFocus: true }); if (!config.debug.disableMinimalMode) { await this.toggleMinimalLayout(false); } } public async exitEditorMode() { const vscodeConfig = vscode.workspace.getConfiguration(); await Promise.all([ vscodeConfig.update('windhawk.editedModId', undefined), vscodeConfig.update('windhawk.editedModWasModified', undefined), vscodeConfig.update('git.enabled', undefined), ]); await vscode.commands.executeCommand('windhawk.start'); await vscode.commands.executeCommand('workbench.action.closeEditorsInOtherGroups'); await vscode.commands.executeCommand('workbench.action.closeOtherEditors'); if (!config.debug.disableMinimalMode) { await this.toggleMinimalLayout(true); } } public async restoreEditorMode() { const vscodeConfig = vscode.workspace.getConfiguration(); const modIdConfig = vscodeConfig.get('windhawk.editedModId'); const modId = typeof modIdConfig === 'string' ? modIdConfig : null; if (modId) { const modWasModified = !!vscodeConfig.get('windhawk.editedModWasModified'); await this.enterEditorMode(modId, modWasModified); return { modId, modWasModified }; } else { await this.exitEditorMode(); return { modId: null }; } } public async setEditorModeModId(modId: string) { const vscodeConfig = vscode.workspace.getConfiguration(); await vscodeConfig.update('windhawk.editedModId', modId); } public async markEditorModeModAsModified(modified: boolean) { if (!modified && fs.existsSync(this.getFilePath('.git'))) { child_process.spawn('git', ['add', 'mod.wh.cpp'], { cwd: this.workspacePath, stdio: 'ignore' }); } const vscodeConfig = vscode.workspace.getConfiguration(); await vscodeConfig.update('windhawk.editedModWasModified', modified); } } ================================================ FILE: src/vscode-windhawk/src/utils/modConfigUtils.ts ================================================ import * as fs from 'fs'; import * as reg from 'native-reg'; import * as path from 'path'; import * as ini from '../ini'; type ModSettings = Record; type ModSettingsConfig = { initialSettings: ModSettings, previousInitialSettings?: ModSettings }; // Field descriptor for automated parsing/serialization type FieldType = 'string' | 'boolean' | 'string-array'; interface FieldDescriptor { name: string; storageName: string; type: FieldType; } const CONFIG_FIELDS = [ { name: 'libraryFileName', storageName: 'LibraryFileName', type: 'string' }, { name: 'disabled', storageName: 'Disabled', type: 'boolean' }, { name: 'loggingEnabled', storageName: 'LoggingEnabled', type: 'boolean' }, { name: 'debugLoggingEnabled', storageName: 'DebugLoggingEnabled', type: 'boolean' }, { name: 'include', storageName: 'Include', type: 'string-array' }, { name: 'exclude', storageName: 'Exclude', type: 'string-array' }, { name: 'includeCustom', storageName: 'IncludeCustom', type: 'string-array' }, { name: 'excludeCustom', storageName: 'ExcludeCustom', type: 'string-array' }, { name: 'includeExcludeCustomOnly', storageName: 'IncludeExcludeCustomOnly', type: 'boolean' }, { name: 'patternsMatchCriticalSystemProcesses', storageName: 'PatternsMatchCriticalSystemProcesses', type: 'boolean' }, { name: 'architecture', storageName: 'Architecture', type: 'string-array' }, { name: 'version', storageName: 'Version', type: 'string' } ] as const satisfies readonly FieldDescriptor[]; // Map field types to TypeScript types type FieldTypeToTSType = T extends 'string' ? string : T extends 'boolean' ? boolean : T extends 'string-array' ? string[] : never; // Derive ModConfig type from CONFIG_FIELDS type ModConfig = { [K in typeof CONFIG_FIELDS[number] as K['name']]: FieldTypeToTSType }; // Extract valid storage field names from CONFIG_FIELDS type StorageFieldName = typeof CONFIG_FIELDS[number]['storageName']; // Storage abstraction layer interface ModStorageBackend { // Config operations readAllConfigFields(modId: string): Partial> | null; writeAllConfigFields(modId: string, fields: Partial>): void; writeConfigField(modId: string, field: StorageFieldName, value: string | number): void; configExists(modId: string): boolean; // Settings operations readAllSettings(modId: string): Record; writeAllSettings(modId: string, settings: Record): void; // Lifecycle operations deleteConfig(modId: string): void; renameConfig(fromId: string, toId: string): void; // Bulk operations getConfigOfInstalled(): Record; } // Unified codec for ModConfig parsing/serialization class ModConfigCodec { static parse(backend: ModStorageBackend, modId: string): ModConfig | null { // Batch read all fields at once for performance const rawFields = backend.readAllConfigFields(modId); if (!rawFields) { return null; } const libraryFileName = rawFields['LibraryFileName']; if (!libraryFileName || typeof libraryFileName !== 'string') { return null; } // Build config object field by field with proper typing const config: Partial = {}; for (const field of CONFIG_FIELDS) { const rawValue = rawFields[field.storageName]; switch (field.type) { case 'string': config[field.name] = (rawValue ?? '') as string; break; case 'boolean': config[field.name] = !!rawValue; break; case 'string-array': config[field.name] = splitPipeDelimited((rawValue ?? '') as string); break; } } // All fields should be populated at this point return config as ModConfig; } static serialize(backend: ModStorageBackend, modId: string, config: Partial): void { const fieldsToWrite: Partial> = {}; for (const field of CONFIG_FIELDS) { const value = config[field.name]; if (value === undefined) { continue; } let storageValue: string | number; switch (field.type) { case 'string': storageValue = value as string; break; case 'boolean': storageValue = (value as boolean) ? 1 : 0; break; case 'string-array': storageValue = (value as string[]).join('|'); break; } fieldsToWrite[field.storageName] = storageValue; } // Batch write all fields at once for performance backend.writeAllConfigFields(modId, fieldsToWrite); } } function getSettingsChangeTime() { // Unix timestamp in seconds, limited to a positive signed 32-bit integer. return (Date.now() / 1000) & 0x7fffffff; } function splitPipeDelimited(value: string): string[] { return !value ? [] : value.split('|'); } function mergeModSettings(existingSettings: ModSettings, newSettings: ModSettings) { const getNamePrefix = (name: string) => { // Treat each option individually, except for arrays. For arrays, only // consider the prefix before the first [index] - if any settings // already exist with that prefix, don't add any other settings with the // same prefix. return name.replace(/\[\d+\].*$/, '[0]'); }; const existingNamePrefixes: Record = {}; for (const name of Object.keys(existingSettings)) { existingNamePrefixes[getNamePrefix(name)] = true; } const mergedSettings: ModSettings = { ...existingSettings }; let existingSettingsChanged = false; for (const [name, value] of Object.entries(newSettings)) { if (!existingNamePrefixes[getNamePrefix(name)]) { mergedSettings[name] = value; existingSettingsChanged = true; } } return { mergedSettings, existingSettingsChanged }; } function getModStoragePath(engineModsWritablePath: string, modId: string) { return path.join(engineModsWritablePath, 'mod-storage', modId); } function deleteModStoragePath(engineModsWritablePath: string, modId: string): void { const modStoragePath = getModStoragePath(engineModsWritablePath, modId); try { fs.rmSync(modStoragePath, { recursive: true, force: true }); } catch (e) { // Ignore errors. } } // INI-based storage backend (portable mode) class IniStorageBackend implements ModStorageBackend { private engineModsPath: string; private engineModsWritablePath: string; constructor(appDataPath: string) { this.engineModsPath = path.join(appDataPath, 'Engine', 'Mods'); this.engineModsWritablePath = path.join(appDataPath, 'Engine', 'ModsWritable'); } private getModIniPath(modId: string) { return path.join(this.engineModsPath, modId + '.ini'); } private getModWritableIniPath(modId: string) { return path.join(this.engineModsWritablePath, modId + '.ini'); } readAllConfigFields(modId: string): Partial> | null { const modIniPath = this.getModIniPath(modId); const modConfig = ini.fromFileOrDefault(modIniPath); if (!modConfig.Mod) { return null; } const result: Partial> = {}; for (const field of CONFIG_FIELDS) { const value = modConfig.Mod[field.storageName]; if (value !== undefined) { // Convert string representations to appropriate types if (field.type === 'boolean') { result[field.storageName] = parseInt(value, 10); } else { result[field.storageName] = value; } } } return result; } writeAllConfigFields(modId: string, fields: Partial>): void { const modIniPath = this.getModIniPath(modId); const modConfig = ini.fromFileOrDefault(modIniPath); modConfig.Mod = modConfig.Mod || {}; for (const [field, value] of Object.entries(fields)) { modConfig.Mod[field] = value.toString(); } fs.mkdirSync(path.dirname(modIniPath), { recursive: true }); ini.toFile(modIniPath, modConfig); } writeConfigField(modId: string, field: StorageFieldName, value: string | number): void { this.writeAllConfigFields(modId, { [field]: value }); } configExists(modId: string): boolean { const modIniPath = this.getModIniPath(modId); const modConfig = ini.fromFileOrDefault(modIniPath); return !!modConfig.Mod?.LibraryFileName; } readAllSettings(modId: string): Record { const modIniPath = this.getModIniPath(modId); const modConfig = ini.fromFileOrDefault(modIniPath); return modConfig.Settings || {}; } writeAllSettings(modId: string, settings: Record): void { const modIniPath = this.getModIniPath(modId); const modConfig = ini.fromFileOrDefault(modIniPath); const settingsSection: Record = {}; for (const [k, v] of Object.entries(settings)) { settingsSection[k] = v.toString(); } modConfig.Settings = settingsSection; modConfig.Mod = modConfig.Mod || {}; modConfig.Mod.SettingsChangeTime = getSettingsChangeTime().toString(); fs.mkdirSync(path.dirname(modIniPath), { recursive: true }); ini.toFile(modIniPath, modConfig); } deleteConfig(modId: string): void { const modIniPath = this.getModIniPath(modId); try { fs.unlinkSync(modIniPath); } catch (e: any) { if (e.code !== 'ENOENT') { throw e; } } const modWritableIniPath = this.getModWritableIniPath(modId); try { fs.unlinkSync(modWritableIniPath); } catch (e: any) { if (e.code !== 'ENOENT') { throw e; } } deleteModStoragePath(this.engineModsWritablePath, modId); } renameConfig(fromId: string, toId: string): void { const modIniPathFrom = this.getModIniPath(fromId); const modIniPathTo = this.getModIniPath(toId); try { fs.renameSync(modIniPathFrom, modIniPathTo); } catch (e: any) { if (e.code !== 'ENOENT') { throw e; } } const modWritableIniPathFrom = this.getModWritableIniPath(fromId); const modWritableIniPathTo = this.getModWritableIniPath(toId); try { fs.renameSync(modWritableIniPathFrom, modWritableIniPathTo); } catch (e: any) { if (e.code !== 'ENOENT') { throw e; } } } getConfigOfInstalled(): Record { const mods: Record = {}; let engineModsDir: fs.Dir; try { engineModsDir = fs.opendirSync(this.engineModsPath); } catch (e: any) { if (e.code !== 'ENOENT') { throw e; } return mods; } try { let engineModsDirEntry: fs.Dirent | null; while ((engineModsDirEntry = engineModsDir.readSync()) !== null) { if (engineModsDirEntry.isFile() && engineModsDirEntry.name.endsWith('.ini')) { const modId = engineModsDirEntry.name.slice(0, -'.ini'.length); const config = ModConfigCodec.parse(this, modId); if (config) { mods[modId] = config; } } } } finally { engineModsDir.closeSync(); } return mods; } } // Registry-based storage backend (non-portable mode) class RegistryStorageBackend implements ModStorageBackend { private regKey: reg.HKEY; private regSubKey: string; private regSubKeyModWritable: string; private engineModsWritablePath: string; constructor(regKey: reg.HKEY, regSubKey: string, appDataPath: string) { this.regKey = regKey; this.regSubKey = regSubKey + '\\Engine\\Mods'; this.regSubKeyModWritable = regSubKey + '\\Engine\\ModsWritable'; this.engineModsWritablePath = path.join(appDataPath, 'Engine', 'ModsWritable'); } readAllConfigFields(modId: string): Partial> | null { const key = reg.openKey(this.regKey, this.regSubKey + '\\' + modId, reg.Access.QUERY_VALUE | reg.Access.WOW64_64KEY); if (!key) { return null; } try { const result: Partial> = {}; for (const field of CONFIG_FIELDS) { const isDword = field.type === 'boolean'; let value: string | number | null; if (isDword) { value = reg.getValue(key, null, field.storageName, reg.GetValueFlags.RT_REG_DWORD) as number | null; } else { value = reg.getValue(key, null, field.storageName, reg.GetValueFlags.RT_REG_SZ) as string | null; } if (value !== null) { result[field.storageName] = value; } } return result; } finally { reg.closeKey(key); } } writeAllConfigFields(modId: string, fields: Partial>): void { const key = reg.createKey(this.regKey, this.regSubKey + '\\' + modId, reg.Access.SET_VALUE | reg.Access.WOW64_64KEY); try { for (const [field, value] of Object.entries(fields)) { if (typeof value === 'number') { reg.setValueDWORD(key, field, value); } else { reg.setValueSZ(key, field, value); } } } finally { reg.closeKey(key); } } writeConfigField(modId: string, field: StorageFieldName, value: string | number): void { this.writeAllConfigFields(modId, { [field]: value }); } configExists(modId: string): boolean { const key = reg.openKey(this.regKey, this.regSubKey + '\\' + modId, reg.Access.QUERY_VALUE | reg.Access.WOW64_64KEY); if (!key) { return false; } try { return !!reg.getValue(key, null, 'LibraryFileName', reg.GetValueFlags.RT_REG_SZ); } finally { reg.closeKey(key); } } readAllSettings(modId: string): Record { const settings: Record = {}; const key = reg.openKey(this.regKey, this.regSubKey + '\\' + modId + '\\Settings', reg.Access.QUERY_VALUE | reg.Access.WOW64_64KEY); if (key) { try { for (const valueName of reg.enumValueNames(key)) { const value = reg.getValue(key, null, valueName, reg.GetValueFlags.RT_REG_DWORD | reg.GetValueFlags.RT_REG_SZ); if (value !== null) { if (typeof value === 'number') { // Add `| 0` after every math operation to get a // 32-bit signed integer result. const valueSigned = value | 0; settings[valueName] = valueSigned; } else { settings[valueName] = value as string; } } } } finally { reg.closeKey(key); } } return settings; } writeAllSettings(modId: string, settings: Record): void { const settingsKey = reg.createKey(this.regKey, this.regSubKey + '\\' + modId + '\\Settings', reg.Access.QUERY_VALUE | reg.Access.SET_VALUE | reg.Access.DELETE | reg.Access.ENUMERATE_SUB_KEYS | reg.Access.WOW64_64KEY); try { reg.deleteTree(settingsKey, null); for (const [name, value] of Object.entries(settings)) { if (typeof value === 'number') { // Add [...] `>>> 0` for a 32-bit unsigned integer result. const valueUnsigned = value >>> 0; reg.setValueDWORD(settingsKey, name, valueUnsigned); } else { reg.setValueSZ(settingsKey, name, value); } } } finally { reg.closeKey(settingsKey); } const modKey = reg.createKey(this.regKey, this.regSubKey + '\\' + modId, reg.Access.SET_VALUE | reg.Access.WOW64_64KEY); try { reg.setValueDWORD(modKey, 'SettingsChangeTime', getSettingsChangeTime()); } finally { reg.closeKey(modKey); } } deleteConfig(modId: string): void { for (const subKey of [this.regSubKey, this.regSubKeyModWritable]) { const key = reg.openKey(this.regKey, subKey + '\\' + modId, reg.Access.QUERY_VALUE | reg.Access.SET_VALUE | reg.Access.DELETE | reg.Access.ENUMERATE_SUB_KEYS | reg.Access.WOW64_64KEY); if (key) { try { if (reg.deleteTree(key, null)) { reg.deleteKey(key, ''); } } finally { reg.closeKey(key); } } } deleteModStoragePath(this.engineModsWritablePath, modId); } renameConfig(fromId: string, toId: string): void { for (const subKey of [this.regSubKey, this.regSubKeyModWritable]) { const key = reg.openKey(this.regKey, subKey + '\\' + fromId, reg.Access.WRITE | reg.Access.WOW64_64KEY); if (key) { try { reg.renameKey(key, null, toId); } finally { reg.closeKey(key); } } } } getConfigOfInstalled(): Record { const mods: Record = {}; const key = reg.openKey(this.regKey, this.regSubKey, reg.Access.QUERY_VALUE | reg.Access.ENUMERATE_SUB_KEYS | reg.Access.WOW64_64KEY); if (key) { try { for (const modId of reg.enumKeyNames(key)) { const config = ModConfigCodec.parse(this, modId); if (config) { mods[modId] = config; } } } finally { reg.closeKey(key); } } return mods; } } export interface ModConfigUtils { getConfigOfInstalled(): Record; doesConfigExist(modId: string): boolean; getModConfig(modId: string): ModConfig | null; setModConfig(modId: string, config: Partial, settingsConfig?: ModSettingsConfig): void; getModSettings(modId: string): ModSettings; setModSettings(modId: string, settings: ModSettings): void; enableMod(modId: string, enable: boolean): void; enableLogging(modId: string, enable: boolean): void; deleteMod(modId: string): void; changeModId(modIdFrom: string, modIdTo: string): void; } // Base implementation using storage backend pattern class ModConfigUtilsBase implements ModConfigUtils { protected backend: ModStorageBackend; protected constructor(backend: ModStorageBackend) { this.backend = backend; } public getConfigOfInstalled() { return this.backend.getConfigOfInstalled(); } public doesConfigExist(modId: string) { return this.backend.configExists(modId); } public getModConfig(modId: string) { return ModConfigCodec.parse(this.backend, modId); } public setModConfig(modId: string, config: Partial, settingsConfig?: ModSettingsConfig) { const configExisted = this.backend.configExists(modId); ModConfigCodec.serialize(this.backend, modId, config); if (settingsConfig) { if (!settingsConfig.previousInitialSettings && !configExisted) { this.backend.writeAllSettings(modId, settingsConfig.initialSettings); } else { const { mergedSettings, existingSettingsChanged } = mergeModSettings({ ...(settingsConfig.previousInitialSettings || {}), ...this.backend.readAllSettings(modId) }, settingsConfig.initialSettings); if (existingSettingsChanged) { this.backend.writeAllSettings(modId, mergedSettings); } } } } public getModSettings(modId: string) { return this.backend.readAllSettings(modId); } public setModSettings(modId: string, settings: ModSettings) { this.backend.writeAllSettings(modId, settings); } public enableMod(modId: string, enable: boolean) { this.backend.writeConfigField(modId, 'Disabled', enable ? 0 : 1); } public enableLogging(modId: string, enable: boolean) { this.backend.writeConfigField(modId, 'LoggingEnabled', enable ? 1 : 0); } public deleteMod(modId: string) { this.backend.deleteConfig(modId); } public changeModId(modIdFrom: string, modIdTo: string) { this.backend.renameConfig(modIdFrom, modIdTo); } } export class ModConfigUtilsPortable extends ModConfigUtilsBase { public constructor(appDataPath: string) { super(new IniStorageBackend(appDataPath)); } } export class ModConfigUtilsNonPortable extends ModConfigUtilsBase { public constructor(regKey: reg.HKEY, regSubKey: string, appDataPath: string) { super(new RegistryStorageBackend(regKey, regSubKey, appDataPath)); } } ================================================ FILE: src/vscode-windhawk/src/utils/modFilesUtils.ts ================================================ import * as fs from 'fs'; import * as https from 'https'; import fetch from 'node-fetch'; import * as path from 'path'; import * as semver from 'semver'; type ArchitectureSubfolder = '32' | '64' | 'arm64'; export default class ModFilesUtils { private engineModsPath: string; private arm64Enabled: boolean; private currentWindhawkVersion: semver.SemVer | null; public constructor(appDataPath: string, arm64Enabled: boolean, currentWindhawkVersion: semver.SemVer | null) { this.engineModsPath = path.join(appDataPath, 'Engine', 'Mods'); this.arm64Enabled = arm64Enabled; this.currentWindhawkVersion = currentWindhawkVersion; } /** * Converts metadata architecture strings to DLL subfolders. * Handles special cases like x86-64 expanding to both 64 and arm64 when ARM64 is enabled. */ private subfoldersFromArchitectures(architectures: string[]): Set { const subfolders = new Set(); // Default to x86 and x86-64 if no architectures specified const archsToProcess = (architectures.length > 0) ? architectures : ['x86', 'x86-64']; for (const arch of archsToProcess) { switch (arch) { case 'x86': subfolders.add('32'); break; case 'x86-64': // x86-64 means "64-bit" for compatibility, which could be // either x64 or ARM64 if (this.arm64Enabled) { subfolders.add('64'); subfolders.add('arm64'); } else { subfolders.add('64'); } break; case 'amd64': // Explicitly x64, not ARM64 subfolders.add('64'); break; case 'arm64': if (this.arm64Enabled) { subfolders.add('arm64'); } break; default: throw new Error(`Unsupported architecture: ${arch}`); } } return subfolders; } private deleteOldModFilesInFolder(modId: string, subfolder: ArchitectureSubfolder, currentDllName?: string) { const compiledModsPath = path.join(this.engineModsPath, subfolder); let compiledModsDir: fs.Dir; try { compiledModsDir = fs.opendirSync(compiledModsPath); } catch (e: any) { // Ignore if directory doesn't exist. if (e.code !== 'ENOENT') { throw e; } return; } try { let compiledModsDirEntry: fs.Dirent | null; while ((compiledModsDirEntry = compiledModsDir.readSync()) !== null) { if (!compiledModsDirEntry.isFile()) { continue; } const filename = compiledModsDirEntry.name; if (currentDllName && filename === currentDllName) { continue; } if (!filename.startsWith(modId + '_') || !filename.endsWith('.dll')) { continue; } const filenamePart = filename.slice((modId + '_').length, -'.dll'.length); if (!filenamePart.match(/(^|_)[0-9]+$/)) { continue; } const compiledModPath = path.join(compiledModsPath, filename); try { fs.unlinkSync(compiledModPath); } catch (e) { // Ignore errors (file may be in use). } } } finally { compiledModsDir.closeSync(); } } public deleteOldModFiles(modId: string, architectures: string[], currentDllName?: string) { const subfolders = this.subfoldersFromArchitectures(architectures); for (const subfolder of subfolders) { this.deleteOldModFilesInFolder(modId, subfolder, currentDllName); } } public async downloadPrecompiledMod( modId: string, version: string, architectures: string[], modsUrl: string ): Promise<{ targetDllName: string }> { // Generate a unique DLL name const targetDllName = modId + '_' + version + '_' + randomIntFromInterval(100000, 999999) + '.dll'; const subfolders = this.subfoldersFromArchitectures(architectures); if (subfolders.size === 0) { throw new Error('The current architecture is not supported'); } // Collect URLs and target paths const downloads: Array<{ subfolder: ArchitectureSubfolder; url: string; targetPath: string }> = []; for (const subfolder of subfolders) { const url = `${modsUrl}${modId}/${version}_${subfolder}.dll`; const targetPath = path.join(this.engineModsPath, subfolder, targetDllName); downloads.push({ subfolder, url, targetPath }); } // Collect all URLs to fetch (DLLs + versions.json) const versionsJsonUrl = `${modsUrl}${modId}/versions.json`; const urlsToFetch = [...downloads.map(d => d.url), versionsJsonUrl]; // Create HTTP agent with keepAlive to reuse connections const agent = new https.Agent({ keepAlive: true }); // Fetch all URLs in parallel with shared agent const responses = await Promise.all(urlsToFetch.map(url => fetch(url, { agent }))); // Clean up the agent after all requests complete agent.destroy(); // Check all responses succeeded for (let i = 0; i < downloads.length; i++) { const response = responses[i]; if (!response.ok) { throw new Error(`Failed to download ${downloads[i].subfolder} DLL: ${response.statusText || response.status}`); } } // Check versions.json response const versionsJsonResponse = responses[responses.length - 1]; if (!versionsJsonResponse.ok) { throw new Error(`Failed to download versions.json: ${versionsJsonResponse.statusText || versionsJsonResponse.status}`); } // Check minimum Windhawk version requirement const versionsJsonText = await versionsJsonResponse.json(); const versionInfo = versionsJsonText.find((v: any) => v.version === version); const minWindhawkVersion = versionInfo?.minWindhawkVersion; if (minWindhawkVersion) { let currentVersion = this.currentWindhawkVersion; const requiredVersion = semver.coerce(minWindhawkVersion); if (currentVersion && requiredVersion && semver.lt(currentVersion, requiredVersion)) { throw new Error( `Mod version ${version} requires Windhawk ${minWindhawkVersion} or later, ` + `but current version is ${currentVersion.version}` ); } } try { // Write all DLL buffers const dllResponses = responses.slice(0, downloads.length); const buffers = await Promise.all(dllResponses.map(r => r.buffer())); for (let i = 0; i < buffers.length; i++) { fs.mkdirSync(path.dirname(downloads[i].targetPath), { recursive: true }); fs.writeFileSync(downloads[i].targetPath, buffers[i]); } } catch (e: any) { // Clean up any partially downloaded files for (const cleanupSubfolder of subfolders) { const cleanupPath = path.join(this.engineModsPath, cleanupSubfolder, targetDllName); try { if (fs.existsSync(cleanupPath)) { fs.unlinkSync(cleanupPath); } } catch (cleanupError) { // Ignore cleanup errors } } throw e; } return { targetDllName }; } public deleteModFiles(modId: string) { // Delete all files for all architectures const allSubfolders: ArchitectureSubfolder[] = ['32', '64']; if (this.arm64Enabled) { allSubfolders.push('arm64'); } for (const subfolder of allSubfolders) { this.deleteOldModFilesInFolder(modId, subfolder); } } } // https://stackoverflow.com/a/7228322 // min and max included function randomIntFromInterval(min: number, max: number) { return Math.floor(Math.random() * (max - min + 1) + min); } ================================================ FILE: src/vscode-windhawk/src/utils/modSourceUtils.ts ================================================ import * as fs from 'fs'; import * as yaml from 'js-yaml'; import * as jsonschema from 'jsonschema'; import * as path from 'path'; import { InitialSettingItem, InitialSettings, InitialSettingsValue } from '../webviewIPCMessages'; const modMetadataParams = { singleValue: [ 'id', 'version', 'github', 'twitter', 'homepage', 'compilerOptions', 'license', 'donateUrl', ], singleValueLocalizable: [ 'name', 'description', 'author', ], multiValue: [ 'include', 'exclude', 'architecture', ], } as const; type ModMetadataParamsSingleValue = typeof modMetadataParams.singleValue[number]; type ModMetadataParamsSingleValueLocalizable = typeof modMetadataParams.singleValueLocalizable[number]; type ModMetadataParamsMultiValue = typeof modMetadataParams.multiValue[number]; function isModMetadataParamsSingleValue(k: string): k is ModMetadataParamsSingleValue { return modMetadataParams.singleValue.includes(k as any); } function isModMetadataParamsSingleValueLocalizable(k: string): k is ModMetadataParamsSingleValueLocalizable { return modMetadataParams.singleValueLocalizable.includes(k as any); } function isModMetadataParamsMultiValue(k: string): k is ModMetadataParamsMultiValue { return modMetadataParams.multiValue.includes(k as any); } type ModMetadata = Partial< Record & Record & Record >; export default class ModSourceUtils { private modsSourcePath: string; public constructor(appDataPath: string) { this.modsSourcePath = path.join(appDataPath, 'ModsSource'); } private getModSourcePath(modId: string) { return path.join(this.modsSourcePath, modId + '.wh.cpp'); } private getBestLanguageMatch(matchLanguage: string, candidates: { language: string | null, value: string }[]) { const languages = candidates.map(x => x.language && x.language.toLowerCase()); let iterLanguage = matchLanguage; let foundIndex; for (; ;) { // Exact match. foundIndex = languages.indexOf(iterLanguage); if (foundIndex !== -1) { return candidates[foundIndex]; } // A more specific language. foundIndex = languages.findIndex(language => language && language.startsWith(iterLanguage)); if (foundIndex !== -1) { return candidates[foundIndex]; } if (!iterLanguage.includes('-')) { break; } iterLanguage = iterLanguage.replace(/-[^-]*$/, ''); } // No language. foundIndex = languages.indexOf(null); if (foundIndex !== -1) { return candidates[foundIndex]; } // No matches of any kind, return the first item. return candidates[0]; } private extractMetadataRaw(modSource: string) { const metadataBlockMatch = modSource.match(/^\/\/[ \t]+==WindhawkMod==[ \t]*$([\s\S]+?)^\/\/[ \t]+==\/WindhawkMod==[ \t]*$/m); if (!metadataBlockMatch) { throw new Error('Couldn\'t find a metadata block in the source code'); } const metadataBlock = metadataBlockMatch[1]; const result: Record = {}; for (const line of metadataBlock.split('\n')) { const lineTrimmed = line.trimEnd(); if (lineTrimmed === '') { continue; } const match = lineTrimmed.match(/^\/\/[ \t]+@(_?[a-zA-Z]+)(?::([a-z]{2}(?:-[A-Z]{2})?))?[ \t]+(.*)$/); if (!match) { const lineTruncated = lineTrimmed.length > 20 ? (lineTrimmed.slice(0, 17) + '...') : lineTrimmed; throw new Error('Couldn\'t parse metadata line: ' + lineTruncated); } const key = match[1]; const language = match[2] as string | undefined; const value = match[3]; result[key] = result[key] ?? []; result[key].push({ language: language ?? null, value }); } return result; } private validateMetadata(metadata: ModMetadata) { const modId = metadata.id; if (!modId) { throw new Error('Mod id must be specified in the source code'); } if (!modId.match(/^[0-9a-z-]+$/)) { throw new Error('Mod id must only contain the following characters: 0-9, a-z, and a hyphen (-)'); } const paths = { include: metadata.include, exclude: metadata.exclude }; for (const [category, pathsArray] of Object.entries(paths)) { for (const path of pathsArray || []) { if (path.match(/[/"<>|]/)) { throw new Error(`Mod ${category} path contains one of the forbidden characters: / " < > |`); } } } const supportedArchitecture = [ 'x86', 'x86-64', 'amd64', 'arm64' ]; for (const architecture of metadata.architecture || []) { if (!supportedArchitecture.includes(architecture)) { throw new Error(`Mod architecture must be one of ${supportedArchitecture.join(', ')}: ${architecture}`); } } } public extractMetadata(modSource: string, language: string) { const metadataRaw = this.extractMetadataRaw(modSource); const result: ModMetadata = {}; for (const [metadataKeyRaw, metadataValue] of Object.entries(metadataRaw)) { if (metadataValue.length === 0) { throw new Error(`Missing metadata parameter: ${metadataKeyRaw}`); } const metadataKey = metadataKeyRaw.replace(/^_/, ''); if (isModMetadataParamsSingleValueLocalizable(metadataKey)) { const languages = new Set(); for (const item of metadataValue) { if (languages.has(item.language)) { throw new Error(`Duplicate metadata parameter: ${metadataKey}` + (item.language !== null ? `:${item.language}` : '')); } languages.add(item.language); } result[metadataKey] = this.getBestLanguageMatch(language, metadataValue).value; } else if (isModMetadataParamsMultiValue(metadataKey)) { for (const item of metadataValue) { if (item.language !== null) { throw new Error(`Metadata parameter can't be localized: ${metadataKey}:${item.language}`); } } result[metadataKey] = metadataValue.map(x => x.value); } else if (isModMetadataParamsSingleValue(metadataKey)) { for (const item of metadataValue) { if (item.language !== null) { throw new Error(`Metadata parameter can't be localized: ${metadataKey}:${item.language}`); } } if (metadataValue.length > 1) { throw new Error(`Duplicate metadata parameter: ${metadataKey}`); } result[metadataKey] = metadataValue[0].value; } else if (metadataKeyRaw.startsWith('_')) { // Ignore for forward compatibility. } else { throw new Error(`Unsupported metadata parameter: ${metadataKey}`); } } this.validateMetadata(result); return result; } public appendToIdAndName(modSource: string, appendToId?: string, appendToName?: string) { // This function can be made more generic in the future, if necessary. const search = /(^\/\/[ \t]+==WindhawkMod==[ \t]*$)([\s\S]+?)(^\/\/[ \t]+==\/WindhawkMod==[ \t]*$)/m; return modSource.replace(search, (match, p1: string, p2: string, p3: string) => { let p2New = p2; if (appendToId) { p2New = p2New.replace(/^(\/\/[ \t]+@id[ \t]+)(.*?)([ \t]*)$/m, '$1$2' + appendToId.replace(/\$/g, '$$$$') + '$3'); } if (appendToName) { p2New = p2New.replace(/^(\/\/[ \t]+@name(?::(?:[a-z]{2}(?:-[A-Z]{2})?))?[ \t]+)(.*?)([ \t]*)$/mg, '$1$2' + appendToName.replace(/\$/g, '$$$$') + '$3'); } return p1 + p2New + p3; }); } public getMetadataOfInstalled(language: string, onLoadError: (modId: string, error: Error) => void) { const mods: Record = {}; let modsSourceDir: fs.Dir; try { modsSourceDir = fs.opendirSync(this.modsSourcePath); } catch (e) { // Ignore if file doesn't exist. if (e.code !== 'ENOENT') { throw e; } return mods; } try { let modsSourceDirEntry: fs.Dirent | null; while ((modsSourceDirEntry = modsSourceDir.readSync()) !== null) { if (modsSourceDirEntry.isFile() && modsSourceDirEntry.name.endsWith('.wh.cpp')) { const modId = modsSourceDirEntry.name.slice(0, -'.wh.cpp'.length); const modSourcePath = path.join(this.modsSourcePath, modsSourceDirEntry.name); try { const modSourceMetadata = this.extractMetadata(fs.readFileSync(modSourcePath, 'utf8'), language); mods[modId] = modSourceMetadata; } catch (e) { onLoadError(modId, e); } } } } finally { modsSourceDir.closeSync(); } return mods; } public getSource(modId: string) { const modSourcePath = this.getModSourcePath(modId); return fs.readFileSync(modSourcePath, 'utf8'); } public setSource(modId: string, modSource: string) { const modSourcePath = this.getModSourcePath(modId); fs.mkdirSync(path.dirname(modSourcePath), { recursive: true }); fs.writeFileSync(modSourcePath, modSource); } public doesSourceExist(modId: string) { const modSourcePath = this.getModSourcePath(modId); return fs.existsSync(modSourcePath); } public extractReadme(modSource: string) { const readmeBlockMatch = modSource.match(/^\/\/[ \t]+==WindhawkModReadme==[ \t]*$\s*\/\*\s*([\s\S]+?)\s*\*\/\s*^\/\/[ \t]+==\/WindhawkModReadme==[ \t]*$/m); if (readmeBlockMatch === null) { return null; } return readmeBlockMatch[1]; } private extractInitialSettingsRaw(modSource: string) { const settingsBlockMatch = modSource.match(/^\/\/[ \t]+==WindhawkModSettings==[ \t]*$\s*\/\*\s*([\s\S]+?)\s*\*\/\s*^\/\/[ \t]+==\/WindhawkModSettings==[ \t]*$/m); if (settingsBlockMatch === null) { return null; } const settings = yaml.load(settingsBlockMatch[1], { schema: yaml.JSON_SCHEMA }); if (!Array.isArray(settings)) { throw new Error('Failed to parse settings: not a valid YAML array'); } const schema = { "type": "array", "minItems": 1, "items": { "type": "object", "minProperties": 1, "additionalProperties": false, "patternProperties": { "^[0-9A-Za-z_-]+$": { "anyOf": [ { "type": "boolean" }, { "type": "number" }, { "type": "string" }, { "$ref": "#" }, { "type": "array", "minItems": 1, "anyOf": [ { "items": { "type": "number" } }, { "items": { "type": "string" } }, { "items": { "$ref": "#" } } ] } ] }, "^\\$(name|description)(:[a-z]{2}(-[A-Z]{2})?)?$": { "type": "string" }, "^\\$(options)(:[a-z]{2}(-[A-Z]{2})?)?$": { "type": "array", "minItems": 2, "items": { "type": "object", "minProperties": 1, "maxProperties": 1, "patternProperties": { "^.*$": { "type": "string" } } } } } } }; const validatorResult = jsonschema.validate(settings, schema); if (!validatorResult.valid) { throw new Error('Failed to parse settings: ' + validatorResult.toString()); } return settings as Record[]; } public extractInitialSettings(modSource: string, language: string): InitialSettings | null { const parseSettings = (settings: Record[]): InitialSettings => { return settings.map(parseSettingsValueAnnotated); }; const parseSettingsValueAnnotated = (value: Record): InitialSettingItem => { const actualParameters = Object.keys(value).filter(x => !x.startsWith('$')); if (actualParameters.length === 0) { throw new Error('Missing settings key'); } else if (actualParameters.length > 1) { throw new Error('More than one settings key'); } const actualParameter = actualParameters[0]; const metaParameters = Object.keys(value).filter(x => x.startsWith('$')); const result: Record = {}; for (const paramWithPrefix of metaParameters) { const param = paramWithPrefix.slice(1); // remove '$' const paramParts = param.split(':'); result[paramParts[0]] = result[paramParts[0]] ?? []; result[paramParts[0]].push({ language: paramParts[1] ?? null, value: value[paramWithPrefix] }); } for (const key of Object.keys(result)) { result[key] = this.getBestLanguageMatch(language, result[key]).value; } result.key = actualParameter; result.value = parseSettingsValue(value[actualParameter]); return result as InitialSettingItem; }; const parseSettingsValue = (value: any): InitialSettingsValue => { if (typeof value === 'boolean' || typeof value === 'number' || typeof value === 'string' || typeof value[0] === 'number' || typeof value[0] === 'string') { return value; } return Array.isArray(value[0]) ? value.map(parseSettings) : parseSettings(value); }; const settingsRaw = this.extractInitialSettingsRaw(modSource); if (!settingsRaw) { return null; } return parseSettings(settingsRaw); } public extractInitialSettingsForEngine(modSource: string) { const parseSettings = (settings: Record[], keyPrefix = '') => { for (const value of settings) { parseSettingsValueAnnotated(value, keyPrefix); } }; const parseSettingsValueAnnotated = (value: Record, keyPrefix = '') => { const actualParameters = Object.keys(value).filter(x => !x.startsWith('$')); if (actualParameters.length === 0) { throw new Error('Missing settings key'); } else if (actualParameters.length > 1) { throw new Error('More than one settings key'); } const actualParameter = actualParameters[0]; const key = (keyPrefix && (keyPrefix + '.')) + actualParameter; parseSettingsValue(value[actualParameter], key); }; const parseSettingsValue = (value: any, key: string) => { if (typeof value === 'boolean') { parsed[key] = value ? 1 : 0; return; } if (typeof value === 'number' || typeof value === 'string') { parsed[key] = value; return; } if (typeof value[0] === 'number' || typeof value[0] === 'string') { for (const [i, item] of value.entries()) { parseSettingsValue(item, `${key}[${i}]`); } return; } if (Array.isArray(value[0])) { for (const [i, item] of value.entries()) { parseSettings(item, `${key}[${i}]`); } } else { parseSettings(value, key); } }; const parsed: Record = {}; const settingsRaw = this.extractInitialSettingsRaw(modSource); if (!settingsRaw) { return null; } parseSettings(settingsRaw); return parsed; } public deleteSource(modId: string) { const modSourcePath = this.getModSourcePath(modId); try { fs.unlinkSync(modSourcePath); } catch (e) { // Ignore if file doesn't exist. if (e.code !== 'ENOENT') { throw e; } } } } ================================================ FILE: src/vscode-windhawk/src/utils/trayProgramUtils.ts ================================================ import * as child_process from 'child_process'; import * as path from 'path'; import * as vscode from 'vscode'; export default class TrayProgramUtils { private trayProgramPath: string; public constructor(appRootPath: string) { this.trayProgramPath = path.join(appRootPath, 'windhawk.exe'); } private getCleanProcessEnv() { // Return the process environment, but without any environment variables // that are specific to Electron or VSCode. This is because the tray // process might run another instance of VSCode, and these variables // might cause problems (e.g. ELECTRON_RUN_AS_NODE=1). const cleanEnv: NodeJS.ProcessEnv = {}; for (const [key, value] of Object.entries(process.env)) { if (!key.startsWith('ELECTRON_') && !key.startsWith('VSCODE_') && !key.startsWith('WINDHAWK_')) { cleanEnv[key] = value; } } return cleanEnv; } private runTrayProgramWithArgs(args: string[]) { try { const ps = child_process.spawn(this.trayProgramPath, args, { env: this.getCleanProcessEnv(), }); let gotError = false; ps.on('error', err => { //console.log('Oh no, the error: ' + err); gotError = true; vscode.window.showErrorMessage(err.message); }); ps.on('close', code => { //console.log(`ps process exited with code ${code}`); if (!gotError && code !== 0) { vscode.window.showWarningMessage('Communication with the Windhawk tray icon process failed, make sure it\'s running'); } }); } catch (e) { vscode.window.showErrorMessage(e.message); } } public postAppRestartBg() { this.runTrayProgramWithArgs([ '-restart-bg' ]); } public postNewUpdatesFound() { this.runTrayProgramWithArgs([ '-new-updates-found' ]); } public postAppSettingsChanged() { this.runTrayProgramWithArgs([ '-app-settings-changed' ]); } } ================================================ FILE: src/vscode-windhawk/src/utils/updateUtils.ts ================================================ import { spawn } from 'child_process'; import * as crypto from 'crypto'; import * as fs from 'fs'; import fetch from 'node-fetch'; import * as os from 'os'; import * as path from 'path'; export interface UpdateProgress { progress: number; // 0-100 } export interface UpdateCallbacks { onProgress: (data: UpdateProgress) => void; onInstalling: () => void; } export interface UpdateResult { succeeded: boolean; error?: string; } export class UpdateUtils { private _isUpdating = false; private _downloadAbortController: AbortController | null = null; private _tempFolderPath: string | null = null; private _tempInstallerPath: string | null = null; constructor( private readonly _isPortable: boolean, private readonly _appRootPath: string ) { } public isUpdating(): boolean { return this._isUpdating; } public async startUpdate(callbacks: UpdateCallbacks): Promise { if (this._isUpdating) { return { succeeded: false, error: 'Update is already in progress' }; } this._isUpdating = true; try { // Download the latest installer await this._downloadInstaller(callbacks); // Start installation callbacks.onInstalling(); await this._installUpdate(); // At this point, the installer was started successfully return { succeeded: true }; } catch (error: any) { return { succeeded: false, error: error.message || 'Unknown error occurred' }; } finally { this._cleanup(); } } public cancelUpdate(): boolean { if (!this._isUpdating || !this._downloadAbortController) { return false; } // Just abort the download. startUpdate() will handle cleanup. this._downloadAbortController.abort(); return true; } private async _downloadInstaller(callbacks: UpdateCallbacks): Promise { const installerUrl = 'https://github.com/ramensoftware/windhawk/releases/latest/download/windhawk_setup.exe'; this._downloadAbortController = new AbortController(); try { // Create a random subfolder inside os.tmpdir to avoid DLL hijacking const randomFolderName = `windhawk_update_${crypto.randomBytes(8).toString('hex')}`; this._tempFolderPath = path.join(os.tmpdir(), randomFolderName); fs.mkdirSync(this._tempFolderPath, { recursive: true }); this._tempInstallerPath = path.join(this._tempFolderPath, 'windhawk_setup.exe'); const response = await fetch(installerUrl, { signal: this._downloadAbortController.signal }); if (!response.ok) { this._downloadAbortController = null; throw new Error(`Failed to download update: ${response.statusText || response.status}`); } const totalSize = parseInt(response.headers.get('content-length') || '0', 10); let downloadedSize = 0; let lastReportedProgress = -1; const fileStream = fs.createWriteStream(this._tempInstallerPath); await new Promise((resolve, reject) => { if (!response.body) { reject(new Error('Response body is null')); return; } let hasError = false; response.body.on('data', (chunk: Buffer) => { downloadedSize += chunk.length; const progress = totalSize > 0 ? Math.floor((downloadedSize / totalSize) * 100) : 0; // Only report progress if it changed by at least 1% if (progress !== lastReportedProgress) { lastReportedProgress = progress; callbacks.onProgress({ progress }); } }); response.body.pipe(fileStream); fileStream.on('finish', () => { fileStream.close(); if (!hasError) { callbacks.onProgress({ progress: 100 }); resolve(); } }); fileStream.on('error', (error) => { hasError = true; reject(error); }); response.body.on('error', (error) => { hasError = true; fileStream.close(); reject(error); }); }); } finally { this._downloadAbortController = null; } } private async _installUpdate(): Promise { const tempInstallerPath = this._tempInstallerPath; if (!tempInstallerPath || !fs.existsSync(tempInstallerPath)) { throw new Error('Installer file not found'); } return new Promise((resolve, reject) => { let args: string[]; if (this._isPortable) { args = ['/PORTABLE', '/AUTO_UPDATE', '/LANG=1033', `/D=${this._appRootPath}`]; } else { args = ['/AUTO_UPDATE']; } // Run the installer with appropriate flags // The installer should handle restarting Windhawk const installerProcess = spawn(tempInstallerPath, args, { detached: true, stdio: 'ignore' }); installerProcess.on('error', (error) => { reject(new Error(`Failed to start installer: ${error.message}`)); }); // Wait for the process to actually spawn before resolving installerProcess.on('spawn', () => { // Unref so the parent process can exit installerProcess.unref(); // The installer will restart Windhawk, which will close this // extension, so we don't wait for the process to complete resolve(); }); }); } private _cleanup(): void { this._isUpdating = false; this._downloadAbortController = null; if (this._tempInstallerPath) { try { fs.unlinkSync(this._tempInstallerPath); } catch (error) { // Ignore cleanup errors } } if (this._tempFolderPath) { try { fs.rmdirSync(this._tempFolderPath); } catch (error) { // Ignore cleanup errors } } this._tempInstallerPath = null; this._tempFolderPath = null; } } ================================================ FILE: src/vscode-windhawk/src/utils/userProfileUtils.ts ================================================ import * as fs from 'fs'; import * as path from 'path'; type UserProfileType = { id?: string, os?: string, app: Partial<{ version: string, latestVersion: string }>, mods: Record | undefined> }; type onFileModified = (mtimeMs: number) => void; export class UserProfile { private userProfilePath: string; private userProfile: UserProfileType; private onFileModified?: onFileModified; public constructor(userProfilePath: string, onFileModified?: onFileModified) { this.userProfilePath = userProfilePath; this.onFileModified = onFileModified; let userProfileText: string | undefined; try { userProfileText = fs.readFileSync(userProfilePath, 'utf8'); } catch (e) { // Ignore if file doesn't exist. if (e.code !== 'ENOENT') { throw e; } } let userProfile: any = {}; if (userProfileText) { try { userProfile = JSON.parse(userProfileText); } catch (e) { // Ignore if file is invalid. } } userProfile.app = userProfile.app || {}; userProfile.mods = userProfile.mods || {}; this.userProfile = userProfile; } public getAppLatestVersion() { return this.userProfile.app.latestVersion ?? null; } public getModRating(modId: string) { return this.userProfile.mods[modId]?.rating ?? null; } public getModLatestVersion(modId: string) { return this.userProfile.mods[modId]?.latestVersion ?? null; } public setModVersion(modId: string, version: string, resetLatestVersion = true) { const mod = this.userProfile.mods[modId] || {}; mod.version = version; if (resetLatestVersion) { delete mod.latestVersion; } this.userProfile.mods[modId] = mod; } public setModDisabled(modId: string, disabled: boolean) { const mod = this.userProfile.mods[modId] || {}; if (disabled) { mod.disabled = true; } else { delete mod.disabled; } this.userProfile.mods[modId] = mod; } public setModRating(modId: string, rating: number) { const mod = this.userProfile.mods[modId] || {}; if (rating) { mod.rating = rating; } else { delete mod.rating; } this.userProfile.mods[modId] = mod; } public deleteMod(modId: string) { const mod = this.userProfile.mods[modId]; if (mod && mod.rating !== undefined) { // Keep rating but delete other properties. this.userProfile.mods[modId] = { rating: mod.rating }; } else { delete this.userProfile.mods[modId]; } } private isModDeleted(modId: string) { const mod = this.userProfile.mods[modId]; // Consider a mod deleted if it doesn't exist or only has a rating. return mod === undefined || (Object.keys(mod).length === 1 && mod.rating !== undefined); } public updateModDetails(modId: string, version: string, disabled: boolean) { const mod = this.userProfile.mods[modId] || {}; let updated = false; if (mod.version !== version) { mod.version = version; updated = true; } if ((mod.disabled ?? false) !== disabled) { mod.disabled = disabled; updated = true; } this.userProfile.mods[modId] = mod; return updated; } public cleanupRemovedMods(currentModIds: Set) { let updated = false; for (const modId of Object.keys(this.userProfile.mods)) { if (!currentModIds.has(modId) && !this.isModDeleted(modId)) { this.deleteMod(modId); updated = true; } } return updated; } public updateLatestVersions(appLatestVersion?: string, modLatestVersions?: Record) { let updated = false; if (appLatestVersion && this.userProfile.app.latestVersion !== appLatestVersion) { this.userProfile.app.latestVersion = appLatestVersion; updated = true; } for (const [modId, latestVersion] of Object.entries(modLatestVersions || {})) { if (this.isModDeleted(modId)) { continue; } const mod = this.userProfile.mods[modId]; if (mod && mod.latestVersion !== latestVersion) { mod.latestVersion = latestVersion; updated = true; } } return updated; } public write(asExternalUpdate = false) { fs.writeFileSync(this.userProfilePath, JSON.stringify(this.userProfile, null, 2)); if (!asExternalUpdate) { this.onFileModified?.(fs.statSync(this.userProfilePath).mtimeMs); } } } export default class UserProfileUtils { private userProfilePath: string; private lastModifiedByUserMtimeMs: number | null = null; public constructor(appDataPath: string) { this.userProfilePath = path.join(appDataPath, 'userprofile.json'); } public getFilePath() { return this.userProfilePath; } public read() { return new UserProfile(this.userProfilePath, mtimeMs => { this.lastModifiedByUserMtimeMs = mtimeMs; }); } public getLastModifiedByUserMtimeMs() { return this.lastModifiedByUserMtimeMs; } } ================================================ FILE: src/vscode-windhawk/src/webviewIPC.ts ================================================ import * as vscode from 'vscode'; import { CancelUpdateReplyData, CompileEditedModReplyData, CompileModReplyData, DeleteModReplyData, EnableEditedModLoggingReplyData, EnableEditedModReplyData, EnableModReplyData, ExitEditorModeReplyData, GetAppSettingsReplyData, GetFeaturedModsReplyData, GetInitialAppSettingsReplyData, GetInstalledModsReplyData, GetModConfigReplyData, GetModSettingsReplyData, GetModSourceDataReplyData, GetModVersionsReplyData, GetRepositoryModSourceDataReplyData, GetRepositoryModsReplyData, InstallModReplyData, SetEditedModDetailsData, SetEditedModIdData, SetModSettingsReplyData, SetNewAppSettingsData, SetNewModConfigData, StartUpdateReplyData, UpdateAppSettingsReplyData, UpdateDownloadProgressEventData, UpdateInstalledModsDetailsData, UpdateInstallingEventData, UpdateModConfigReplyData, UpdateModRatingReplyData } from './webviewIPCMessages'; // Message types: // * 'message' is a message from the webview to the extension. // * 'messageWithReply' is a message from the webview to the extension that expects a reply. // * 'reply' is a reply to a 'messageWithReply' message. // * 'event' is a message from the extension to the webview. type MessageType = 'message' | 'messageWithReply' | 'reply' | 'event'; type CommonMessageBase = { type: MessageType; command: string; data: Record; }; // type MessageRegular = CommonMessageBase & { // type: 'message'; // command: string; // data: Record; // }; // type MessageWithReply = CommonMessageBase & { // type: 'messageWithReply'; // command: string; // data: Record; // messageId: number; // }; type Reply = CommonMessageBase & { type: 'reply'; command: string; data: Record; messageId: number; }; type Event = CommonMessageBase & { type: 'event'; command: string; data: Record; }; //////////////////////////////////////////////////////////// // Events. export function setNewAppSettings(webview: vscode.Webview | undefined, data: SetNewAppSettingsData) { if (!webview) return; const msg: Event = { type: 'event', command: 'setNewAppSettings', data, }; webview.postMessage(msg); } export function updateInstalledModsDetails(webview: vscode.Webview | undefined, data: UpdateInstalledModsDetailsData) { if (!webview) return; const msg: Event = { type: 'event', command: 'updateInstalledModsDetails', data, }; webview.postMessage(msg); } export function setNewModConfig(webview: vscode.Webview | undefined, data: SetNewModConfigData) { if (!webview) return; const msg: Event = { type: 'event', command: 'setNewModConfig', data, }; webview.postMessage(msg); } export function editedModWasModified(webview: vscode.Webview | undefined) { if (!webview) return; const msg: Event = { type: 'event', command: 'editedModWasModified', data: {}, }; webview.postMessage(msg); } export function compileEditedModStart(webview: vscode.Webview | undefined) { if (!webview) return; const msg: Event = { type: 'event', command: 'compileEditedModStart', data: {}, }; webview.postMessage(msg); } export function setEditedModDetails(webview: vscode.Webview | undefined, data: SetEditedModDetailsData) { if (!webview) return; const msg: Event = { type: 'event', command: 'setEditedModDetails', data, }; webview.postMessage(msg); } export function setEditedModId(webview: vscode.Webview | undefined, data: SetEditedModIdData) { if (!webview) return; const msg: Event = { type: 'event', command: 'setEditedModId', data, }; webview.postMessage(msg); } export function updateDownloadProgress(webview: vscode.Webview | undefined, data: UpdateDownloadProgressEventData) { if (!webview) return; const msg: Event = { type: 'event', command: 'updateDownloadProgress', data, }; webview.postMessage(msg); } export function updateInstalling(webview: vscode.Webview | undefined, data: UpdateInstallingEventData) { if (!webview) return; const msg: Event = { type: 'event', command: 'updateInstalling', data, }; webview.postMessage(msg); } //////////////////////////////////////////////////////////// // Replies. export function getInitialAppSettingsReply( webview: vscode.Webview | undefined, messageId: number, data: GetInitialAppSettingsReplyData ) { if (!webview) return; const msg: Reply = { type: 'reply', command: 'getInitialAppSettings', messageId, data, }; webview.postMessage(msg); } export function getInstalledModsReply( webview: vscode.Webview | undefined, messageId: number, data: GetInstalledModsReplyData ) { if (!webview) return; const msg: Reply = { type: 'reply', command: 'getInstalledMods', messageId, data, }; webview.postMessage(msg); } export function getFeaturedModsReply( webview: vscode.Webview | undefined, messageId: number, data: GetFeaturedModsReplyData ) { if (!webview) return; const msg: Reply = { type: 'reply', command: 'getFeaturedMods', messageId, data, }; webview.postMessage(msg); } export function getRepositoryModsReply( webview: vscode.Webview | undefined, messageId: number, data: GetRepositoryModsReplyData ) { if (!webview) return; const msg: Reply = { type: 'reply', command: 'getRepositoryMods', messageId, data, }; webview.postMessage(msg); } export function getModSourceDataReply( webview: vscode.Webview | undefined, messageId: number, data: GetModSourceDataReplyData ) { if (!webview) return; const msg: Reply = { type: 'reply', command: 'getModSourceData', messageId, data, }; webview.postMessage(msg); } export function getRepositoryModSourceDataReply( webview: vscode.Webview | undefined, messageId: number, data: GetRepositoryModSourceDataReplyData ) { if (!webview) return; const msg: Reply = { type: 'reply', command: 'getRepositoryModSourceData', messageId, data, }; webview.postMessage(msg); } export function getModVersionsReply( webview: vscode.Webview | undefined, messageId: number, data: GetModVersionsReplyData ) { if (!webview) return; const msg: Reply = { type: 'reply', command: 'getModVersions', messageId, data, }; webview.postMessage(msg); } export function getModSettingsReply( webview: vscode.Webview | undefined, messageId: number, data: GetModSettingsReplyData ) { if (!webview) return; const msg: Reply = { type: 'reply', command: 'getModSettings', messageId, data, }; webview.postMessage(msg); } export function setModSettingsReply( webview: vscode.Webview | undefined, messageId: number, data: SetModSettingsReplyData ) { if (!webview) return; const msg: Reply = { type: 'reply', command: 'setModSettings', messageId, data, }; webview.postMessage(msg); } export function getModConfigReply( webview: vscode.Webview | undefined, messageId: number, data: GetModConfigReplyData ) { if (!webview) return; const msg: Reply = { type: 'reply', command: 'getModConfig', messageId, data, }; webview.postMessage(msg); } export function updateModConfigReply( webview: vscode.Webview | undefined, messageId: number, data: UpdateModConfigReplyData ) { if (!webview) return; const msg: Reply = { type: 'reply', command: 'updateModConfig', messageId, data, }; webview.postMessage(msg); } export function installModReply( webview: vscode.Webview | undefined, messageId: number, data: InstallModReplyData ) { if (!webview) return; const msg: Reply = { type: 'reply', command: 'installMod', messageId, data, }; webview.postMessage(msg); } export function compileModReply( webview: vscode.Webview | undefined, messageId: number, data: CompileModReplyData ) { if (!webview) return; const msg: Reply = { type: 'reply', command: 'compileMod', messageId, data, }; webview.postMessage(msg); } export function enableModReply( webview: vscode.Webview | undefined, messageId: number, data: EnableModReplyData ) { if (!webview) return; const msg: Reply = { type: 'reply', command: 'enableMod', messageId, data, }; webview.postMessage(msg); } export function deleteModReply( webview: vscode.Webview | undefined, messageId: number, data: DeleteModReplyData ) { if (!webview) return; const msg: Reply = { type: 'reply', command: 'deleteMod', messageId, data, }; webview.postMessage(msg); } export function updateModRatingReply( webview: vscode.Webview | undefined, messageId: number, data: UpdateModRatingReplyData ) { if (!webview) return; const msg: Reply = { type: 'reply', command: 'updateModRating', messageId, data, }; webview.postMessage(msg); } export function getAppSettingsReply( webview: vscode.Webview | undefined, messageId: number, data: GetAppSettingsReplyData ) { if (!webview) return; const msg: Reply = { type: 'reply', command: 'getAppSettings', messageId, data, }; webview.postMessage(msg); } export function updateAppSettingsReply( webview: vscode.Webview | undefined, messageId: number, data: UpdateAppSettingsReplyData ) { if (!webview) return; const msg: Reply = { type: 'reply', command: 'updateAppSettings', messageId, data, }; webview.postMessage(msg); } export function enableEditedModReply( webview: vscode.Webview | undefined, messageId: number, data: EnableEditedModReplyData ) { if (!webview) return; const msg: Reply = { type: 'reply', command: 'enableEditedMod', messageId, data, }; webview.postMessage(msg); } export function enableEditedModLoggingReply( webview: vscode.Webview | undefined, messageId: number, data: EnableEditedModLoggingReplyData ) { if (!webview) return; const msg: Reply = { type: 'reply', command: 'enableEditedModLogging', messageId, data, }; webview.postMessage(msg); } export function compileEditedModReply( webview: vscode.Webview | undefined, messageId: number, data: CompileEditedModReplyData ) { if (!webview) return; const msg: Reply = { type: 'reply', command: 'compileEditedMod', messageId, data, }; webview.postMessage(msg); } export function exitEditorModeReply( webview: vscode.Webview | undefined, messageId: number, data: ExitEditorModeReplyData ) { if (!webview) return; const msg: Reply = { type: 'reply', command: 'exitEditorMode', messageId, data, }; webview.postMessage(msg); } export function startUpdateReply( webview: vscode.Webview | undefined, messageId: number, data: StartUpdateReplyData ) { if (!webview) return; const msg: Reply = { type: 'reply', command: 'startUpdate', messageId, data, }; webview.postMessage(msg); } export function cancelUpdateReply( webview: vscode.Webview | undefined, messageId: number, data: CancelUpdateReplyData ) { if (!webview) return; const msg: Reply = { type: 'reply', command: 'cancelUpdate', messageId, data, }; webview.postMessage(msg); } ================================================ FILE: src/vscode-windhawk/src/webviewIPCMessages.ts ================================================ // Message types: // * 'message' is a message from the webview to the extension. // * 'messageWithReply' is a message from the webview to the extension that expects a reply. // * 'reply' is a reply to a 'messageWithReply' message. // * 'event' is a message from the extension to the webview. export type webviewIPCMessageType = | 'message' | 'messageWithReply' | 'reply' | 'event'; export type webviewIPCMessageCommon = { type: webviewIPCMessageType; command: string; data: Record; }; export type webviewIPCMessage = webviewIPCMessageCommon & { type: 'message'; command: string; data: Record; }; export type webviewIPCMessageWithReply = webviewIPCMessageCommon & { type: 'messageWithReply'; command: string; data: Record; messageId: number; }; export type webviewIPCReply = webviewIPCMessageCommon & { type: 'reply'; command: string; data: Record; messageId: number; }; export type webviewIPCEvent = webviewIPCMessageCommon & { type: 'event'; command: string; data: Record; }; export type webviewIPCMessageAny = | webviewIPCMessage | webviewIPCMessageWithReply | webviewIPCReply | webviewIPCEvent; //////////////////////////////////////////////////////////// // Types. export type NoData = Record; export type ModConfig = { // libraryFileName: string; disabled: boolean; loggingEnabled: boolean; debugLoggingEnabled: boolean; include: string[]; exclude: string[]; includeCustom: string[]; excludeCustom: string[]; includeExcludeCustomOnly: boolean; patternsMatchCriticalSystemProcesses: boolean; architecture: string[]; version: string; }; export type AppSettings = { language: string; disableUpdateCheck: boolean; disableRunUIScheduledTask: boolean | null; devModeOptOut: boolean; devModeUsedAtLeastOnce: boolean; hideTrayIcon: boolean; alwaysCompileModsLocally: boolean; dontAutoShowToolkit: boolean; modTasksDialogDelay: number; safeMode: boolean; loggingVerbosity: number; engine: { loggingVerbosity: number; include: string[]; exclude: string[]; injectIntoCriticalProcesses: boolean; injectIntoIncompatiblePrograms: boolean; injectIntoGames: boolean; }; }; export type ModMetadata = Partial<{ version: string; // id: string; github: string; twitter: string; homepage: string; compilerOptions: string; license: string; donateUrl: string; name: string; description: string; author: string; include: string[]; exclude: string[]; architecture: string[]; }>; export type RepositoryDetails = { users: number; rating: number; // ratingUsers: number; ratingBreakdown: number[]; defaultSorting: number; published: number; updated: number; }; export type AppUISettings = { language: string; devModeOptOut: boolean; devModeUsedAtLeastOnce: boolean; loggingEnabled: boolean; updateIsAvailable: boolean; safeMode: boolean; }; export type InitialSettingsValue = | boolean | number | string | InitialSettings | InitialSettingsArrayValue; export type InitialSettingsArrayValue = number[] | string[] | InitialSettings[]; export type InitialSettingItem = { key: string; value: InitialSettingsValue; name?: string; description?: string; options?: Record[]; }; export type InitialSettings = InitialSettingItem[]; //////////////////////////////////////////////////////////// // Messages. export type EditModData = { modId: string; }; export type ForkModData = { modId: string; modSource?: string; }; //////////////////////////////////////////////////////////// // Messages with replies. export type GetInitialAppSettingsReplyData = { appUISettings: Partial; }; export type InstallModData = { modId: string; modSource: string; disabled?: boolean; }; export type InstallModReplyData = { modId: string; installedModDetails: { metadata: ModMetadata; config: ModConfig; } | null; }; export type CompileModData = { modId: string; disabled?: boolean; }; export type CompileModReplyData = { modId: string; compiledModDetails: { metadata: ModMetadata; config: ModConfig; } | null; }; export type EnableModData = { modId: string; enable: boolean; }; export type EnableModReplyData = { modId: string; enabled: boolean; succeeded: boolean; }; export type DeleteModData = { modId: string; }; export type DeleteModReplyData = { modId: string; succeeded: boolean; }; export type UpdateModRatingData = { modId: string; rating: number; }; export type UpdateModRatingReplyData = { modId: string; rating: number; succeeded: boolean; }; export type GetInstalledModsReplyData = { installedMods: Record< string, { metadata: ModMetadata | null; config: ModConfig | null; updateAvailable: boolean; userRating: number; } >; }; export type GetFeaturedModsReplyData = { featuredMods: Record< string, { metadata: ModMetadata; details: RepositoryDetails; } > | null; }; export type GetModSourceDataData = { modId: string; }; export type GetModSourceDataReplyData = { modId: string; data: { source: string | null; metadata: ModMetadata | null; readme: string | null; initialSettings: InitialSettings | null; }; }; export type GetRepositoryModSourceDataData = { modId: string; version?: string; }; export type GetRepositoryModSourceDataReplyData = { modId: string; version?: string; data: { source: string | null; metadata: ModMetadata | null; readme: string | null; initialSettings: InitialSettings | null; }; }; export type GetModVersionsData = { modId: string; }; export type GetModVersionsReplyData = { modId: string; versions: { version: string; timestamp: number; isPreRelease: boolean; }[]; }; export type GetAppSettingsReplyData = { appSettings: Partial; }; export type UpdateAppSettingsData = { appSettings: Partial; }; export type UpdateAppSettingsReplyData = { appSettings: Partial; succeeded: boolean; }; export type GetModSettingsData = { modId: string; }; export type GetModSettingsReplyData = { modId: string; settings: Record; }; export type SetModSettingsData = { modId: string; settings: Record; }; export type SetModSettingsReplyData = { modId: string; succeeded: boolean; }; export type GetModConfigData = { modId: string; }; export type GetModConfigReplyData = { modId: string; config: ModConfig | null; }; export type UpdateModConfigData = { modId: string; config: Partial; }; export type UpdateModConfigReplyData = { modId: string; succeeded: boolean; }; export type GetRepositoryModsReplyData = { mods: Record< string, { repository: { metadata: ModMetadata; details: RepositoryDetails; featured?: boolean; }; installed?: { metadata: ModMetadata | null; config: ModConfig | null; userRating: number; }; } > | null; }; export type StartUpdateReplyData = { succeeded: boolean; error?: string; }; export type CancelUpdateReplyData = { succeeded: boolean; }; export type EnableEditedModData = { enable: boolean; }; export type EnableEditedModReplyData = { enabled: boolean; succeeded: boolean; }; export type EnableEditedModLoggingData = { enable: boolean; }; export type EnableEditedModLoggingReplyData = { enabled: boolean; succeeded: boolean; }; export type CompileEditedModData = { disabled: boolean; loggingEnabled: boolean; }; export type CompileEditedModReplyData = { succeeded: boolean; clearModified: boolean; }; export type ExitEditorModeData = { saveToDrafts: boolean; }; export type ExitEditorModeReplyData = { succeeded: boolean; }; //////////////////////////////////////////////////////////// // Events. export type SetNewAppSettingsData = { appUISettings: Partial; }; export type UpdateDownloadProgressEventData = { progress: number; // 0-100 }; export type UpdateInstallingEventData = NoData; export type UpdateInstalledModsDetailsData = { details: Record< string, { updateAvailable: boolean; userRating: number; } >; }; export type SetNewModConfigData = { modId: string, config: Partial }; export type SetEditedModIdData = { modId: string; }; export type SetEditedModDetailsData = { modId: string; modDetails: ModConfig | null; modWasModified: boolean; }; ================================================ FILE: src/vscode-windhawk/syntaxes/cpp.injection.json ================================================ { "scopeName": "source.cpp.windhawk", "injectionSelector": "L:source.cpp", "patterns": [ { "include": "#mod-info-wrapper" }, { "include": "#yaml-settings-wrapper" }, { "include": "#markdown-readme-wrapper" } ], "repository": { "mod-info-wrapper": { "name": "text.windhawk-mod-info-wrapper", "begin": "^//[ \t]+==WindhawkMod==[ \t]*$", "end": "^//[ \t]+==/WindhawkMod==[ \t]*$", "beginCaptures": { "0": { "name": "comment.line.double-slash.cpp" } }, "endCaptures": { "0": { "name": "comment.line.double-slash.cpp" } }, "patterns": [ { "include": "#mod-info" } ] }, "mod-info": { "name": "text.windhawk-mod-info", "match": "\\/\\/[ \t]+(@(?:id|version|github|twitter|homepage|compilerOptions|license|donateUrl|name|description|author|include|exclude|architecture)(?::([a-z]{2}(?:-[A-Z]{2})?))?)\\b(.*)", "captures": { "0": { "name": "comment.line.double-slash.cpp" }, "1": { "name": "storage.type.class.doxygen.cpp" } } }, "yaml-settings-wrapper": { "name": "text.yaml.windhawk-settings-wrapper", "begin": "^//[ \t]+==WindhawkModSettings==[ \t]*$", "end": "^//[ \t]+==/WindhawkModSettings==[ \t]*$", "beginCaptures": { "0": { "name": "comment.line.double-slash.cpp" } }, "endCaptures": { "0": { "name": "comment.line.double-slash.cpp" } }, "patterns": [ { "include": "#yaml-settings" } ] }, "yaml-settings": { "name": "text.yaml.windhawk-settings", "begin": ".*(/\\*).*", "end": ".*(\\*/).*", "beginCaptures": { "1": { "name": "comment.block.cpp" } }, "endCaptures": { "1": { "name": "comment.block.cpp" } }, "contentName": "meta.embedded.block.yaml", "patterns": [ { "include": "source.yaml" } ] }, "markdown-readme-wrapper": { "name": "text.markdown.windhawk-readme-wrapper", "begin": "^//[ \t]+==WindhawkModReadme==[ \t]*$", "end": "^//[ \t]+==/WindhawkModReadme==[ \t]*$", "beginCaptures": { "0": { "name": "comment.line.double-slash.cpp" } }, "endCaptures": { "0": { "name": "comment.line.double-slash.cpp" } }, "patterns": [ { "include": "#markdown-readme" } ] }, "markdown-readme": { "name": "text.markdown.windhawk-readme", "begin": ".*(/\\*).*", "end": ".*(\\*/).*", "beginCaptures": { "1": { "name": "comment.block.cpp" } }, "endCaptures": { "1": { "name": "comment.block.cpp" } }, "contentName": "meta.embedded.block.markdown", "patterns": [ { "include": "text.html.markdown" } ] } } } ================================================ FILE: src/vscode-windhawk/tsconfig.json ================================================ { "compilerOptions": { "module": "commonjs", "target": "es2019", "lib": ["ES2019"], "outDir": "out", "sourceMap": true, "strict": true, "rootDir": "src", "useUnknownInCatchVariables": false, "moduleResolution":"node" }, "include": [ "src/**/*.ts", "./node_modules/vscode/vscode.d.ts", "./node_modules/vscode/lib/*", ] } ================================================ FILE: src/vscode-windhawk/webpack.config.js ================================================ /*--------------------------------------------------------------------------------------------- * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ //@ts-check 'use strict'; const path = require('path'); const CopyWebpackPlugin = require('copy-webpack-plugin'); /**@type {import('webpack').Configuration}*/ const config = { target: 'node', // vscode extensions run in a Node.js-context 📖 -> https://webpack.js.org/configuration/node/ entry: './src/extension.ts', // the entry point of this extension, 📖 -> https://webpack.js.org/configuration/entry-context/ output: { // the bundle is stored in the 'dist' folder (check package.json), 📖 -> https://webpack.js.org/configuration/output/ path: path.resolve(__dirname, 'dist'), filename: 'extension.js', libraryTarget: "commonjs2", devtoolModuleFilenameTemplate: "../[resource-path]", }, devtool: 'source-map', externals: { vscode: "commonjs vscode" // the vscode-module is created on-the-fly and must be excluded. Add other modules that cannot be webpack'ed, 📖 -> https://webpack.js.org/configuration/externals/ }, resolve: { // support reading TypeScript and JavaScript files, 📖 -> https://github.com/TypeStrong/ts-loader extensions: ['.ts', '.js'], alias: { // Workaround for https://github.com/baudehlo/node-fs-ext/pull/104 './build/Release/fs-ext': './build/Release/fs-ext.node' } }, module: { rules: [{ test: /\.ts$/, exclude: /node_modules/, use: [{ loader: 'ts-loader', options: { compilerOptions: { "module": "es6" // override `tsconfig.json` so that TypeScript emits native JavaScript modules. } } }] }, { test: /\.node$/, loader: "node-loader", }] }, plugins: [ new CopyWebpackPlugin({ patterns: [ { from: 'node_modules/native-reg/prebuilds', to: '../prebuilds' } ] }) ] }; module.exports = config; ================================================ FILE: src/vscode-windhawk-ui/.editorconfig ================================================ # Editor configuration, see http://editorconfig.org root = true [*] charset = utf-8 indent_style = space indent_size = 2 insert_final_newline = true trim_trailing_whitespace = true [*.md] max_line_length = off trim_trailing_whitespace = false ================================================ FILE: src/vscode-windhawk-ui/.eslintrc.json ================================================ { "root": true, "ignorePatterns": ["**/*"], "plugins": ["@nrwl/nx"], "overrides": [ { "files": ["*.ts", "*.tsx", "*.js", "*.jsx"], "rules": { "@nrwl/nx/enforce-module-boundaries": [ "error", { "enforceBuildableLibDependency": true, "allow": [], "depConstraints": [ { "sourceTag": "*", "onlyDependOnLibsWithTags": ["*"] } ] } ] } }, { "files": ["*.ts", "*.tsx"], "extends": ["plugin:@nrwl/nx/typescript"], "rules": {} }, { "files": ["*.js", "*.jsx"], "extends": ["plugin:@nrwl/nx/javascript"], "rules": {} }, { "files": ["*.spec.ts", "*.spec.tsx", "*.spec.js", "*.spec.jsx"], "env": { "jest": true }, "rules": {} } ] } ================================================ FILE: src/vscode-windhawk-ui/.gitignore ================================================ # See http://help.github.com/ignore-files/ for more about ignoring files. # compiled output dist tmp /out-tsc # dependencies node_modules # IDEs and editors /.idea .project .classpath .c9/ *.launch .settings/ *.sublime-workspace # IDE - VSCode .vscode/* !.vscode/settings.json !.vscode/tasks.json !.vscode/launch.json !.vscode/extensions.json # misc /.sass-cache /connect.lock /coverage /libpeerconnection.log npm-debug.log yarn-error.log testem.log /typings # System Files .DS_Store Thumbs.db .nx/cache .nx/workspace-data ================================================ FILE: src/vscode-windhawk-ui/.prettierignore ================================================ # Add files here to ignore them from prettier formatting /dist /coverage ================================================ FILE: src/vscode-windhawk-ui/.prettierrc ================================================ { "singleQuote": true } ================================================ FILE: src/vscode-windhawk-ui/.vscode/extensions.json ================================================ { "recommendations": [ "nrwl.angular-console", "esbenp.prettier-vscode", "firsttris.vscode-jest-runner", "dbaeumer.vscode-eslint" ] } ================================================ FILE: src/vscode-windhawk-ui/apps/.gitkeep ================================================ ================================================ FILE: src/vscode-windhawk-ui/apps/vscode-windhawk-ui/.babelrc ================================================ { "presets": [ [ "@nrwl/react/babel", { "runtime": "automatic" } ] ], "plugins": [["styled-components", { "pure": true, "ssr": true }]] } ================================================ FILE: src/vscode-windhawk-ui/apps/vscode-windhawk-ui/.browserslistrc ================================================ # This file is used by: # 1. autoprefixer to adjust CSS to support the below specified browsers # 2. babel preset-env to adjust included polyfills # # For additional information regarding the format and rule options, please see: # https://github.com/browserslist/browserslist#queries # # If you need to support different browsers in production, you may tweak the list below. last 1 Chrome version last 1 Firefox version last 2 Edge major versions last 2 Safari major version last 2 iOS major versions Firefox ESR not IE 9-11 # For IE 9-11 support, remove 'not'. ================================================ FILE: src/vscode-windhawk-ui/apps/vscode-windhawk-ui/.eslintrc.json ================================================ { "extends": ["plugin:@nrwl/nx/react", "../../.eslintrc.json"], "ignorePatterns": ["!**/*"], "overrides": [ { "files": ["*.ts", "*.tsx", "*.js", "*.jsx"], "rules": {} }, { "files": ["*.ts", "*.tsx"], "rules": {} }, { "files": ["*.js", "*.jsx"], "rules": {} } ] } ================================================ FILE: src/vscode-windhawk-ui/apps/vscode-windhawk-ui/jest.config.ts ================================================ /* eslint-disable */ export default { displayName: 'vscode-windhawk-ui', preset: '../../jest.preset.js', transform: { '^(?!.*\\.(js|jsx|ts|tsx|css|json)$)': '@nrwl/react/plugins/jest', '^.+\\.[tj]sx?$': ['babel-jest', { presets: ['@nrwl/react/babel'] }], }, moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx'], coverageDirectory: '../../coverage/apps/vscode-windhawk-ui', }; ================================================ FILE: src/vscode-windhawk-ui/apps/vscode-windhawk-ui/project.json ================================================ { "name": "vscode-windhawk-ui", "$schema": "../../node_modules/nx/schemas/project-schema.json", "sourceRoot": "apps/vscode-windhawk-ui/src", "projectType": "application", "targets": { "build": { "executor": "@nrwl/webpack:webpack", "outputs": ["{options.outputPath}"], "defaultConfiguration": "production", "options": { "compiler": "babel", "outputPath": "dist/apps/vscode-windhawk-ui", "index": "apps/vscode-windhawk-ui/src/index.html", "main": "apps/vscode-windhawk-ui/src/main.tsx", "polyfills": "apps/vscode-windhawk-ui/src/polyfills.ts", "tsConfig": "apps/vscode-windhawk-ui/tsconfig.app.json", "assets": [ "apps/vscode-windhawk-ui/src/favicon.ico", "apps/vscode-windhawk-ui/src/locales" ], "styles": [], "scripts": [], "webpackConfig": "apps/vscode-windhawk-ui/webpack.config.js" }, "configurations": { "development": { "extractLicenses": false, "optimization": false, "sourceMap": true, "vendorChunk": true }, "production": { "fileReplacements": [ { "replace": "apps/vscode-windhawk-ui/src/environments/environment.ts", "with": "apps/vscode-windhawk-ui/src/environments/environment.prod.ts" } ], "optimization": true, "outputHashing": "all", "sourceMap": false, "namedChunks": false, "extractLicenses": true, "vendorChunk": false } } }, "serve": { "executor": "@nrwl/webpack:dev-server", "defaultConfiguration": "development", "options": { "buildTarget": "vscode-windhawk-ui:build", "hmr": true }, "configurations": { "development": { "buildTarget": "vscode-windhawk-ui:build:development" }, "production": { "buildTarget": "vscode-windhawk-ui:build:production", "hmr": false }, "e2e": { "buildTarget": "vscode-windhawk-ui:build:development", "port": 4201 } } }, "lint": { "executor": "@nrwl/linter:eslint", "outputs": ["{options.outputFile}"], "options": { "lintFilePatterns": ["apps/vscode-windhawk-ui/**/*.{ts,tsx,js,jsx}"] } }, "test": { "executor": "@nrwl/jest:jest", "outputs": ["{workspaceRoot}/coverage/{projectRoot}"], "options": { "jestConfig": "apps/vscode-windhawk-ui/jest.config.ts", "passWithNoTests": true } } }, "tags": [] } ================================================ FILE: src/vscode-windhawk-ui/apps/vscode-windhawk-ui/src/app/app.css ================================================ /* stylelint-disable at-rule-empty-line-before,at-rule-name-space-after,at-rule-no-unknown */ /* stylelint-disable no-duplicate-selectors */ /* stylelint-disable */ /* stylelint-disable declaration-bang-space-before,no-duplicate-selectors,string-no-newline */ [class^=ant-]::-ms-clear, [class*= ant-]::-ms-clear, [class^=ant-] input::-ms-clear, [class*= ant-] input::-ms-clear, [class^=ant-] input::-ms-reveal, [class*= ant-] input::-ms-reveal { display: none; } /* stylelint-disable property-no-vendor-prefix, at-rule-no-vendor-prefix */ html, body { width: 100%; height: 100%; } input::-ms-clear, input::-ms-reveal { display: none; } *, *::before, *::after { box-sizing: border-box; } html { font-family: sans-serif; line-height: 1.15; -webkit-text-size-adjust: 100%; -ms-text-size-adjust: 100%; -ms-overflow-style: scrollbar; -webkit-tap-highlight-color: rgba(0, 0, 0, 0); } @-ms-viewport { width: device-width; } body { margin: 0; color: rgba(255, 255, 255, 0.85); font-size: 14px; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, 'Noto Sans', sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol', 'Noto Color Emoji'; font-variant: tabular-nums; line-height: 1.5715; background-color: var(--app-background-color); font-feature-settings: 'tnum'; } [tabindex='-1']:focus { outline: none !important; } hr { box-sizing: content-box; height: 0; overflow: visible; } h1, h2, h3, h4, h5, h6 { margin-top: 0; margin-bottom: 0.5em; color: rgba(255, 255, 255, 0.85); font-weight: 500; } p { margin-top: 0; margin-bottom: 1em; } abbr[title], abbr[data-original-title] { text-decoration: underline; text-decoration: underline dotted; border-bottom: 0; cursor: help; } address { margin-bottom: 1em; font-style: normal; line-height: inherit; } input[type='text'], input[type='password'], input[type='number'], textarea { -webkit-appearance: none; } ol, ul, dl { margin-top: 0; margin-bottom: 1em; } ol ol, ul ul, ol ul, ul ol { margin-bottom: 0; } dt { font-weight: 500; } dd { margin-bottom: 0.5em; margin-left: 0; } blockquote { margin: 0 0 1em; } dfn { font-style: italic; } b, strong { font-weight: bolder; } small { font-size: 80%; } sub, sup { position: relative; font-size: 75%; line-height: 0; vertical-align: baseline; } sub { bottom: -0.25em; } sup { top: -0.5em; } a { color: #177ddc; text-decoration: none; background-color: transparent; outline: none; cursor: pointer; transition: color 0.3s; -webkit-text-decoration-skip: objects; } a:hover { color: #165996; } a:active { color: #388ed3; } a:active, a:hover { text-decoration: none; outline: 0; } a:focus { text-decoration: none; outline: 0; } a[disabled] { color: rgba(255, 255, 255, 0.3); cursor: not-allowed; } pre, code, kbd, samp { font-size: 1em; font-family: 'SFMono-Regular', Consolas, 'Liberation Mono', Menlo, Courier, monospace; } pre { margin-top: 0; margin-bottom: 1em; overflow: auto; } figure { margin: 0 0 1em; } img { vertical-align: middle; border-style: none; } a, area, button, [role='button'], input:not([type='range']), label, select, summary, textarea { touch-action: manipulation; } table { border-collapse: collapse; } caption { padding-top: 0.75em; padding-bottom: 0.3em; color: rgba(255, 255, 255, 0.45); text-align: left; caption-side: bottom; } input, button, select, optgroup, textarea { margin: 0; color: inherit; font-size: inherit; font-family: inherit; line-height: inherit; } button, input { overflow: visible; } button, select { text-transform: none; } button, html [type="button"], [type="reset"], [type="submit"] { -webkit-appearance: button; } button::-moz-focus-inner, [type='button']::-moz-focus-inner, [type='reset']::-moz-focus-inner, [type='submit']::-moz-focus-inner { padding: 0; border-style: none; } input[type='radio'], input[type='checkbox'] { box-sizing: border-box; padding: 0; } input[type='date'], input[type='time'], input[type='datetime-local'], input[type='month'] { -webkit-appearance: listbox; } textarea { overflow: auto; resize: vertical; } fieldset { min-width: 0; margin: 0; padding: 0; border: 0; } legend { display: block; width: 100%; max-width: 100%; margin-bottom: 0.5em; padding: 0; color: inherit; font-size: 1.5em; line-height: inherit; white-space: normal; } progress { vertical-align: baseline; } [type='number']::-webkit-inner-spin-button, [type='number']::-webkit-outer-spin-button { height: auto; } [type='search'] { outline-offset: -2px; -webkit-appearance: none; } [type='search']::-webkit-search-cancel-button, [type='search']::-webkit-search-decoration { -webkit-appearance: none; } ::-webkit-file-upload-button { font: inherit; -webkit-appearance: button; } output { display: inline-block; } summary { display: list-item; } template { display: none; } [hidden] { display: none !important; } mark { padding: 0.2em; background-color: #2b2611; } ::selection { color: #fff; background: #177ddc; } .clearfix::before { display: table; content: ''; } .clearfix::after { display: table; clear: both; content: ''; } .anticon { display: inline-flex; align-items: center; color: inherit; font-style: normal; line-height: 0; text-align: center; text-transform: none; vertical-align: -0.125em; text-rendering: optimizelegibility; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; } .anticon > * { line-height: 1; } .anticon svg { display: inline-block; } .anticon::before { display: none; } .anticon .anticon-icon { display: block; } .anticon > .anticon { line-height: 0; vertical-align: 0; } .anticon[tabindex] { cursor: pointer; } .anticon-spin, .anticon-spin::before { display: inline-block; animation: loadingCircle 1s infinite linear; } .ant-fade-enter, .ant-fade-appear { animation-duration: 0.2s; animation-fill-mode: both; animation-play-state: paused; } .ant-fade-leave { animation-duration: 0.2s; animation-fill-mode: both; animation-play-state: paused; } .ant-fade-enter.ant-fade-enter-active, .ant-fade-appear.ant-fade-appear-active { animation-name: antFadeIn; animation-play-state: running; } .ant-fade-leave.ant-fade-leave-active { animation-name: antFadeOut; animation-play-state: running; pointer-events: none; } .ant-fade-enter, .ant-fade-appear { opacity: 0; animation-timing-function: linear; } .ant-fade-leave { animation-timing-function: linear; } @keyframes antFadeIn { 0% { opacity: 0; } 100% { opacity: 1; } } @keyframes antFadeOut { 0% { opacity: 1; } 100% { opacity: 0; } } .ant-move-up-enter, .ant-move-up-appear { animation-duration: 0.2s; animation-fill-mode: both; animation-play-state: paused; } .ant-move-up-leave { animation-duration: 0.2s; animation-fill-mode: both; animation-play-state: paused; } .ant-move-up-enter.ant-move-up-enter-active, .ant-move-up-appear.ant-move-up-appear-active { animation-name: antMoveUpIn; animation-play-state: running; } .ant-move-up-leave.ant-move-up-leave-active { animation-name: antMoveUpOut; animation-play-state: running; pointer-events: none; } .ant-move-up-enter, .ant-move-up-appear { opacity: 0; animation-timing-function: cubic-bezier(0.08, 0.82, 0.17, 1); } .ant-move-up-leave { animation-timing-function: cubic-bezier(0.6, 0.04, 0.98, 0.34); } .ant-move-down-enter, .ant-move-down-appear { animation-duration: 0.2s; animation-fill-mode: both; animation-play-state: paused; } .ant-move-down-leave { animation-duration: 0.2s; animation-fill-mode: both; animation-play-state: paused; } .ant-move-down-enter.ant-move-down-enter-active, .ant-move-down-appear.ant-move-down-appear-active { animation-name: antMoveDownIn; animation-play-state: running; } .ant-move-down-leave.ant-move-down-leave-active { animation-name: antMoveDownOut; animation-play-state: running; pointer-events: none; } .ant-move-down-enter, .ant-move-down-appear { opacity: 0; animation-timing-function: cubic-bezier(0.08, 0.82, 0.17, 1); } .ant-move-down-leave { animation-timing-function: cubic-bezier(0.6, 0.04, 0.98, 0.34); } .ant-move-left-enter, .ant-move-left-appear { animation-duration: 0.2s; animation-fill-mode: both; animation-play-state: paused; } .ant-move-left-leave { animation-duration: 0.2s; animation-fill-mode: both; animation-play-state: paused; } .ant-move-left-enter.ant-move-left-enter-active, .ant-move-left-appear.ant-move-left-appear-active { animation-name: antMoveLeftIn; animation-play-state: running; } .ant-move-left-leave.ant-move-left-leave-active { animation-name: antMoveLeftOut; animation-play-state: running; pointer-events: none; } .ant-move-left-enter, .ant-move-left-appear { opacity: 0; animation-timing-function: cubic-bezier(0.08, 0.82, 0.17, 1); } .ant-move-left-leave { animation-timing-function: cubic-bezier(0.6, 0.04, 0.98, 0.34); } .ant-move-right-enter, .ant-move-right-appear { animation-duration: 0.2s; animation-fill-mode: both; animation-play-state: paused; } .ant-move-right-leave { animation-duration: 0.2s; animation-fill-mode: both; animation-play-state: paused; } .ant-move-right-enter.ant-move-right-enter-active, .ant-move-right-appear.ant-move-right-appear-active { animation-name: antMoveRightIn; animation-play-state: running; } .ant-move-right-leave.ant-move-right-leave-active { animation-name: antMoveRightOut; animation-play-state: running; pointer-events: none; } .ant-move-right-enter, .ant-move-right-appear { opacity: 0; animation-timing-function: cubic-bezier(0.08, 0.82, 0.17, 1); } .ant-move-right-leave { animation-timing-function: cubic-bezier(0.6, 0.04, 0.98, 0.34); } @keyframes antMoveDownIn { 0% { transform: translateY(100%); transform-origin: 0 0; opacity: 0; } 100% { transform: translateY(0%); transform-origin: 0 0; opacity: 1; } } @keyframes antMoveDownOut { 0% { transform: translateY(0%); transform-origin: 0 0; opacity: 1; } 100% { transform: translateY(100%); transform-origin: 0 0; opacity: 0; } } @keyframes antMoveLeftIn { 0% { transform: translateX(-100%); transform-origin: 0 0; opacity: 0; } 100% { transform: translateX(0%); transform-origin: 0 0; opacity: 1; } } @keyframes antMoveLeftOut { 0% { transform: translateX(0%); transform-origin: 0 0; opacity: 1; } 100% { transform: translateX(-100%); transform-origin: 0 0; opacity: 0; } } @keyframes antMoveRightIn { 0% { transform: translateX(100%); transform-origin: 0 0; opacity: 0; } 100% { transform: translateX(0%); transform-origin: 0 0; opacity: 1; } } @keyframes antMoveRightOut { 0% { transform: translateX(0%); transform-origin: 0 0; opacity: 1; } 100% { transform: translateX(100%); transform-origin: 0 0; opacity: 0; } } @keyframes antMoveUpIn { 0% { transform: translateY(-100%); transform-origin: 0 0; opacity: 0; } 100% { transform: translateY(0%); transform-origin: 0 0; opacity: 1; } } @keyframes antMoveUpOut { 0% { transform: translateY(0%); transform-origin: 0 0; opacity: 1; } 100% { transform: translateY(-100%); transform-origin: 0 0; opacity: 0; } } @keyframes loadingCircle { 100% { transform: rotate(360deg); } } [ant-click-animating='true'], [ant-click-animating-without-extra-node='true'] { position: relative; } html { --antd-wave-shadow-color: #177ddc; --scroll-bar: 0; } [ant-click-animating-without-extra-node='true']::after, .ant-click-animating-node { position: absolute; top: 0; right: 0; bottom: 0; left: 0; display: block; border-radius: inherit; box-shadow: 0 0 0 0 #177ddc; box-shadow: 0 0 0 0 var(--antd-wave-shadow-color); opacity: 0.2; animation: fadeEffect 2s cubic-bezier(0.08, 0.82, 0.17, 1), waveEffect 0.4s cubic-bezier(0.08, 0.82, 0.17, 1); animation-fill-mode: forwards; content: ''; pointer-events: none; } @keyframes waveEffect { 100% { box-shadow: 0 0 0 #177ddc; box-shadow: 0 0 0 6px var(--antd-wave-shadow-color); } } @keyframes fadeEffect { 100% { opacity: 0; } } .ant-slide-up-enter, .ant-slide-up-appear { animation-duration: 0.2s; animation-fill-mode: both; animation-play-state: paused; } .ant-slide-up-leave { animation-duration: 0.2s; animation-fill-mode: both; animation-play-state: paused; } .ant-slide-up-enter.ant-slide-up-enter-active, .ant-slide-up-appear.ant-slide-up-appear-active { animation-name: antSlideUpIn; animation-play-state: running; } .ant-slide-up-leave.ant-slide-up-leave-active { animation-name: antSlideUpOut; animation-play-state: running; pointer-events: none; } .ant-slide-up-enter, .ant-slide-up-appear { transform: scale(0); transform-origin: 0% 0%; opacity: 0; animation-timing-function: cubic-bezier(0.23, 1, 0.32, 1); } .ant-slide-up-leave { animation-timing-function: cubic-bezier(0.755, 0.05, 0.855, 0.06); } .ant-slide-down-enter, .ant-slide-down-appear { animation-duration: 0.2s; animation-fill-mode: both; animation-play-state: paused; } .ant-slide-down-leave { animation-duration: 0.2s; animation-fill-mode: both; animation-play-state: paused; } .ant-slide-down-enter.ant-slide-down-enter-active, .ant-slide-down-appear.ant-slide-down-appear-active { animation-name: antSlideDownIn; animation-play-state: running; } .ant-slide-down-leave.ant-slide-down-leave-active { animation-name: antSlideDownOut; animation-play-state: running; pointer-events: none; } .ant-slide-down-enter, .ant-slide-down-appear { transform: scale(0); transform-origin: 0% 0%; opacity: 0; animation-timing-function: cubic-bezier(0.23, 1, 0.32, 1); } .ant-slide-down-leave { animation-timing-function: cubic-bezier(0.755, 0.05, 0.855, 0.06); } .ant-slide-left-enter, .ant-slide-left-appear { animation-duration: 0.2s; animation-fill-mode: both; animation-play-state: paused; } .ant-slide-left-leave { animation-duration: 0.2s; animation-fill-mode: both; animation-play-state: paused; } .ant-slide-left-enter.ant-slide-left-enter-active, .ant-slide-left-appear.ant-slide-left-appear-active { animation-name: antSlideLeftIn; animation-play-state: running; } .ant-slide-left-leave.ant-slide-left-leave-active { animation-name: antSlideLeftOut; animation-play-state: running; pointer-events: none; } .ant-slide-left-enter, .ant-slide-left-appear { transform: scale(0); transform-origin: 0% 0%; opacity: 0; animation-timing-function: cubic-bezier(0.23, 1, 0.32, 1); } .ant-slide-left-leave { animation-timing-function: cubic-bezier(0.755, 0.05, 0.855, 0.06); } .ant-slide-right-enter, .ant-slide-right-appear { animation-duration: 0.2s; animation-fill-mode: both; animation-play-state: paused; } .ant-slide-right-leave { animation-duration: 0.2s; animation-fill-mode: both; animation-play-state: paused; } .ant-slide-right-enter.ant-slide-right-enter-active, .ant-slide-right-appear.ant-slide-right-appear-active { animation-name: antSlideRightIn; animation-play-state: running; } .ant-slide-right-leave.ant-slide-right-leave-active { animation-name: antSlideRightOut; animation-play-state: running; pointer-events: none; } .ant-slide-right-enter, .ant-slide-right-appear { transform: scale(0); transform-origin: 0% 0%; opacity: 0; animation-timing-function: cubic-bezier(0.23, 1, 0.32, 1); } .ant-slide-right-leave { animation-timing-function: cubic-bezier(0.755, 0.05, 0.855, 0.06); } @keyframes antSlideUpIn { 0% { transform: scaleY(0.8); transform-origin: 0% 0%; opacity: 0; } 100% { transform: scaleY(1); transform-origin: 0% 0%; opacity: 1; } } @keyframes antSlideUpOut { 0% { transform: scaleY(1); transform-origin: 0% 0%; opacity: 1; } 100% { transform: scaleY(0.8); transform-origin: 0% 0%; opacity: 0; } } @keyframes antSlideDownIn { 0% { transform: scaleY(0.8); transform-origin: 100% 100%; opacity: 0; } 100% { transform: scaleY(1); transform-origin: 100% 100%; opacity: 1; } } @keyframes antSlideDownOut { 0% { transform: scaleY(1); transform-origin: 100% 100%; opacity: 1; } 100% { transform: scaleY(0.8); transform-origin: 100% 100%; opacity: 0; } } @keyframes antSlideLeftIn { 0% { transform: scaleX(0.8); transform-origin: 0% 0%; opacity: 0; } 100% { transform: scaleX(1); transform-origin: 0% 0%; opacity: 1; } } @keyframes antSlideLeftOut { 0% { transform: scaleX(1); transform-origin: 0% 0%; opacity: 1; } 100% { transform: scaleX(0.8); transform-origin: 0% 0%; opacity: 0; } } @keyframes antSlideRightIn { 0% { transform: scaleX(0.8); transform-origin: 100% 0%; opacity: 0; } 100% { transform: scaleX(1); transform-origin: 100% 0%; opacity: 1; } } @keyframes antSlideRightOut { 0% { transform: scaleX(1); transform-origin: 100% 0%; opacity: 1; } 100% { transform: scaleX(0.8); transform-origin: 100% 0%; opacity: 0; } } .ant-zoom-enter, .ant-zoom-appear { animation-duration: 0.2s; animation-fill-mode: both; animation-play-state: paused; } .ant-zoom-leave { animation-duration: 0.2s; animation-fill-mode: both; animation-play-state: paused; } .ant-zoom-enter.ant-zoom-enter-active, .ant-zoom-appear.ant-zoom-appear-active { animation-name: antZoomIn; animation-play-state: running; } .ant-zoom-leave.ant-zoom-leave-active { animation-name: antZoomOut; animation-play-state: running; pointer-events: none; } .ant-zoom-enter, .ant-zoom-appear { transform: scale(0); opacity: 0; animation-timing-function: cubic-bezier(0.08, 0.82, 0.17, 1); } .ant-zoom-enter-prepare, .ant-zoom-appear-prepare { transform: none; } .ant-zoom-leave { animation-timing-function: cubic-bezier(0.78, 0.14, 0.15, 0.86); } .ant-zoom-big-enter, .ant-zoom-big-appear { animation-duration: 0.2s; animation-fill-mode: both; animation-play-state: paused; } .ant-zoom-big-leave { animation-duration: 0.2s; animation-fill-mode: both; animation-play-state: paused; } .ant-zoom-big-enter.ant-zoom-big-enter-active, .ant-zoom-big-appear.ant-zoom-big-appear-active { animation-name: antZoomBigIn; animation-play-state: running; } .ant-zoom-big-leave.ant-zoom-big-leave-active { animation-name: antZoomBigOut; animation-play-state: running; pointer-events: none; } .ant-zoom-big-enter, .ant-zoom-big-appear { transform: scale(0); opacity: 0; animation-timing-function: cubic-bezier(0.08, 0.82, 0.17, 1); } .ant-zoom-big-enter-prepare, .ant-zoom-big-appear-prepare { transform: none; } .ant-zoom-big-leave { animation-timing-function: cubic-bezier(0.78, 0.14, 0.15, 0.86); } .ant-zoom-big-fast-enter, .ant-zoom-big-fast-appear { animation-duration: 0.1s; animation-fill-mode: both; animation-play-state: paused; } .ant-zoom-big-fast-leave { animation-duration: 0.1s; animation-fill-mode: both; animation-play-state: paused; } .ant-zoom-big-fast-enter.ant-zoom-big-fast-enter-active, .ant-zoom-big-fast-appear.ant-zoom-big-fast-appear-active { animation-name: antZoomBigIn; animation-play-state: running; } .ant-zoom-big-fast-leave.ant-zoom-big-fast-leave-active { animation-name: antZoomBigOut; animation-play-state: running; pointer-events: none; } .ant-zoom-big-fast-enter, .ant-zoom-big-fast-appear { transform: scale(0); opacity: 0; animation-timing-function: cubic-bezier(0.08, 0.82, 0.17, 1); } .ant-zoom-big-fast-enter-prepare, .ant-zoom-big-fast-appear-prepare { transform: none; } .ant-zoom-big-fast-leave { animation-timing-function: cubic-bezier(0.78, 0.14, 0.15, 0.86); } .ant-zoom-up-enter, .ant-zoom-up-appear { animation-duration: 0.2s; animation-fill-mode: both; animation-play-state: paused; } .ant-zoom-up-leave { animation-duration: 0.2s; animation-fill-mode: both; animation-play-state: paused; } .ant-zoom-up-enter.ant-zoom-up-enter-active, .ant-zoom-up-appear.ant-zoom-up-appear-active { animation-name: antZoomUpIn; animation-play-state: running; } .ant-zoom-up-leave.ant-zoom-up-leave-active { animation-name: antZoomUpOut; animation-play-state: running; pointer-events: none; } .ant-zoom-up-enter, .ant-zoom-up-appear { transform: scale(0); opacity: 0; animation-timing-function: cubic-bezier(0.08, 0.82, 0.17, 1); } .ant-zoom-up-enter-prepare, .ant-zoom-up-appear-prepare { transform: none; } .ant-zoom-up-leave { animation-timing-function: cubic-bezier(0.78, 0.14, 0.15, 0.86); } .ant-zoom-down-enter, .ant-zoom-down-appear { animation-duration: 0.2s; animation-fill-mode: both; animation-play-state: paused; } .ant-zoom-down-leave { animation-duration: 0.2s; animation-fill-mode: both; animation-play-state: paused; } .ant-zoom-down-enter.ant-zoom-down-enter-active, .ant-zoom-down-appear.ant-zoom-down-appear-active { animation-name: antZoomDownIn; animation-play-state: running; } .ant-zoom-down-leave.ant-zoom-down-leave-active { animation-name: antZoomDownOut; animation-play-state: running; pointer-events: none; } .ant-zoom-down-enter, .ant-zoom-down-appear { transform: scale(0); opacity: 0; animation-timing-function: cubic-bezier(0.08, 0.82, 0.17, 1); } .ant-zoom-down-enter-prepare, .ant-zoom-down-appear-prepare { transform: none; } .ant-zoom-down-leave { animation-timing-function: cubic-bezier(0.78, 0.14, 0.15, 0.86); } .ant-zoom-left-enter, .ant-zoom-left-appear { animation-duration: 0.2s; animation-fill-mode: both; animation-play-state: paused; } .ant-zoom-left-leave { animation-duration: 0.2s; animation-fill-mode: both; animation-play-state: paused; } .ant-zoom-left-enter.ant-zoom-left-enter-active, .ant-zoom-left-appear.ant-zoom-left-appear-active { animation-name: antZoomLeftIn; animation-play-state: running; } .ant-zoom-left-leave.ant-zoom-left-leave-active { animation-name: antZoomLeftOut; animation-play-state: running; pointer-events: none; } .ant-zoom-left-enter, .ant-zoom-left-appear { transform: scale(0); opacity: 0; animation-timing-function: cubic-bezier(0.08, 0.82, 0.17, 1); } .ant-zoom-left-enter-prepare, .ant-zoom-left-appear-prepare { transform: none; } .ant-zoom-left-leave { animation-timing-function: cubic-bezier(0.78, 0.14, 0.15, 0.86); } .ant-zoom-right-enter, .ant-zoom-right-appear { animation-duration: 0.2s; animation-fill-mode: both; animation-play-state: paused; } .ant-zoom-right-leave { animation-duration: 0.2s; animation-fill-mode: both; animation-play-state: paused; } .ant-zoom-right-enter.ant-zoom-right-enter-active, .ant-zoom-right-appear.ant-zoom-right-appear-active { animation-name: antZoomRightIn; animation-play-state: running; } .ant-zoom-right-leave.ant-zoom-right-leave-active { animation-name: antZoomRightOut; animation-play-state: running; pointer-events: none; } .ant-zoom-right-enter, .ant-zoom-right-appear { transform: scale(0); opacity: 0; animation-timing-function: cubic-bezier(0.08, 0.82, 0.17, 1); } .ant-zoom-right-enter-prepare, .ant-zoom-right-appear-prepare { transform: none; } .ant-zoom-right-leave { animation-timing-function: cubic-bezier(0.78, 0.14, 0.15, 0.86); } @keyframes antZoomIn { 0% { transform: scale(0.2); opacity: 0; } 100% { transform: scale(1); opacity: 1; } } @keyframes antZoomOut { 0% { transform: scale(1); } 100% { transform: scale(0.2); opacity: 0; } } @keyframes antZoomBigIn { 0% { transform: scale(0.8); opacity: 0; } 100% { transform: scale(1); opacity: 1; } } @keyframes antZoomBigOut { 0% { transform: scale(1); } 100% { transform: scale(0.8); opacity: 0; } } @keyframes antZoomUpIn { 0% { transform: scale(0.8); transform-origin: 50% 0%; opacity: 0; } 100% { transform: scale(1); transform-origin: 50% 0%; } } @keyframes antZoomUpOut { 0% { transform: scale(1); transform-origin: 50% 0%; } 100% { transform: scale(0.8); transform-origin: 50% 0%; opacity: 0; } } @keyframes antZoomLeftIn { 0% { transform: scale(0.8); transform-origin: 0% 50%; opacity: 0; } 100% { transform: scale(1); transform-origin: 0% 50%; } } @keyframes antZoomLeftOut { 0% { transform: scale(1); transform-origin: 0% 50%; } 100% { transform: scale(0.8); transform-origin: 0% 50%; opacity: 0; } } @keyframes antZoomRightIn { 0% { transform: scale(0.8); transform-origin: 100% 50%; opacity: 0; } 100% { transform: scale(1); transform-origin: 100% 50%; } } @keyframes antZoomRightOut { 0% { transform: scale(1); transform-origin: 100% 50%; } 100% { transform: scale(0.8); transform-origin: 100% 50%; opacity: 0; } } @keyframes antZoomDownIn { 0% { transform: scale(0.8); transform-origin: 50% 100%; opacity: 0; } 100% { transform: scale(1); transform-origin: 50% 100%; } } @keyframes antZoomDownOut { 0% { transform: scale(1); transform-origin: 50% 100%; } 100% { transform: scale(0.8); transform-origin: 50% 100%; opacity: 0; } } .ant-motion-collapse-legacy { overflow: hidden; } .ant-motion-collapse-legacy-active { transition: height 0.2s cubic-bezier(0.645, 0.045, 0.355, 1), opacity 0.2s cubic-bezier(0.645, 0.045, 0.355, 1) !important; } .ant-motion-collapse { overflow: hidden; transition: height 0.2s cubic-bezier(0.645, 0.045, 0.355, 1), opacity 0.2s cubic-bezier(0.645, 0.045, 0.355, 1) !important; } .ant-affix { position: fixed; z-index: 10; } .ant-alert { box-sizing: border-box; margin: 0; padding: 0; color: rgba(255, 255, 255, 0.85); font-size: 14px; font-variant: tabular-nums; line-height: 1.5715; list-style: none; font-feature-settings: 'tnum'; position: relative; display: flex; align-items: center; padding: 8px 15px; word-wrap: break-word; border-radius: 2px; } .ant-alert-content { flex: 1; min-width: 0; } .ant-alert-icon { margin-right: 8px; } .ant-alert-description { display: none; font-size: 14px; line-height: 22px; } .ant-alert-success { background-color: #162312; border: 1px solid #274916; } .ant-alert-success .ant-alert-icon { color: #49aa19; } .ant-alert-info { background-color: #111b26; border: 1px solid #153450; } .ant-alert-info .ant-alert-icon { color: #177ddc; } .ant-alert-warning { background-color: #2b2111; border: 1px solid #594214; } .ant-alert-warning .ant-alert-icon { color: #d89614; } .ant-alert-error { background-color: #2a1215; border: 1px solid #58181c; } .ant-alert-error .ant-alert-icon { color: #a61d24; } .ant-alert-error .ant-alert-description > pre { margin: 0; padding: 0; } .ant-alert-action { margin-left: 8px; } .ant-alert-close-icon { margin-left: 8px; padding: 0; overflow: hidden; font-size: 12px; line-height: 12px; background-color: transparent; border: none; outline: none; cursor: pointer; } .ant-alert-close-icon .anticon-close { color: rgba(255, 255, 255, 0.45); transition: color 0.3s; } .ant-alert-close-icon .anticon-close:hover { color: rgba(255, 255, 255, 0.75); } .ant-alert-close-text { color: rgba(255, 255, 255, 0.45); transition: color 0.3s; } .ant-alert-close-text:hover { color: rgba(255, 255, 255, 0.75); } .ant-alert-with-description { align-items: flex-start; padding: 15px 15px 15px 24px; } .ant-alert-with-description.ant-alert-no-icon { padding: 15px 15px; } .ant-alert-with-description .ant-alert-icon { margin-right: 15px; font-size: 24px; } .ant-alert-with-description .ant-alert-message { display: block; margin-bottom: 4px; color: rgba(255, 255, 255, 0.85); font-size: 16px; } .ant-alert-message { color: rgba(255, 255, 255, 0.85); } .ant-alert-with-description .ant-alert-description { display: block; } .ant-alert.ant-alert-motion-leave { overflow: hidden; opacity: 1; transition: max-height 0.3s cubic-bezier(0.78, 0.14, 0.15, 0.86), opacity 0.3s cubic-bezier(0.78, 0.14, 0.15, 0.86), padding-top 0.3s cubic-bezier(0.78, 0.14, 0.15, 0.86), padding-bottom 0.3s cubic-bezier(0.78, 0.14, 0.15, 0.86), margin-bottom 0.3s cubic-bezier(0.78, 0.14, 0.15, 0.86); } .ant-alert.ant-alert-motion-leave-active { max-height: 0; margin-bottom: 0 !important; padding-top: 0; padding-bottom: 0; opacity: 0; } .ant-alert-banner { margin-bottom: 0; border: 0; border-radius: 0; } .ant-alert.ant-alert-rtl { direction: rtl; } .ant-alert-rtl .ant-alert-icon { margin-right: auto; margin-left: 8px; } .ant-alert-rtl .ant-alert-action { margin-right: 8px; margin-left: auto; } .ant-alert-rtl .ant-alert-close-icon { margin-right: 8px; margin-left: auto; } .ant-alert-rtl.ant-alert-with-description { padding-right: 24px; padding-left: 15px; } .ant-alert-rtl.ant-alert-with-description .ant-alert-icon { margin-right: auto; margin-left: 15px; } .ant-anchor { box-sizing: border-box; margin: 0; padding: 0; color: rgba(255, 255, 255, 0.85); font-size: 14px; font-variant: tabular-nums; line-height: 1.5715; list-style: none; font-feature-settings: 'tnum'; position: relative; padding-left: 2px; } .ant-anchor-wrapper { margin-left: -4px; padding-left: 4px; overflow: auto; background-color: transparent; } .ant-anchor-ink { position: absolute; top: 0; left: 0; height: 100%; } .ant-anchor-ink::before { position: relative; display: block; width: 2px; height: 100%; margin: 0 auto; background-color: #303030; content: ' '; } .ant-anchor-ink-ball { position: absolute; left: 50%; display: none; width: 8px; height: 8px; background-color: #141414; border: 2px solid #177ddc; border-radius: 8px; transform: translateX(-50%); transition: top 0.3s ease-in-out; } .ant-anchor-ink-ball.ant-anchor-ink-ball-visible { display: inline-block; } .ant-anchor-fixed .ant-anchor-ink .ant-anchor-ink-ball { display: none; } .ant-anchor-link { padding: 4px 0 4px 16px; } .ant-anchor-link-title { position: relative; display: block; margin-bottom: 3px; overflow: hidden; color: rgba(255, 255, 255, 0.85); white-space: nowrap; text-overflow: ellipsis; transition: all 0.3s; } .ant-anchor-link-title:only-child { margin-bottom: 0; } .ant-anchor-link-active > .ant-anchor-link-title { color: #177ddc; } .ant-anchor-link .ant-anchor-link { padding-top: 2px; padding-bottom: 2px; } .ant-anchor-rtl { direction: rtl; } .ant-anchor-rtl.ant-anchor-wrapper { margin-right: -4px; margin-left: 0; padding-right: 4px; padding-left: 0; } .ant-anchor-rtl .ant-anchor-ink { right: 0; left: auto; } .ant-anchor-rtl .ant-anchor-ink-ball { right: 50%; left: 0; transform: translateX(50%); } .ant-anchor-rtl .ant-anchor-link { padding: 4px 16px 4px 0; } .ant-select-auto-complete { box-sizing: border-box; margin: 0; padding: 0; color: rgba(255, 255, 255, 0.85); font-size: 14px; font-variant: tabular-nums; line-height: 1.5715; list-style: none; font-feature-settings: 'tnum'; } .ant-select-auto-complete .ant-select-clear { right: 13px; } .ant-avatar { box-sizing: border-box; margin: 0; padding: 0; color: rgba(255, 255, 255, 0.85); font-size: 14px; font-variant: tabular-nums; line-height: 1.5715; list-style: none; font-feature-settings: 'tnum'; position: relative; display: inline-block; overflow: hidden; color: #fff; white-space: nowrap; text-align: center; vertical-align: middle; background: rgba(255, 255, 255, 0.3); width: 32px; height: 32px; line-height: 32px; border-radius: 50%; } .ant-avatar-image { background: transparent; } .ant-avatar .ant-image-img { display: block; } .ant-avatar-string { position: absolute; left: 50%; transform-origin: 0 center; } .ant-avatar.ant-avatar-icon { font-size: 18px; } .ant-avatar.ant-avatar-icon > .anticon { margin: 0; } .ant-avatar-lg { width: 40px; height: 40px; line-height: 40px; border-radius: 50%; } .ant-avatar-lg-string { position: absolute; left: 50%; transform-origin: 0 center; } .ant-avatar-lg.ant-avatar-icon { font-size: 24px; } .ant-avatar-lg.ant-avatar-icon > .anticon { margin: 0; } .ant-avatar-sm { width: 24px; height: 24px; line-height: 24px; border-radius: 50%; } .ant-avatar-sm-string { position: absolute; left: 50%; transform-origin: 0 center; } .ant-avatar-sm.ant-avatar-icon { font-size: 14px; } .ant-avatar-sm.ant-avatar-icon > .anticon { margin: 0; } .ant-avatar-square { border-radius: 2px; } .ant-avatar > img { display: block; width: 100%; height: 100%; object-fit: cover; } .ant-avatar-group { display: inline-flex; } .ant-avatar-group .ant-avatar { border: 1px solid #fff; } .ant-avatar-group .ant-avatar:not(:first-child) { margin-left: -8px; } .ant-avatar-group-popover .ant-avatar + .ant-avatar { margin-left: 3px; } .ant-avatar-group-rtl .ant-avatar:not(:first-child) { margin-right: -8px; margin-left: 0; } .ant-avatar-group-popover.ant-popover-rtl .ant-avatar + .ant-avatar { margin-right: 3px; margin-left: 0; } .ant-back-top { box-sizing: border-box; margin: 0; padding: 0; color: rgba(255, 255, 255, 0.85); font-size: 14px; font-variant: tabular-nums; line-height: 1.5715; list-style: none; font-feature-settings: 'tnum'; position: fixed; right: 100px; bottom: 50px; z-index: 10; width: 40px; height: 40px; cursor: pointer; } .ant-back-top:empty { display: none; } .ant-back-top-rtl { right: auto; left: 100px; direction: rtl; } .ant-back-top-content { width: 40px; height: 40px; overflow: hidden; color: #fff; text-align: center; background-color: rgba(255, 255, 255, 0.45); border-radius: 20px; transition: all 0.3s; } .ant-back-top-content:hover { background-color: rgba(255, 255, 255, 0.85); transition: all 0.3s; } .ant-back-top-icon { font-size: 24px; line-height: 40px; } @media screen and (max-width: 768px) { .ant-back-top { right: 60px; } .ant-back-top-rtl { right: auto; left: 60px; } } @media screen and (max-width: 480px) { .ant-back-top { right: 20px; } .ant-back-top-rtl { right: auto; left: 20px; } } .ant-badge { box-sizing: border-box; margin: 0; padding: 0; color: rgba(255, 255, 255, 0.85); font-size: 14px; font-variant: tabular-nums; line-height: 1.5715; list-style: none; font-feature-settings: 'tnum'; position: relative; display: inline-block; line-height: 1; } .ant-badge-count { z-index: auto; min-width: 20px; height: 20px; padding: 0 6px; color: #fff; font-weight: normal; font-size: 12px; line-height: 20px; white-space: nowrap; text-align: center; background: #a61d24; border-radius: 10px; box-shadow: 0 0 0 1px #141414; } .ant-badge-count a, .ant-badge-count a:hover { color: #fff; } .ant-badge-count-sm { min-width: 14px; height: 14px; padding: 0; font-size: 12px; line-height: 14px; border-radius: 7px; } .ant-badge-multiple-words { padding: 0 8px; } .ant-badge-dot { z-index: auto; width: 6px; min-width: 6px; height: 6px; background: #a61d24; border-radius: 100%; box-shadow: 0 0 0 1px #141414; } .ant-badge-dot.ant-scroll-number { transition: background 1.5s; } .ant-badge-count, .ant-badge-dot, .ant-badge .ant-scroll-number-custom-component { position: absolute; top: 0; right: 0; transform: translate(50%, -50%); transform-origin: 100% 0%; } .ant-badge-count.anticon-spin, .ant-badge-dot.anticon-spin, .ant-badge .ant-scroll-number-custom-component.anticon-spin { animation: antBadgeLoadingCircle 1s infinite linear; } .ant-badge-status { line-height: inherit; vertical-align: baseline; } .ant-badge-status-dot { position: relative; top: -1px; display: inline-block; width: 6px; height: 6px; vertical-align: middle; border-radius: 50%; } .ant-badge-status-success { background-color: #49aa19; } .ant-badge-status-processing { position: relative; background-color: #177ddc; } .ant-badge-status-processing::after { position: absolute; top: 0; left: 0; width: 100%; height: 100%; border: 1px solid #177ddc; border-radius: 50%; animation: antStatusProcessing 1.2s infinite ease-in-out; content: ''; } .ant-badge-status-default { background-color: #d9d9d9; } .ant-badge-status-error { background-color: #a61d24; } .ant-badge-status-warning { background-color: #d89614; } .ant-badge-status-pink { background: #cb2b83; } .ant-badge-status-magenta { background: #cb2b83; } .ant-badge-status-red { background: #d32029; } .ant-badge-status-volcano { background: #d84a1b; } .ant-badge-status-orange { background: #d87a16; } .ant-badge-status-yellow { background: #d8bd14; } .ant-badge-status-gold { background: #d89614; } .ant-badge-status-cyan { background: #13a8a8; } .ant-badge-status-lime { background: #8bbb11; } .ant-badge-status-green { background: #49aa19; } .ant-badge-status-blue { background: #177ddc; } .ant-badge-status-geekblue { background: #2b4acb; } .ant-badge-status-purple { background: #642ab5; } .ant-badge-status-text { margin-left: 8px; color: rgba(255, 255, 255, 0.85); font-size: 14px; } .ant-badge-zoom-appear, .ant-badge-zoom-enter { animation: antZoomBadgeIn 0.3s cubic-bezier(0.12, 0.4, 0.29, 1.46); animation-fill-mode: both; } .ant-badge-zoom-leave { animation: antZoomBadgeOut 0.3s cubic-bezier(0.71, -0.46, 0.88, 0.6); animation-fill-mode: both; } .ant-badge-not-a-wrapper .ant-badge-zoom-appear, .ant-badge-not-a-wrapper .ant-badge-zoom-enter { animation: antNoWrapperZoomBadgeIn 0.3s cubic-bezier(0.12, 0.4, 0.29, 1.46); } .ant-badge-not-a-wrapper .ant-badge-zoom-leave { animation: antNoWrapperZoomBadgeOut 0.3s cubic-bezier(0.71, -0.46, 0.88, 0.6); } .ant-badge-not-a-wrapper:not(.ant-badge-status) { vertical-align: middle; } .ant-badge-not-a-wrapper .ant-scroll-number-custom-component, .ant-badge-not-a-wrapper .ant-badge-count { transform: none; } .ant-badge-not-a-wrapper .ant-scroll-number-custom-component, .ant-badge-not-a-wrapper .ant-scroll-number { position: relative; top: auto; display: block; transform-origin: 50% 50%; } @keyframes antStatusProcessing { 0% { transform: scale(0.8); opacity: 0.5; } 100% { transform: scale(2.4); opacity: 0; } } .ant-scroll-number { overflow: hidden; direction: ltr; } .ant-scroll-number-only { position: relative; display: inline-block; height: 20px; transition: all 0.3s cubic-bezier(0.645, 0.045, 0.355, 1); /* stylelint-disable property-no-vendor-prefix */ -webkit-transform-style: preserve-3d; -webkit-backface-visibility: hidden; /* stylelint-enable property-no-vendor-prefix */ } .ant-scroll-number-only > p.ant-scroll-number-only-unit { height: 20px; margin: 0; /* stylelint-disable property-no-vendor-prefix */ -webkit-transform-style: preserve-3d; -webkit-backface-visibility: hidden; /* stylelint-enable property-no-vendor-prefix */ } .ant-scroll-number-symbol { vertical-align: top; } @keyframes antZoomBadgeIn { 0% { transform: scale(0) translate(50%, -50%); opacity: 0; } 100% { transform: scale(1) translate(50%, -50%); } } @keyframes antZoomBadgeOut { 0% { transform: scale(1) translate(50%, -50%); } 100% { transform: scale(0) translate(50%, -50%); opacity: 0; } } @keyframes antNoWrapperZoomBadgeIn { 0% { transform: scale(0); opacity: 0; } 100% { transform: scale(1); } } @keyframes antNoWrapperZoomBadgeOut { 0% { transform: scale(1); } 100% { transform: scale(0); opacity: 0; } } @keyframes antBadgeLoadingCircle { 0% { transform-origin: 50%; } 100% { transform: translate(50%, -50%) rotate(360deg); transform-origin: 50%; } } .ant-ribbon-wrapper { position: relative; } .ant-ribbon { box-sizing: border-box; margin: 0; padding: 0; color: rgba(255, 255, 255, 0.85); font-size: 14px; font-variant: tabular-nums; line-height: 1.5715; list-style: none; font-feature-settings: 'tnum'; position: absolute; top: 8px; height: 22px; padding: 0 8px; color: #fff; line-height: 22px; white-space: nowrap; background-color: #177ddc; border-radius: 2px; } .ant-ribbon-text { color: #fff; } .ant-ribbon-corner { position: absolute; top: 100%; width: 8px; height: 8px; color: currentcolor; border: 4px solid; transform: scaleY(0.75); transform-origin: top; } .ant-ribbon-corner::after { position: absolute; top: -4px; left: -4px; width: inherit; height: inherit; color: rgba(0, 0, 0, 0.25); border: inherit; content: ''; } .ant-ribbon-color-pink { color: #cb2b83; background: #cb2b83; } .ant-ribbon-color-magenta { color: #cb2b83; background: #cb2b83; } .ant-ribbon-color-red { color: #d32029; background: #d32029; } .ant-ribbon-color-volcano { color: #d84a1b; background: #d84a1b; } .ant-ribbon-color-orange { color: #d87a16; background: #d87a16; } .ant-ribbon-color-yellow { color: #d8bd14; background: #d8bd14; } .ant-ribbon-color-gold { color: #d89614; background: #d89614; } .ant-ribbon-color-cyan { color: #13a8a8; background: #13a8a8; } .ant-ribbon-color-lime { color: #8bbb11; background: #8bbb11; } .ant-ribbon-color-green { color: #49aa19; background: #49aa19; } .ant-ribbon-color-blue { color: #177ddc; background: #177ddc; } .ant-ribbon-color-geekblue { color: #2b4acb; background: #2b4acb; } .ant-ribbon-color-purple { color: #642ab5; background: #642ab5; } .ant-ribbon.ant-ribbon-placement-end { right: -8px; border-bottom-right-radius: 0; } .ant-ribbon.ant-ribbon-placement-end .ant-ribbon-corner { right: 0; border-color: currentcolor transparent transparent currentcolor; } .ant-ribbon.ant-ribbon-placement-start { left: -8px; border-bottom-left-radius: 0; } .ant-ribbon.ant-ribbon-placement-start .ant-ribbon-corner { left: 0; border-color: currentcolor currentcolor transparent transparent; } .ant-badge-rtl { direction: rtl; } .ant-badge-rtl.ant-badge:not(.ant-badge-not-a-wrapper) .ant-badge-count, .ant-badge-rtl.ant-badge:not(.ant-badge-not-a-wrapper) .ant-badge-dot, .ant-badge-rtl.ant-badge:not(.ant-badge-not-a-wrapper) .ant-scroll-number-custom-component { right: auto; left: 0; direction: ltr; transform: translate(-50%, -50%); transform-origin: 0% 0%; } .ant-badge-rtl.ant-badge:not(.ant-badge-not-a-wrapper) .ant-scroll-number-custom-component { right: auto; left: 0; transform: translate(-50%, -50%); transform-origin: 0% 0%; } .ant-badge-rtl .ant-badge-status-text { margin-right: 8px; margin-left: 0; } .ant-badge:not(.ant-badge-not-a-wrapper).ant-badge-rtl .ant-badge-zoom-appear, .ant-badge:not(.ant-badge-not-a-wrapper).ant-badge-rtl .ant-badge-zoom-enter { animation-name: antZoomBadgeInRtl; } .ant-badge:not(.ant-badge-not-a-wrapper).ant-badge-rtl .ant-badge-zoom-leave { animation-name: antZoomBadgeOutRtl; } .ant-ribbon-rtl { direction: rtl; } .ant-ribbon-rtl.ant-ribbon-placement-end { right: unset; left: -8px; border-bottom-right-radius: 2px; border-bottom-left-radius: 0; } .ant-ribbon-rtl.ant-ribbon-placement-end .ant-ribbon-corner { right: unset; left: 0; border-color: currentcolor currentcolor transparent transparent; } .ant-ribbon-rtl.ant-ribbon-placement-end .ant-ribbon-corner::after { border-color: currentcolor currentcolor transparent transparent; } .ant-ribbon-rtl.ant-ribbon-placement-start { right: -8px; left: unset; border-bottom-right-radius: 0; border-bottom-left-radius: 2px; } .ant-ribbon-rtl.ant-ribbon-placement-start .ant-ribbon-corner { right: 0; left: unset; border-color: currentcolor transparent transparent currentcolor; } .ant-ribbon-rtl.ant-ribbon-placement-start .ant-ribbon-corner::after { border-color: currentcolor transparent transparent currentcolor; } @keyframes antZoomBadgeInRtl { 0% { transform: scale(0) translate(-50%, -50%); opacity: 0; } 100% { transform: scale(1) translate(-50%, -50%); } } @keyframes antZoomBadgeOutRtl { 0% { transform: scale(1) translate(-50%, -50%); } 100% { transform: scale(0) translate(-50%, -50%); opacity: 0; } } .ant-breadcrumb { box-sizing: border-box; margin: 0; padding: 0; color: rgba(255, 255, 255, 0.85); font-variant: tabular-nums; line-height: 1.5715; list-style: none; font-feature-settings: 'tnum'; color: rgba(255, 255, 255, 0.45); font-size: 14px; } .ant-breadcrumb .anticon { font-size: 14px; } .ant-breadcrumb ol { display: flex; flex-wrap: wrap; margin: 0; padding: 0; list-style: none; } .ant-breadcrumb a { color: rgba(255, 255, 255, 0.45); transition: color 0.3s; } .ant-breadcrumb a:hover { color: rgba(255, 255, 255, 0.85); } .ant-breadcrumb li:last-child { color: rgba(255, 255, 255, 0.85); } .ant-breadcrumb li:last-child a { color: rgba(255, 255, 255, 0.85); } li:last-child > .ant-breadcrumb-separator { display: none; } .ant-breadcrumb-separator { margin: 0 8px; color: rgba(255, 255, 255, 0.45); } .ant-breadcrumb-link > .anticon + span, .ant-breadcrumb-link > .anticon + a { margin-left: 4px; } .ant-breadcrumb-overlay-link > .anticon { margin-left: 4px; } .ant-breadcrumb-rtl { direction: rtl; } .ant-breadcrumb-rtl::before { display: table; content: ''; } .ant-breadcrumb-rtl::after { display: table; clear: both; content: ''; } .ant-breadcrumb-rtl::before { display: table; content: ''; } .ant-breadcrumb-rtl::after { display: table; clear: both; content: ''; } .ant-breadcrumb-rtl > span { float: right; } .ant-breadcrumb-rtl .ant-breadcrumb-link > .anticon + span, .ant-breadcrumb-rtl .ant-breadcrumb-link > .anticon + a { margin-right: 4px; margin-left: 0; } .ant-breadcrumb-rtl .ant-breadcrumb-overlay-link > .anticon { margin-right: 4px; margin-left: 0; } .ant-btn { line-height: 1.5715; position: relative; display: inline-block; font-weight: 400; white-space: nowrap; text-align: center; background-image: none; border: 1px solid transparent; box-shadow: 0 2px 0 rgba(0, 0, 0, 0.015); cursor: pointer; transition: all 0.3s cubic-bezier(0.645, 0.045, 0.355, 1); user-select: none; touch-action: manipulation; height: 32px; padding: 4px 15px; font-size: 14px; border-radius: 2px; color: rgba(255, 255, 255, 0.85); border-color: #434343; background: transparent; } .ant-btn > .anticon { line-height: 1; } .ant-btn, .ant-btn:active, .ant-btn:focus { outline: 0; } .ant-btn:not([disabled]):hover { text-decoration: none; } .ant-btn:not([disabled]):active { outline: 0; box-shadow: none; } .ant-btn[disabled] { cursor: not-allowed; } .ant-btn[disabled] > * { pointer-events: none; } .ant-btn-lg { height: 40px; padding: 6.4px 15px; font-size: 16px; border-radius: 2px; } .ant-btn-sm { height: 24px; padding: 0px 7px; font-size: 14px; border-radius: 2px; } .ant-btn > a:only-child { color: currentcolor; } .ant-btn > a:only-child::after { position: absolute; top: 0; right: 0; bottom: 0; left: 0; background: transparent; content: ''; } .ant-btn:hover, .ant-btn:focus { color: #165996; border-color: #165996; background: transparent; } .ant-btn:hover > a:only-child, .ant-btn:focus > a:only-child { color: currentcolor; } .ant-btn:hover > a:only-child::after, .ant-btn:focus > a:only-child::after { position: absolute; top: 0; right: 0; bottom: 0; left: 0; background: transparent; content: ''; } .ant-btn:active { color: #388ed3; border-color: #388ed3; background: transparent; } .ant-btn:active > a:only-child { color: currentcolor; } .ant-btn:active > a:only-child::after { position: absolute; top: 0; right: 0; bottom: 0; left: 0; background: transparent; content: ''; } .ant-btn[disabled], .ant-btn[disabled]:hover, .ant-btn[disabled]:focus, .ant-btn[disabled]:active { color: rgba(255, 255, 255, 0.3); border-color: #434343; background: rgba(255, 255, 255, 0.08); text-shadow: none; box-shadow: none; } .ant-btn[disabled] > a:only-child, .ant-btn[disabled]:hover > a:only-child, .ant-btn[disabled]:focus > a:only-child, .ant-btn[disabled]:active > a:only-child { color: currentcolor; } .ant-btn[disabled] > a:only-child::after, .ant-btn[disabled]:hover > a:only-child::after, .ant-btn[disabled]:focus > a:only-child::after, .ant-btn[disabled]:active > a:only-child::after { position: absolute; top: 0; right: 0; bottom: 0; left: 0; background: transparent; content: ''; } .ant-btn:hover, .ant-btn:focus, .ant-btn:active { text-decoration: none; background: transparent; } .ant-btn > span { display: inline-block; } .ant-btn-primary { color: #fff; border-color: #177ddc; background: #177ddc; text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.12); box-shadow: 0 2px 0 rgba(0, 0, 0, 0.045); } .ant-btn-primary > a:only-child { color: currentcolor; } .ant-btn-primary > a:only-child::after { position: absolute; top: 0; right: 0; bottom: 0; left: 0; background: transparent; content: ''; } .ant-btn-primary:hover, .ant-btn-primary:focus { color: #fff; border-color: #095cb5; background: #095cb5; } .ant-btn-primary:hover > a:only-child, .ant-btn-primary:focus > a:only-child { color: currentcolor; } .ant-btn-primary:hover > a:only-child::after, .ant-btn-primary:focus > a:only-child::after { position: absolute; top: 0; right: 0; bottom: 0; left: 0; background: transparent; content: ''; } .ant-btn-primary:active { color: #fff; border-color: #3c9be8; background: #3c9be8; } .ant-btn-primary:active > a:only-child { color: currentcolor; } .ant-btn-primary:active > a:only-child::after { position: absolute; top: 0; right: 0; bottom: 0; left: 0; background: transparent; content: ''; } .ant-btn-primary[disabled], .ant-btn-primary[disabled]:hover, .ant-btn-primary[disabled]:focus, .ant-btn-primary[disabled]:active { color: rgba(255, 255, 255, 0.3); border-color: #434343; background: rgba(255, 255, 255, 0.08); text-shadow: none; box-shadow: none; } .ant-btn-primary[disabled] > a:only-child, .ant-btn-primary[disabled]:hover > a:only-child, .ant-btn-primary[disabled]:focus > a:only-child, .ant-btn-primary[disabled]:active > a:only-child { color: currentcolor; } .ant-btn-primary[disabled] > a:only-child::after, .ant-btn-primary[disabled]:hover > a:only-child::after, .ant-btn-primary[disabled]:focus > a:only-child::after, .ant-btn-primary[disabled]:active > a:only-child::after { position: absolute; top: 0; right: 0; bottom: 0; left: 0; background: transparent; content: ''; } .ant-btn-group .ant-btn-primary:not(:first-child):not(:last-child) { border-right-color: #165996; border-left-color: #165996; } .ant-btn-group .ant-btn-primary:not(:first-child):not(:last-child):disabled { border-color: #434343; } .ant-btn-group .ant-btn-primary:first-child:not(:last-child) { border-right-color: #165996; } .ant-btn-group .ant-btn-primary:first-child:not(:last-child)[disabled] { border-right-color: #434343; } .ant-btn-group .ant-btn-primary:last-child:not(:first-child), .ant-btn-group .ant-btn-primary + .ant-btn-primary { border-left-color: #165996; } .ant-btn-group .ant-btn-primary:last-child:not(:first-child)[disabled], .ant-btn-group .ant-btn-primary + .ant-btn-primary[disabled] { border-left-color: #434343; } .ant-btn-ghost { color: rgba(255, 255, 255, 0.85); border-color: #434343; background: transparent; } .ant-btn-ghost > a:only-child { color: currentcolor; } .ant-btn-ghost > a:only-child::after { position: absolute; top: 0; right: 0; bottom: 0; left: 0; background: transparent; content: ''; } .ant-btn-ghost:hover, .ant-btn-ghost:focus { color: #165996; border-color: #165996; background: transparent; } .ant-btn-ghost:hover > a:only-child, .ant-btn-ghost:focus > a:only-child { color: currentcolor; } .ant-btn-ghost:hover > a:only-child::after, .ant-btn-ghost:focus > a:only-child::after { position: absolute; top: 0; right: 0; bottom: 0; left: 0; background: transparent; content: ''; } .ant-btn-ghost:active { color: #388ed3; border-color: #388ed3; background: transparent; } .ant-btn-ghost:active > a:only-child { color: currentcolor; } .ant-btn-ghost:active > a:only-child::after { position: absolute; top: 0; right: 0; bottom: 0; left: 0; background: transparent; content: ''; } .ant-btn-ghost[disabled], .ant-btn-ghost[disabled]:hover, .ant-btn-ghost[disabled]:focus, .ant-btn-ghost[disabled]:active { color: rgba(255, 255, 255, 0.3); border-color: #434343; background: rgba(255, 255, 255, 0.08); text-shadow: none; box-shadow: none; } .ant-btn-ghost[disabled] > a:only-child, .ant-btn-ghost[disabled]:hover > a:only-child, .ant-btn-ghost[disabled]:focus > a:only-child, .ant-btn-ghost[disabled]:active > a:only-child { color: currentcolor; } .ant-btn-ghost[disabled] > a:only-child::after, .ant-btn-ghost[disabled]:hover > a:only-child::after, .ant-btn-ghost[disabled]:focus > a:only-child::after, .ant-btn-ghost[disabled]:active > a:only-child::after { position: absolute; top: 0; right: 0; bottom: 0; left: 0; background: transparent; content: ''; } .ant-btn-dashed { color: rgba(255, 255, 255, 0.85); border-color: #434343; background: transparent; border-style: dashed; } .ant-btn-dashed > a:only-child { color: currentcolor; } .ant-btn-dashed > a:only-child::after { position: absolute; top: 0; right: 0; bottom: 0; left: 0; background: transparent; content: ''; } .ant-btn-dashed:hover, .ant-btn-dashed:focus { color: #165996; border-color: #165996; background: transparent; } .ant-btn-dashed:hover > a:only-child, .ant-btn-dashed:focus > a:only-child { color: currentcolor; } .ant-btn-dashed:hover > a:only-child::after, .ant-btn-dashed:focus > a:only-child::after { position: absolute; top: 0; right: 0; bottom: 0; left: 0; background: transparent; content: ''; } .ant-btn-dashed:active { color: #388ed3; border-color: #388ed3; background: transparent; } .ant-btn-dashed:active > a:only-child { color: currentcolor; } .ant-btn-dashed:active > a:only-child::after { position: absolute; top: 0; right: 0; bottom: 0; left: 0; background: transparent; content: ''; } .ant-btn-dashed[disabled], .ant-btn-dashed[disabled]:hover, .ant-btn-dashed[disabled]:focus, .ant-btn-dashed[disabled]:active { color: rgba(255, 255, 255, 0.3); border-color: #434343; background: rgba(255, 255, 255, 0.08); text-shadow: none; box-shadow: none; } .ant-btn-dashed[disabled] > a:only-child, .ant-btn-dashed[disabled]:hover > a:only-child, .ant-btn-dashed[disabled]:focus > a:only-child, .ant-btn-dashed[disabled]:active > a:only-child { color: currentcolor; } .ant-btn-dashed[disabled] > a:only-child::after, .ant-btn-dashed[disabled]:hover > a:only-child::after, .ant-btn-dashed[disabled]:focus > a:only-child::after, .ant-btn-dashed[disabled]:active > a:only-child::after { position: absolute; top: 0; right: 0; bottom: 0; left: 0; background: transparent; content: ''; } .ant-btn-danger { color: #fff; border-color: #a61d24; background: #a61d24; text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.12); box-shadow: 0 2px 0 rgba(0, 0, 0, 0.045); } .ant-btn-danger > a:only-child { color: currentcolor; } .ant-btn-danger > a:only-child::after { position: absolute; top: 0; right: 0; bottom: 0; left: 0; background: transparent; content: ''; } .ant-btn-danger:hover, .ant-btn-danger:focus { color: #fff; border-color: #800f19; background: #800f19; } .ant-btn-danger:hover > a:only-child, .ant-btn-danger:focus > a:only-child { color: currentcolor; } .ant-btn-danger:hover > a:only-child::after, .ant-btn-danger:focus > a:only-child::after { position: absolute; top: 0; right: 0; bottom: 0; left: 0; background: transparent; content: ''; } .ant-btn-danger:active { color: #fff; border-color: #b33b3d; background: #b33b3d; } .ant-btn-danger:active > a:only-child { color: currentcolor; } .ant-btn-danger:active > a:only-child::after { position: absolute; top: 0; right: 0; bottom: 0; left: 0; background: transparent; content: ''; } .ant-btn-danger[disabled], .ant-btn-danger[disabled]:hover, .ant-btn-danger[disabled]:focus, .ant-btn-danger[disabled]:active { color: rgba(255, 255, 255, 0.3); border-color: #434343; background: rgba(255, 255, 255, 0.08); text-shadow: none; box-shadow: none; } .ant-btn-danger[disabled] > a:only-child, .ant-btn-danger[disabled]:hover > a:only-child, .ant-btn-danger[disabled]:focus > a:only-child, .ant-btn-danger[disabled]:active > a:only-child { color: currentcolor; } .ant-btn-danger[disabled] > a:only-child::after, .ant-btn-danger[disabled]:hover > a:only-child::after, .ant-btn-danger[disabled]:focus > a:only-child::after, .ant-btn-danger[disabled]:active > a:only-child::after { position: absolute; top: 0; right: 0; bottom: 0; left: 0; background: transparent; content: ''; } .ant-btn-link { color: #177ddc; border-color: transparent; background: transparent; box-shadow: none; } .ant-btn-link > a:only-child { color: currentcolor; } .ant-btn-link > a:only-child::after { position: absolute; top: 0; right: 0; bottom: 0; left: 0; background: transparent; content: ''; } .ant-btn-link:hover, .ant-btn-link:focus { color: #165996; border-color: #165996; background: transparent; } .ant-btn-link:hover > a:only-child, .ant-btn-link:focus > a:only-child { color: currentcolor; } .ant-btn-link:hover > a:only-child::after, .ant-btn-link:focus > a:only-child::after { position: absolute; top: 0; right: 0; bottom: 0; left: 0; background: transparent; content: ''; } .ant-btn-link:active { color: #388ed3; border-color: #388ed3; background: transparent; } .ant-btn-link:active > a:only-child { color: currentcolor; } .ant-btn-link:active > a:only-child::after { position: absolute; top: 0; right: 0; bottom: 0; left: 0; background: transparent; content: ''; } .ant-btn-link[disabled], .ant-btn-link[disabled]:hover, .ant-btn-link[disabled]:focus, .ant-btn-link[disabled]:active { color: rgba(255, 255, 255, 0.3); border-color: #434343; background: rgba(255, 255, 255, 0.08); text-shadow: none; box-shadow: none; } .ant-btn-link[disabled] > a:only-child, .ant-btn-link[disabled]:hover > a:only-child, .ant-btn-link[disabled]:focus > a:only-child, .ant-btn-link[disabled]:active > a:only-child { color: currentcolor; } .ant-btn-link[disabled] > a:only-child::after, .ant-btn-link[disabled]:hover > a:only-child::after, .ant-btn-link[disabled]:focus > a:only-child::after, .ant-btn-link[disabled]:active > a:only-child::after { position: absolute; top: 0; right: 0; bottom: 0; left: 0; background: transparent; content: ''; } .ant-btn-link:hover { background: transparent; } .ant-btn-link:hover, .ant-btn-link:focus, .ant-btn-link:active { border-color: transparent; } .ant-btn-link[disabled], .ant-btn-link[disabled]:hover, .ant-btn-link[disabled]:focus, .ant-btn-link[disabled]:active { color: rgba(255, 255, 255, 0.3); border-color: transparent; background: transparent; text-shadow: none; box-shadow: none; } .ant-btn-link[disabled] > a:only-child, .ant-btn-link[disabled]:hover > a:only-child, .ant-btn-link[disabled]:focus > a:only-child, .ant-btn-link[disabled]:active > a:only-child { color: currentcolor; } .ant-btn-link[disabled] > a:only-child::after, .ant-btn-link[disabled]:hover > a:only-child::after, .ant-btn-link[disabled]:focus > a:only-child::after, .ant-btn-link[disabled]:active > a:only-child::after { position: absolute; top: 0; right: 0; bottom: 0; left: 0; background: transparent; content: ''; } .ant-btn-text { color: rgba(255, 255, 255, 0.85); border-color: transparent; background: transparent; box-shadow: none; } .ant-btn-text > a:only-child { color: currentcolor; } .ant-btn-text > a:only-child::after { position: absolute; top: 0; right: 0; bottom: 0; left: 0; background: transparent; content: ''; } .ant-btn-text:hover, .ant-btn-text:focus { color: #165996; border-color: #165996; background: transparent; } .ant-btn-text:hover > a:only-child, .ant-btn-text:focus > a:only-child { color: currentcolor; } .ant-btn-text:hover > a:only-child::after, .ant-btn-text:focus > a:only-child::after { position: absolute; top: 0; right: 0; bottom: 0; left: 0; background: transparent; content: ''; } .ant-btn-text:active { color: #388ed3; border-color: #388ed3; background: transparent; } .ant-btn-text:active > a:only-child { color: currentcolor; } .ant-btn-text:active > a:only-child::after { position: absolute; top: 0; right: 0; bottom: 0; left: 0; background: transparent; content: ''; } .ant-btn-text[disabled], .ant-btn-text[disabled]:hover, .ant-btn-text[disabled]:focus, .ant-btn-text[disabled]:active { color: rgba(255, 255, 255, 0.3); border-color: #434343; background: rgba(255, 255, 255, 0.08); text-shadow: none; box-shadow: none; } .ant-btn-text[disabled] > a:only-child, .ant-btn-text[disabled]:hover > a:only-child, .ant-btn-text[disabled]:focus > a:only-child, .ant-btn-text[disabled]:active > a:only-child { color: currentcolor; } .ant-btn-text[disabled] > a:only-child::after, .ant-btn-text[disabled]:hover > a:only-child::after, .ant-btn-text[disabled]:focus > a:only-child::after, .ant-btn-text[disabled]:active > a:only-child::after { position: absolute; top: 0; right: 0; bottom: 0; left: 0; background: transparent; content: ''; } .ant-btn-text:hover, .ant-btn-text:focus { color: rgba(255, 255, 255, 0.85); background: rgba(255, 255, 255, 0.03); border-color: transparent; } .ant-btn-text:active { color: rgba(255, 255, 255, 0.85); background: rgba(255, 255, 255, 0.04); border-color: transparent; } .ant-btn-text[disabled], .ant-btn-text[disabled]:hover, .ant-btn-text[disabled]:focus, .ant-btn-text[disabled]:active { color: rgba(255, 255, 255, 0.3); border-color: transparent; background: transparent; text-shadow: none; box-shadow: none; } .ant-btn-text[disabled] > a:only-child, .ant-btn-text[disabled]:hover > a:only-child, .ant-btn-text[disabled]:focus > a:only-child, .ant-btn-text[disabled]:active > a:only-child { color: currentcolor; } .ant-btn-text[disabled] > a:only-child::after, .ant-btn-text[disabled]:hover > a:only-child::after, .ant-btn-text[disabled]:focus > a:only-child::after, .ant-btn-text[disabled]:active > a:only-child::after { position: absolute; top: 0; right: 0; bottom: 0; left: 0; background: transparent; content: ''; } .ant-btn-dangerous { color: #a61d24; border-color: #a61d24; background: transparent; } .ant-btn-dangerous > a:only-child { color: currentcolor; } .ant-btn-dangerous > a:only-child::after { position: absolute; top: 0; right: 0; bottom: 0; left: 0; background: transparent; content: ''; } .ant-btn-dangerous:hover, .ant-btn-dangerous:focus { color: #800f19; border-color: #800f19; background: transparent; } .ant-btn-dangerous:hover > a:only-child, .ant-btn-dangerous:focus > a:only-child { color: currentcolor; } .ant-btn-dangerous:hover > a:only-child::after, .ant-btn-dangerous:focus > a:only-child::after { position: absolute; top: 0; right: 0; bottom: 0; left: 0; background: transparent; content: ''; } .ant-btn-dangerous:active { color: #b33b3d; border-color: #b33b3d; background: transparent; } .ant-btn-dangerous:active > a:only-child { color: currentcolor; } .ant-btn-dangerous:active > a:only-child::after { position: absolute; top: 0; right: 0; bottom: 0; left: 0; background: transparent; content: ''; } .ant-btn-dangerous[disabled], .ant-btn-dangerous[disabled]:hover, .ant-btn-dangerous[disabled]:focus, .ant-btn-dangerous[disabled]:active { color: rgba(255, 255, 255, 0.3); border-color: #434343; background: rgba(255, 255, 255, 0.08); text-shadow: none; box-shadow: none; } .ant-btn-dangerous[disabled] > a:only-child, .ant-btn-dangerous[disabled]:hover > a:only-child, .ant-btn-dangerous[disabled]:focus > a:only-child, .ant-btn-dangerous[disabled]:active > a:only-child { color: currentcolor; } .ant-btn-dangerous[disabled] > a:only-child::after, .ant-btn-dangerous[disabled]:hover > a:only-child::after, .ant-btn-dangerous[disabled]:focus > a:only-child::after, .ant-btn-dangerous[disabled]:active > a:only-child::after { position: absolute; top: 0; right: 0; bottom: 0; left: 0; background: transparent; content: ''; } .ant-btn-dangerous.ant-btn-primary { color: #fff; border-color: #a61d24; background: #a61d24; text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.12); box-shadow: 0 2px 0 rgba(0, 0, 0, 0.045); } .ant-btn-dangerous.ant-btn-primary > a:only-child { color: currentcolor; } .ant-btn-dangerous.ant-btn-primary > a:only-child::after { position: absolute; top: 0; right: 0; bottom: 0; left: 0; background: transparent; content: ''; } .ant-btn-dangerous.ant-btn-primary:hover, .ant-btn-dangerous.ant-btn-primary:focus { color: #fff; border-color: #800f19; background: #800f19; } .ant-btn-dangerous.ant-btn-primary:hover > a:only-child, .ant-btn-dangerous.ant-btn-primary:focus > a:only-child { color: currentcolor; } .ant-btn-dangerous.ant-btn-primary:hover > a:only-child::after, .ant-btn-dangerous.ant-btn-primary:focus > a:only-child::after { position: absolute; top: 0; right: 0; bottom: 0; left: 0; background: transparent; content: ''; } .ant-btn-dangerous.ant-btn-primary:active { color: #fff; border-color: #b33b3d; background: #b33b3d; } .ant-btn-dangerous.ant-btn-primary:active > a:only-child { color: currentcolor; } .ant-btn-dangerous.ant-btn-primary:active > a:only-child::after { position: absolute; top: 0; right: 0; bottom: 0; left: 0; background: transparent; content: ''; } .ant-btn-dangerous.ant-btn-primary[disabled], .ant-btn-dangerous.ant-btn-primary[disabled]:hover, .ant-btn-dangerous.ant-btn-primary[disabled]:focus, .ant-btn-dangerous.ant-btn-primary[disabled]:active { color: rgba(255, 255, 255, 0.3); border-color: #434343; background: rgba(255, 255, 255, 0.08); text-shadow: none; box-shadow: none; } .ant-btn-dangerous.ant-btn-primary[disabled] > a:only-child, .ant-btn-dangerous.ant-btn-primary[disabled]:hover > a:only-child, .ant-btn-dangerous.ant-btn-primary[disabled]:focus > a:only-child, .ant-btn-dangerous.ant-btn-primary[disabled]:active > a:only-child { color: currentcolor; } .ant-btn-dangerous.ant-btn-primary[disabled] > a:only-child::after, .ant-btn-dangerous.ant-btn-primary[disabled]:hover > a:only-child::after, .ant-btn-dangerous.ant-btn-primary[disabled]:focus > a:only-child::after, .ant-btn-dangerous.ant-btn-primary[disabled]:active > a:only-child::after { position: absolute; top: 0; right: 0; bottom: 0; left: 0; background: transparent; content: ''; } .ant-btn-dangerous.ant-btn-link { color: #a61d24; border-color: transparent; background: transparent; box-shadow: none; } .ant-btn-dangerous.ant-btn-link > a:only-child { color: currentcolor; } .ant-btn-dangerous.ant-btn-link > a:only-child::after { position: absolute; top: 0; right: 0; bottom: 0; left: 0; background: transparent; content: ''; } .ant-btn-dangerous.ant-btn-link:hover, .ant-btn-dangerous.ant-btn-link:focus { color: #165996; border-color: #165996; background: transparent; } .ant-btn-dangerous.ant-btn-link:hover > a:only-child, .ant-btn-dangerous.ant-btn-link:focus > a:only-child { color: currentcolor; } .ant-btn-dangerous.ant-btn-link:hover > a:only-child::after, .ant-btn-dangerous.ant-btn-link:focus > a:only-child::after { position: absolute; top: 0; right: 0; bottom: 0; left: 0; background: transparent; content: ''; } .ant-btn-dangerous.ant-btn-link:active { color: #388ed3; border-color: #388ed3; background: transparent; } .ant-btn-dangerous.ant-btn-link:active > a:only-child { color: currentcolor; } .ant-btn-dangerous.ant-btn-link:active > a:only-child::after { position: absolute; top: 0; right: 0; bottom: 0; left: 0; background: transparent; content: ''; } .ant-btn-dangerous.ant-btn-link[disabled], .ant-btn-dangerous.ant-btn-link[disabled]:hover, .ant-btn-dangerous.ant-btn-link[disabled]:focus, .ant-btn-dangerous.ant-btn-link[disabled]:active { color: rgba(255, 255, 255, 0.3); border-color: #434343; background: rgba(255, 255, 255, 0.08); text-shadow: none; box-shadow: none; } .ant-btn-dangerous.ant-btn-link[disabled] > a:only-child, .ant-btn-dangerous.ant-btn-link[disabled]:hover > a:only-child, .ant-btn-dangerous.ant-btn-link[disabled]:focus > a:only-child, .ant-btn-dangerous.ant-btn-link[disabled]:active > a:only-child { color: currentcolor; } .ant-btn-dangerous.ant-btn-link[disabled] > a:only-child::after, .ant-btn-dangerous.ant-btn-link[disabled]:hover > a:only-child::after, .ant-btn-dangerous.ant-btn-link[disabled]:focus > a:only-child::after, .ant-btn-dangerous.ant-btn-link[disabled]:active > a:only-child::after { position: absolute; top: 0; right: 0; bottom: 0; left: 0; background: transparent; content: ''; } .ant-btn-dangerous.ant-btn-link:hover, .ant-btn-dangerous.ant-btn-link:focus { color: #800f19; border-color: transparent; background: transparent; } .ant-btn-dangerous.ant-btn-link:hover > a:only-child, .ant-btn-dangerous.ant-btn-link:focus > a:only-child { color: currentcolor; } .ant-btn-dangerous.ant-btn-link:hover > a:only-child::after, .ant-btn-dangerous.ant-btn-link:focus > a:only-child::after { position: absolute; top: 0; right: 0; bottom: 0; left: 0; background: transparent; content: ''; } .ant-btn-dangerous.ant-btn-link:active { color: #b33b3d; border-color: transparent; background: transparent; } .ant-btn-dangerous.ant-btn-link:active > a:only-child { color: currentcolor; } .ant-btn-dangerous.ant-btn-link:active > a:only-child::after { position: absolute; top: 0; right: 0; bottom: 0; left: 0; background: transparent; content: ''; } .ant-btn-dangerous.ant-btn-link[disabled], .ant-btn-dangerous.ant-btn-link[disabled]:hover, .ant-btn-dangerous.ant-btn-link[disabled]:focus, .ant-btn-dangerous.ant-btn-link[disabled]:active { color: rgba(255, 255, 255, 0.3); border-color: transparent; background: transparent; text-shadow: none; box-shadow: none; } .ant-btn-dangerous.ant-btn-link[disabled] > a:only-child, .ant-btn-dangerous.ant-btn-link[disabled]:hover > a:only-child, .ant-btn-dangerous.ant-btn-link[disabled]:focus > a:only-child, .ant-btn-dangerous.ant-btn-link[disabled]:active > a:only-child { color: currentcolor; } .ant-btn-dangerous.ant-btn-link[disabled] > a:only-child::after, .ant-btn-dangerous.ant-btn-link[disabled]:hover > a:only-child::after, .ant-btn-dangerous.ant-btn-link[disabled]:focus > a:only-child::after, .ant-btn-dangerous.ant-btn-link[disabled]:active > a:only-child::after { position: absolute; top: 0; right: 0; bottom: 0; left: 0; background: transparent; content: ''; } .ant-btn-dangerous.ant-btn-text { color: #a61d24; border-color: transparent; background: transparent; box-shadow: none; } .ant-btn-dangerous.ant-btn-text > a:only-child { color: currentcolor; } .ant-btn-dangerous.ant-btn-text > a:only-child::after { position: absolute; top: 0; right: 0; bottom: 0; left: 0; background: transparent; content: ''; } .ant-btn-dangerous.ant-btn-text:hover, .ant-btn-dangerous.ant-btn-text:focus { color: #165996; border-color: #165996; background: transparent; } .ant-btn-dangerous.ant-btn-text:hover > a:only-child, .ant-btn-dangerous.ant-btn-text:focus > a:only-child { color: currentcolor; } .ant-btn-dangerous.ant-btn-text:hover > a:only-child::after, .ant-btn-dangerous.ant-btn-text:focus > a:only-child::after { position: absolute; top: 0; right: 0; bottom: 0; left: 0; background: transparent; content: ''; } .ant-btn-dangerous.ant-btn-text:active { color: #388ed3; border-color: #388ed3; background: transparent; } .ant-btn-dangerous.ant-btn-text:active > a:only-child { color: currentcolor; } .ant-btn-dangerous.ant-btn-text:active > a:only-child::after { position: absolute; top: 0; right: 0; bottom: 0; left: 0; background: transparent; content: ''; } .ant-btn-dangerous.ant-btn-text[disabled], .ant-btn-dangerous.ant-btn-text[disabled]:hover, .ant-btn-dangerous.ant-btn-text[disabled]:focus, .ant-btn-dangerous.ant-btn-text[disabled]:active { color: rgba(255, 255, 255, 0.3); border-color: #434343; background: rgba(255, 255, 255, 0.08); text-shadow: none; box-shadow: none; } .ant-btn-dangerous.ant-btn-text[disabled] > a:only-child, .ant-btn-dangerous.ant-btn-text[disabled]:hover > a:only-child, .ant-btn-dangerous.ant-btn-text[disabled]:focus > a:only-child, .ant-btn-dangerous.ant-btn-text[disabled]:active > a:only-child { color: currentcolor; } .ant-btn-dangerous.ant-btn-text[disabled] > a:only-child::after, .ant-btn-dangerous.ant-btn-text[disabled]:hover > a:only-child::after, .ant-btn-dangerous.ant-btn-text[disabled]:focus > a:only-child::after, .ant-btn-dangerous.ant-btn-text[disabled]:active > a:only-child::after { position: absolute; top: 0; right: 0; bottom: 0; left: 0; background: transparent; content: ''; } .ant-btn-dangerous.ant-btn-text:hover, .ant-btn-dangerous.ant-btn-text:focus { color: #800f19; border-color: transparent; background: rgba(255, 255, 255, 0.03); } .ant-btn-dangerous.ant-btn-text:hover > a:only-child, .ant-btn-dangerous.ant-btn-text:focus > a:only-child { color: currentcolor; } .ant-btn-dangerous.ant-btn-text:hover > a:only-child::after, .ant-btn-dangerous.ant-btn-text:focus > a:only-child::after { position: absolute; top: 0; right: 0; bottom: 0; left: 0; background: transparent; content: ''; } .ant-btn-dangerous.ant-btn-text:active { color: #b33b3d; border-color: transparent; background: rgba(255, 255, 255, 0.04); } .ant-btn-dangerous.ant-btn-text:active > a:only-child { color: currentcolor; } .ant-btn-dangerous.ant-btn-text:active > a:only-child::after { position: absolute; top: 0; right: 0; bottom: 0; left: 0; background: transparent; content: ''; } .ant-btn-dangerous.ant-btn-text[disabled], .ant-btn-dangerous.ant-btn-text[disabled]:hover, .ant-btn-dangerous.ant-btn-text[disabled]:focus, .ant-btn-dangerous.ant-btn-text[disabled]:active { color: rgba(255, 255, 255, 0.3); border-color: transparent; background: transparent; text-shadow: none; box-shadow: none; } .ant-btn-dangerous.ant-btn-text[disabled] > a:only-child, .ant-btn-dangerous.ant-btn-text[disabled]:hover > a:only-child, .ant-btn-dangerous.ant-btn-text[disabled]:focus > a:only-child, .ant-btn-dangerous.ant-btn-text[disabled]:active > a:only-child { color: currentcolor; } .ant-btn-dangerous.ant-btn-text[disabled] > a:only-child::after, .ant-btn-dangerous.ant-btn-text[disabled]:hover > a:only-child::after, .ant-btn-dangerous.ant-btn-text[disabled]:focus > a:only-child::after, .ant-btn-dangerous.ant-btn-text[disabled]:active > a:only-child::after { position: absolute; top: 0; right: 0; bottom: 0; left: 0; background: transparent; content: ''; } .ant-btn-icon-only { width: 32px; height: 32px; padding: 2.4px 0; font-size: 16px; border-radius: 2px; vertical-align: -3px; } .ant-btn-icon-only > * { font-size: 16px; } .ant-btn-icon-only.ant-btn-lg { width: 40px; height: 40px; padding: 4.9px 0; font-size: 18px; border-radius: 2px; } .ant-btn-icon-only.ant-btn-lg > * { font-size: 18px; } .ant-btn-icon-only.ant-btn-sm { width: 24px; height: 24px; padding: 0px 0; font-size: 14px; border-radius: 2px; } .ant-btn-icon-only.ant-btn-sm > * { font-size: 14px; } .ant-btn-icon-only > .anticon { display: flex; justify-content: center; } .ant-btn-icon-only .anticon-loading { padding: 0 !important; } a.ant-btn-icon-only { vertical-align: -1px; } a.ant-btn-icon-only > .anticon { display: inline; } .ant-btn-round { height: 32px; padding: 4px 16px; font-size: 14px; border-radius: 32px; } .ant-btn-round.ant-btn-lg { height: 40px; padding: 6.4px 20px; font-size: 16px; border-radius: 40px; } .ant-btn-round.ant-btn-sm { height: 24px; padding: 0px 12px; font-size: 14px; border-radius: 24px; } .ant-btn-round.ant-btn-icon-only { width: auto; } .ant-btn-circle { min-width: 32px; padding-right: 0; padding-left: 0; text-align: center; border-radius: 50%; } .ant-btn-circle.ant-btn-lg { min-width: 40px; border-radius: 50%; } .ant-btn-circle.ant-btn-sm { min-width: 24px; border-radius: 50%; } .ant-btn::before { position: absolute; top: -1px; right: -1px; bottom: -1px; left: -1px; z-index: 1; display: none; background: #141414; border-radius: inherit; opacity: 0.35; transition: opacity 0.2s; content: ''; pointer-events: none; } .ant-btn .anticon { transition: margin-left 0.3s cubic-bezier(0.645, 0.045, 0.355, 1); } .ant-btn .anticon.anticon-plus > svg, .ant-btn .anticon.anticon-minus > svg { shape-rendering: optimizespeed; } .ant-btn.ant-btn-loading { position: relative; cursor: default; } .ant-btn.ant-btn-loading::before { display: block; } .ant-btn > .ant-btn-loading-icon { transition: width 0.3s cubic-bezier(0.645, 0.045, 0.355, 1), opacity 0.3s cubic-bezier(0.645, 0.045, 0.355, 1); } .ant-btn > .ant-btn-loading-icon .anticon { padding-right: 8px; animation: none; } .ant-btn > .ant-btn-loading-icon .anticon svg { animation: loadingCircle 1s infinite linear; } .ant-btn-group { position: relative; display: inline-flex; } .ant-btn-group > .ant-btn, .ant-btn-group > span > .ant-btn { position: relative; } .ant-btn-group > .ant-btn:hover, .ant-btn-group > span > .ant-btn:hover, .ant-btn-group > .ant-btn:focus, .ant-btn-group > span > .ant-btn:focus, .ant-btn-group > .ant-btn:active, .ant-btn-group > span > .ant-btn:active { z-index: 2; } .ant-btn-group > .ant-btn[disabled], .ant-btn-group > span > .ant-btn[disabled] { z-index: 0; } .ant-btn-group .ant-btn-icon-only { font-size: 14px; } .ant-btn-group .ant-btn + .ant-btn, .ant-btn + .ant-btn-group, .ant-btn-group span + .ant-btn, .ant-btn-group .ant-btn + span, .ant-btn-group > span + span, .ant-btn-group + .ant-btn, .ant-btn-group + .ant-btn-group { margin-left: -1px; } .ant-btn-group .ant-btn-primary + .ant-btn:not(.ant-btn-primary):not([disabled]) { border-left-color: transparent; } .ant-btn-group .ant-btn { border-radius: 0; } .ant-btn-group > .ant-btn:first-child, .ant-btn-group > span:first-child > .ant-btn { margin-left: 0; } .ant-btn-group > .ant-btn:only-child { border-radius: 2px; } .ant-btn-group > span:only-child > .ant-btn { border-radius: 2px; } .ant-btn-group > .ant-btn:first-child:not(:last-child), .ant-btn-group > span:first-child:not(:last-child) > .ant-btn { border-top-left-radius: 2px; border-bottom-left-radius: 2px; } .ant-btn-group > .ant-btn:last-child:not(:first-child), .ant-btn-group > span:last-child:not(:first-child) > .ant-btn { border-top-right-radius: 2px; border-bottom-right-radius: 2px; } .ant-btn-group-sm > .ant-btn:only-child { border-radius: 2px; } .ant-btn-group-sm > span:only-child > .ant-btn { border-radius: 2px; } .ant-btn-group-sm > .ant-btn:first-child:not(:last-child), .ant-btn-group-sm > span:first-child:not(:last-child) > .ant-btn { border-top-left-radius: 2px; border-bottom-left-radius: 2px; } .ant-btn-group-sm > .ant-btn:last-child:not(:first-child), .ant-btn-group-sm > span:last-child:not(:first-child) > .ant-btn { border-top-right-radius: 2px; border-bottom-right-radius: 2px; } .ant-btn-group > .ant-btn-group { float: left; } .ant-btn-group > .ant-btn-group:not(:first-child):not(:last-child) > .ant-btn { border-radius: 0; } .ant-btn-group > .ant-btn-group:first-child:not(:last-child) > .ant-btn:last-child { padding-right: 8px; border-top-right-radius: 0; border-bottom-right-radius: 0; } .ant-btn-group > .ant-btn-group:last-child:not(:first-child) > .ant-btn:first-child { padding-left: 8px; border-top-left-radius: 0; border-bottom-left-radius: 0; } .ant-btn-rtl.ant-btn-group .ant-btn + .ant-btn, .ant-btn-rtl.ant-btn + .ant-btn-group, .ant-btn-rtl.ant-btn-group span + .ant-btn, .ant-btn-rtl.ant-btn-group .ant-btn + span, .ant-btn-rtl.ant-btn-group > span + span, .ant-btn-rtl.ant-btn-group + .ant-btn, .ant-btn-rtl.ant-btn-group + .ant-btn-group, .ant-btn-group-rtl.ant-btn-group .ant-btn + .ant-btn, .ant-btn-group-rtl.ant-btn + .ant-btn-group, .ant-btn-group-rtl.ant-btn-group span + .ant-btn, .ant-btn-group-rtl.ant-btn-group .ant-btn + span, .ant-btn-group-rtl.ant-btn-group > span + span, .ant-btn-group-rtl.ant-btn-group + .ant-btn, .ant-btn-group-rtl.ant-btn-group + .ant-btn-group { margin-right: -1px; margin-left: auto; } .ant-btn-group.ant-btn-group-rtl { direction: rtl; } .ant-btn-group-rtl.ant-btn-group > .ant-btn:first-child:not(:last-child), .ant-btn-group-rtl.ant-btn-group > span:first-child:not(:last-child) > .ant-btn { border-radius: 0 2px 2px 0; } .ant-btn-group-rtl.ant-btn-group > .ant-btn:last-child:not(:first-child), .ant-btn-group-rtl.ant-btn-group > span:last-child:not(:first-child) > .ant-btn { border-radius: 2px 0 0 2px; } .ant-btn-group-rtl.ant-btn-group-sm > .ant-btn:first-child:not(:last-child), .ant-btn-group-rtl.ant-btn-group-sm > span:first-child:not(:last-child) > .ant-btn { border-radius: 0 2px 2px 0; } .ant-btn-group-rtl.ant-btn-group-sm > .ant-btn:last-child:not(:first-child), .ant-btn-group-rtl.ant-btn-group-sm > span:last-child:not(:first-child) > .ant-btn { border-radius: 2px 0 0 2px; } .ant-btn:focus > span, .ant-btn:active > span { position: relative; } .ant-btn > .anticon + span, .ant-btn > span + .anticon { margin-left: 8px; } .ant-btn.ant-btn-background-ghost { color: rgba(255, 255, 255, 0.85); border-color: rgba(255, 255, 255, 0.25); } .ant-btn.ant-btn-background-ghost, .ant-btn.ant-btn-background-ghost:hover, .ant-btn.ant-btn-background-ghost:active, .ant-btn.ant-btn-background-ghost:focus { background: transparent; } .ant-btn.ant-btn-background-ghost:hover, .ant-btn.ant-btn-background-ghost:focus { color: #3c9be8; border-color: #3c9be8; } .ant-btn.ant-btn-background-ghost:active { color: #095cb5; border-color: #095cb5; } .ant-btn.ant-btn-background-ghost[disabled] { color: rgba(255, 255, 255, 0.3); background: transparent; border-color: #434343; } .ant-btn-background-ghost.ant-btn-primary { color: #177ddc; border-color: #177ddc; text-shadow: none; } .ant-btn-background-ghost.ant-btn-primary > a:only-child { color: currentcolor; } .ant-btn-background-ghost.ant-btn-primary > a:only-child::after { position: absolute; top: 0; right: 0; bottom: 0; left: 0; background: transparent; content: ''; } .ant-btn-background-ghost.ant-btn-primary:hover, .ant-btn-background-ghost.ant-btn-primary:focus { color: #095cb5; border-color: #095cb5; } .ant-btn-background-ghost.ant-btn-primary:hover > a:only-child, .ant-btn-background-ghost.ant-btn-primary:focus > a:only-child { color: currentcolor; } .ant-btn-background-ghost.ant-btn-primary:hover > a:only-child::after, .ant-btn-background-ghost.ant-btn-primary:focus > a:only-child::after { position: absolute; top: 0; right: 0; bottom: 0; left: 0; background: transparent; content: ''; } .ant-btn-background-ghost.ant-btn-primary:active { color: #3c9be8; border-color: #3c9be8; } .ant-btn-background-ghost.ant-btn-primary:active > a:only-child { color: currentcolor; } .ant-btn-background-ghost.ant-btn-primary:active > a:only-child::after { position: absolute; top: 0; right: 0; bottom: 0; left: 0; background: transparent; content: ''; } .ant-btn-background-ghost.ant-btn-primary[disabled], .ant-btn-background-ghost.ant-btn-primary[disabled]:hover, .ant-btn-background-ghost.ant-btn-primary[disabled]:focus, .ant-btn-background-ghost.ant-btn-primary[disabled]:active { color: rgba(255, 255, 255, 0.3); border-color: #434343; background: rgba(255, 255, 255, 0.08); text-shadow: none; box-shadow: none; } .ant-btn-background-ghost.ant-btn-primary[disabled] > a:only-child, .ant-btn-background-ghost.ant-btn-primary[disabled]:hover > a:only-child, .ant-btn-background-ghost.ant-btn-primary[disabled]:focus > a:only-child, .ant-btn-background-ghost.ant-btn-primary[disabled]:active > a:only-child { color: currentcolor; } .ant-btn-background-ghost.ant-btn-primary[disabled] > a:only-child::after, .ant-btn-background-ghost.ant-btn-primary[disabled]:hover > a:only-child::after, .ant-btn-background-ghost.ant-btn-primary[disabled]:focus > a:only-child::after, .ant-btn-background-ghost.ant-btn-primary[disabled]:active > a:only-child::after { position: absolute; top: 0; right: 0; bottom: 0; left: 0; background: transparent; content: ''; } .ant-btn-background-ghost.ant-btn-danger { color: #a61d24; border-color: #a61d24; text-shadow: none; } .ant-btn-background-ghost.ant-btn-danger > a:only-child { color: currentcolor; } .ant-btn-background-ghost.ant-btn-danger > a:only-child::after { position: absolute; top: 0; right: 0; bottom: 0; left: 0; background: transparent; content: ''; } .ant-btn-background-ghost.ant-btn-danger:hover, .ant-btn-background-ghost.ant-btn-danger:focus { color: #800f19; border-color: #800f19; } .ant-btn-background-ghost.ant-btn-danger:hover > a:only-child, .ant-btn-background-ghost.ant-btn-danger:focus > a:only-child { color: currentcolor; } .ant-btn-background-ghost.ant-btn-danger:hover > a:only-child::after, .ant-btn-background-ghost.ant-btn-danger:focus > a:only-child::after { position: absolute; top: 0; right: 0; bottom: 0; left: 0; background: transparent; content: ''; } .ant-btn-background-ghost.ant-btn-danger:active { color: #b33b3d; border-color: #b33b3d; } .ant-btn-background-ghost.ant-btn-danger:active > a:only-child { color: currentcolor; } .ant-btn-background-ghost.ant-btn-danger:active > a:only-child::after { position: absolute; top: 0; right: 0; bottom: 0; left: 0; background: transparent; content: ''; } .ant-btn-background-ghost.ant-btn-danger[disabled], .ant-btn-background-ghost.ant-btn-danger[disabled]:hover, .ant-btn-background-ghost.ant-btn-danger[disabled]:focus, .ant-btn-background-ghost.ant-btn-danger[disabled]:active { color: rgba(255, 255, 255, 0.3); border-color: #434343; background: rgba(255, 255, 255, 0.08); text-shadow: none; box-shadow: none; } .ant-btn-background-ghost.ant-btn-danger[disabled] > a:only-child, .ant-btn-background-ghost.ant-btn-danger[disabled]:hover > a:only-child, .ant-btn-background-ghost.ant-btn-danger[disabled]:focus > a:only-child, .ant-btn-background-ghost.ant-btn-danger[disabled]:active > a:only-child { color: currentcolor; } .ant-btn-background-ghost.ant-btn-danger[disabled] > a:only-child::after, .ant-btn-background-ghost.ant-btn-danger[disabled]:hover > a:only-child::after, .ant-btn-background-ghost.ant-btn-danger[disabled]:focus > a:only-child::after, .ant-btn-background-ghost.ant-btn-danger[disabled]:active > a:only-child::after { position: absolute; top: 0; right: 0; bottom: 0; left: 0; background: transparent; content: ''; } .ant-btn-background-ghost.ant-btn-dangerous { color: #a61d24; border-color: #a61d24; text-shadow: none; } .ant-btn-background-ghost.ant-btn-dangerous > a:only-child { color: currentcolor; } .ant-btn-background-ghost.ant-btn-dangerous > a:only-child::after { position: absolute; top: 0; right: 0; bottom: 0; left: 0; background: transparent; content: ''; } .ant-btn-background-ghost.ant-btn-dangerous:hover, .ant-btn-background-ghost.ant-btn-dangerous:focus { color: #800f19; border-color: #800f19; } .ant-btn-background-ghost.ant-btn-dangerous:hover > a:only-child, .ant-btn-background-ghost.ant-btn-dangerous:focus > a:only-child { color: currentcolor; } .ant-btn-background-ghost.ant-btn-dangerous:hover > a:only-child::after, .ant-btn-background-ghost.ant-btn-dangerous:focus > a:only-child::after { position: absolute; top: 0; right: 0; bottom: 0; left: 0; background: transparent; content: ''; } .ant-btn-background-ghost.ant-btn-dangerous:active { color: #b33b3d; border-color: #b33b3d; } .ant-btn-background-ghost.ant-btn-dangerous:active > a:only-child { color: currentcolor; } .ant-btn-background-ghost.ant-btn-dangerous:active > a:only-child::after { position: absolute; top: 0; right: 0; bottom: 0; left: 0; background: transparent; content: ''; } .ant-btn-background-ghost.ant-btn-dangerous[disabled], .ant-btn-background-ghost.ant-btn-dangerous[disabled]:hover, .ant-btn-background-ghost.ant-btn-dangerous[disabled]:focus, .ant-btn-background-ghost.ant-btn-dangerous[disabled]:active { color: rgba(255, 255, 255, 0.3); border-color: #434343; background: rgba(255, 255, 255, 0.08); text-shadow: none; box-shadow: none; } .ant-btn-background-ghost.ant-btn-dangerous[disabled] > a:only-child, .ant-btn-background-ghost.ant-btn-dangerous[disabled]:hover > a:only-child, .ant-btn-background-ghost.ant-btn-dangerous[disabled]:focus > a:only-child, .ant-btn-background-ghost.ant-btn-dangerous[disabled]:active > a:only-child { color: currentcolor; } .ant-btn-background-ghost.ant-btn-dangerous[disabled] > a:only-child::after, .ant-btn-background-ghost.ant-btn-dangerous[disabled]:hover > a:only-child::after, .ant-btn-background-ghost.ant-btn-dangerous[disabled]:focus > a:only-child::after, .ant-btn-background-ghost.ant-btn-dangerous[disabled]:active > a:only-child::after { position: absolute; top: 0; right: 0; bottom: 0; left: 0; background: transparent; content: ''; } .ant-btn-background-ghost.ant-btn-dangerous.ant-btn-link { color: #a61d24; border-color: transparent; text-shadow: none; } .ant-btn-background-ghost.ant-btn-dangerous.ant-btn-link > a:only-child { color: currentcolor; } .ant-btn-background-ghost.ant-btn-dangerous.ant-btn-link > a:only-child::after { position: absolute; top: 0; right: 0; bottom: 0; left: 0; background: transparent; content: ''; } .ant-btn-background-ghost.ant-btn-dangerous.ant-btn-link:hover, .ant-btn-background-ghost.ant-btn-dangerous.ant-btn-link:focus { color: #800f19; border-color: transparent; } .ant-btn-background-ghost.ant-btn-dangerous.ant-btn-link:hover > a:only-child, .ant-btn-background-ghost.ant-btn-dangerous.ant-btn-link:focus > a:only-child { color: currentcolor; } .ant-btn-background-ghost.ant-btn-dangerous.ant-btn-link:hover > a:only-child::after, .ant-btn-background-ghost.ant-btn-dangerous.ant-btn-link:focus > a:only-child::after { position: absolute; top: 0; right: 0; bottom: 0; left: 0; background: transparent; content: ''; } .ant-btn-background-ghost.ant-btn-dangerous.ant-btn-link:active { color: #b33b3d; border-color: transparent; } .ant-btn-background-ghost.ant-btn-dangerous.ant-btn-link:active > a:only-child { color: currentcolor; } .ant-btn-background-ghost.ant-btn-dangerous.ant-btn-link:active > a:only-child::after { position: absolute; top: 0; right: 0; bottom: 0; left: 0; background: transparent; content: ''; } .ant-btn-background-ghost.ant-btn-dangerous.ant-btn-link[disabled], .ant-btn-background-ghost.ant-btn-dangerous.ant-btn-link[disabled]:hover, .ant-btn-background-ghost.ant-btn-dangerous.ant-btn-link[disabled]:focus, .ant-btn-background-ghost.ant-btn-dangerous.ant-btn-link[disabled]:active { color: rgba(255, 255, 255, 0.3); border-color: #434343; background: rgba(255, 255, 255, 0.08); text-shadow: none; box-shadow: none; } .ant-btn-background-ghost.ant-btn-dangerous.ant-btn-link[disabled] > a:only-child, .ant-btn-background-ghost.ant-btn-dangerous.ant-btn-link[disabled]:hover > a:only-child, .ant-btn-background-ghost.ant-btn-dangerous.ant-btn-link[disabled]:focus > a:only-child, .ant-btn-background-ghost.ant-btn-dangerous.ant-btn-link[disabled]:active > a:only-child { color: currentcolor; } .ant-btn-background-ghost.ant-btn-dangerous.ant-btn-link[disabled] > a:only-child::after, .ant-btn-background-ghost.ant-btn-dangerous.ant-btn-link[disabled]:hover > a:only-child::after, .ant-btn-background-ghost.ant-btn-dangerous.ant-btn-link[disabled]:focus > a:only-child::after, .ant-btn-background-ghost.ant-btn-dangerous.ant-btn-link[disabled]:active > a:only-child::after { position: absolute; top: 0; right: 0; bottom: 0; left: 0; background: transparent; content: ''; } .ant-btn-two-chinese-chars::first-letter { letter-spacing: 0.34em; } .ant-btn-two-chinese-chars > *:not(.anticon) { margin-right: -0.34em; letter-spacing: 0.34em; } .ant-btn.ant-btn-block { width: 100%; } .ant-btn:empty { display: inline-block; width: 0; visibility: hidden; content: '\a0'; } a.ant-btn { padding-top: 0.01px !important; line-height: 30px; } a.ant-btn-disabled { cursor: not-allowed; } a.ant-btn-disabled > * { pointer-events: none; } a.ant-btn-disabled, a.ant-btn-disabled:hover, a.ant-btn-disabled:focus, a.ant-btn-disabled:active { color: rgba(255, 255, 255, 0.3); border-color: transparent; background: transparent; text-shadow: none; box-shadow: none; } a.ant-btn-disabled > a:only-child, a.ant-btn-disabled:hover > a:only-child, a.ant-btn-disabled:focus > a:only-child, a.ant-btn-disabled:active > a:only-child { color: currentcolor; } a.ant-btn-disabled > a:only-child::after, a.ant-btn-disabled:hover > a:only-child::after, a.ant-btn-disabled:focus > a:only-child::after, a.ant-btn-disabled:active > a:only-child::after { position: absolute; top: 0; right: 0; bottom: 0; left: 0; background: transparent; content: ''; } a.ant-btn-lg { line-height: 38px; } a.ant-btn-sm { line-height: 22px; } .ant-btn-compact-item:not(.ant-btn-compact-last-item):not(.ant-btn-compact-item-rtl) { margin-right: -1px; } .ant-btn-compact-item:not(.ant-btn-compact-last-item).ant-btn-compact-item-rtl { margin-left: -1px; } .ant-btn-compact-item:hover, .ant-btn-compact-item:focus, .ant-btn-compact-item:active { z-index: 2; } .ant-btn-compact-item[disabled] { z-index: 0; } .ant-btn-compact-item:not(.ant-btn-compact-first-item):not(.ant-btn-compact-last-item).ant-btn { border-radius: 0; } .ant-btn-compact-item.ant-btn.ant-btn-compact-first-item:not(.ant-btn-compact-last-item):not(.ant-btn-compact-item-rtl) { border-top-right-radius: 0; border-bottom-right-radius: 0; } .ant-btn-compact-item.ant-btn.ant-btn-compact-last-item:not(.ant-btn-compact-first-item):not(.ant-btn-compact-item-rtl) { border-top-left-radius: 0; border-bottom-left-radius: 0; } .ant-btn-compact-item.ant-btn.ant-btn-compact-item-rtl.ant-btn-compact-first-item:not(.ant-btn-compact-last-item) { border-top-left-radius: 0; border-bottom-left-radius: 0; } .ant-btn-compact-item.ant-btn.ant-btn-compact-item-rtl.ant-btn-compact-last-item:not(.ant-btn-compact-first-item) { border-top-right-radius: 0; border-bottom-right-radius: 0; } .ant-btn-icon-only.ant-btn-compact-item { flex: none; } .ant-btn-compact-item.ant-btn-primary:not([disabled]) + .ant-btn-compact-item.ant-btn-primary:not([disabled]) { position: relative; } .ant-btn-compact-item.ant-btn-primary:not([disabled]) + .ant-btn-compact-item.ant-btn-primary:not([disabled])::after { position: absolute; top: -1px; left: -1px; display: inline-block; width: 1px; height: calc(100% + 1px * 2); background-color: #165996; content: ' '; } .ant-btn-compact-item-rtl.ant-btn-compact-first-item.ant-btn-compact-item-rtl:not(.ant-btn-compact-last-item) { border-top-left-radius: 0; border-bottom-left-radius: 0; } .ant-btn-compact-item-rtl.ant-btn-compact-last-item.ant-btn-compact-item-rtl:not(.ant-btn-compact-first-item) { border-top-right-radius: 0; border-bottom-right-radius: 0; } .ant-btn-compact-item-rtl.ant-btn-sm.ant-btn-compact-first-item.ant-btn-compact-item-rtl.ant-btn-sm:not(.ant-btn-compact-last-item) { border-top-left-radius: 0; border-bottom-left-radius: 0; } .ant-btn-compact-item-rtl.ant-btn-sm.ant-btn-compact-last-item.ant-btn-compact-item-rtl.ant-btn-sm:not(.ant-btn-compact-first-item) { border-top-right-radius: 0; border-bottom-right-radius: 0; } .ant-btn-compact-item-rtl.ant-btn-primary:not([disabled]) + .ant-btn-compact-item-rtl.ant-btn-primary:not([disabled])::after { right: -1px; } .ant-btn-compact-vertical-item:not(.ant-btn-compact-vertical-last-item) { margin-bottom: -1px; } .ant-btn-compact-vertical-item:hover, .ant-btn-compact-vertical-item:focus, .ant-btn-compact-vertical-item:active { z-index: 2; } .ant-btn-compact-vertical-item[disabled] { z-index: 0; } .ant-btn-compact-vertical-item:not(.ant-btn-compact-vertical-first-item):not(.ant-btn-compact-vertical-last-item) { border-radius: 0; } .ant-btn-compact-vertical-item.ant-btn-compact-vertical-first-item:not(.ant-btn-compact-vertical-last-item) { border-bottom-right-radius: 0; border-bottom-left-radius: 0; } .ant-btn-compact-vertical-item.ant-btn-compact-vertical-last-item:not(.ant-btn-compact-vertical-first-item) { border-top-left-radius: 0; border-top-right-radius: 0; } .ant-btn-compact-vertical-item.ant-btn-primary:not([disabled]) + .ant-btn-compact-vertical-item.ant-btn-primary:not([disabled]) { position: relative; } .ant-btn-compact-vertical-item.ant-btn-primary:not([disabled]) + .ant-btn-compact-vertical-item.ant-btn-primary:not([disabled])::after { position: absolute; top: -1px; left: -1px; display: inline-block; width: calc(100% + 1px * 2); height: 1px; background-color: #165996; content: ' '; } .ant-btn-rtl { direction: rtl; } .ant-btn-group-rtl.ant-btn-group .ant-btn-primary:last-child:not(:first-child), .ant-btn-group-rtl.ant-btn-group .ant-btn-primary + .ant-btn-primary { border-right-color: #165996; border-left-color: #434343; } .ant-btn-group-rtl.ant-btn-group .ant-btn-primary:last-child:not(:first-child)[disabled], .ant-btn-group-rtl.ant-btn-group .ant-btn-primary + .ant-btn-primary[disabled] { border-right-color: #434343; border-left-color: #165996; } .ant-btn-rtl.ant-btn > .ant-btn-loading-icon .anticon { padding-right: 0; padding-left: 8px; } .ant-btn-rtl.ant-btn > .anticon + span, .ant-btn-rtl.ant-btn > span + .anticon { margin-right: 8px; margin-left: 0; } .ant-picker-calendar { box-sizing: border-box; margin: 0; padding: 0; color: rgba(255, 255, 255, 0.85); font-size: 14px; font-variant: tabular-nums; line-height: 1.5715; list-style: none; font-feature-settings: 'tnum'; background: #141414; } .ant-picker-calendar-header { display: flex; justify-content: flex-end; padding: 12px 0; } .ant-picker-calendar-header .ant-picker-calendar-year-select { min-width: 80px; } .ant-picker-calendar-header .ant-picker-calendar-month-select { min-width: 70px; margin-left: 8px; } .ant-picker-calendar-header .ant-picker-calendar-mode-switch { margin-left: 8px; } .ant-picker-calendar .ant-picker-panel { background: #141414; border: 0; border-top: 1px solid #303030; border-radius: 0; } .ant-picker-calendar .ant-picker-panel .ant-picker-month-panel, .ant-picker-calendar .ant-picker-panel .ant-picker-date-panel { width: auto; } .ant-picker-calendar .ant-picker-panel .ant-picker-body { padding: 8px 0; } .ant-picker-calendar .ant-picker-panel .ant-picker-content { width: 100%; } .ant-picker-calendar-mini { border-radius: 2px; } .ant-picker-calendar-mini .ant-picker-calendar-header { padding-right: 8px; padding-left: 8px; } .ant-picker-calendar-mini .ant-picker-panel { border-radius: 0 0 2px 2px; } .ant-picker-calendar-mini .ant-picker-content { height: 256px; } .ant-picker-calendar-mini .ant-picker-content th { height: auto; padding: 0; line-height: 18px; } .ant-picker-calendar-mini .ant-picker-cell::before { pointer-events: none; } .ant-picker-calendar-full .ant-picker-panel { display: block; width: 100%; text-align: right; background: #141414; border: 0; } .ant-picker-calendar-full .ant-picker-panel .ant-picker-body th, .ant-picker-calendar-full .ant-picker-panel .ant-picker-body td { padding: 0; } .ant-picker-calendar-full .ant-picker-panel .ant-picker-body th { height: auto; padding: 0 12px 5px 0; line-height: 18px; } .ant-picker-calendar-full .ant-picker-panel .ant-picker-cell::before { display: none; } .ant-picker-calendar-full .ant-picker-panel .ant-picker-cell:hover .ant-picker-calendar-date { background: rgba(255, 255, 255, 0.08); } .ant-picker-calendar-full .ant-picker-panel .ant-picker-cell .ant-picker-calendar-date-today::before { display: none; } .ant-picker-calendar-full .ant-picker-panel .ant-picker-cell-selected .ant-picker-calendar-date, .ant-picker-calendar-full .ant-picker-panel .ant-picker-cell-selected:hover .ant-picker-calendar-date, .ant-picker-calendar-full .ant-picker-panel .ant-picker-cell-selected .ant-picker-calendar-date-today, .ant-picker-calendar-full .ant-picker-panel .ant-picker-cell-selected:hover .ant-picker-calendar-date-today { background: #111b26; } .ant-picker-calendar-full .ant-picker-panel .ant-picker-cell-selected .ant-picker-calendar-date .ant-picker-calendar-date-value, .ant-picker-calendar-full .ant-picker-panel .ant-picker-cell-selected:hover .ant-picker-calendar-date .ant-picker-calendar-date-value, .ant-picker-calendar-full .ant-picker-panel .ant-picker-cell-selected .ant-picker-calendar-date-today .ant-picker-calendar-date-value, .ant-picker-calendar-full .ant-picker-panel .ant-picker-cell-selected:hover .ant-picker-calendar-date-today .ant-picker-calendar-date-value { color: #177ddc; } .ant-picker-calendar-full .ant-picker-panel .ant-picker-calendar-date { display: block; width: auto; height: auto; margin: 0 4px; padding: 4px 8px 0; border: 0; border-top: 2px solid #303030; border-radius: 0; transition: background 0.3s; } .ant-picker-calendar-full .ant-picker-panel .ant-picker-calendar-date-value { line-height: 24px; transition: color 0.3s; } .ant-picker-calendar-full .ant-picker-panel .ant-picker-calendar-date-content { position: static; width: auto; height: 86px; overflow-y: auto; color: rgba(255, 255, 255, 0.85); line-height: 1.5715; text-align: left; } .ant-picker-calendar-full .ant-picker-panel .ant-picker-calendar-date-today { border-color: #177ddc; } .ant-picker-calendar-full .ant-picker-panel .ant-picker-calendar-date-today .ant-picker-calendar-date-value { color: rgba(255, 255, 255, 0.85); } @media only screen and (max-width: 480px) { .ant-picker-calendar-header { display: block; } .ant-picker-calendar-header .ant-picker-calendar-year-select { width: 50%; } .ant-picker-calendar-header .ant-picker-calendar-month-select { width: calc(50% - 8px); } .ant-picker-calendar-header .ant-picker-calendar-mode-switch { width: 100%; margin-top: 8px; margin-left: 0; } .ant-picker-calendar-header .ant-picker-calendar-mode-switch > label { width: 50%; text-align: center; } } .ant-picker-calendar-rtl { direction: rtl; } .ant-picker-calendar-rtl .ant-picker-calendar-header .ant-picker-calendar-month-select { margin-right: 8px; margin-left: 0; } .ant-picker-calendar-rtl .ant-picker-calendar-header .ant-picker-calendar-mode-switch { margin-right: 8px; margin-left: 0; } .ant-picker-calendar-rtl.ant-picker-calendar-full .ant-picker-panel { text-align: left; } .ant-picker-calendar-rtl.ant-picker-calendar-full .ant-picker-panel .ant-picker-body th { padding: 0 0 5px 12px; } .ant-picker-calendar-rtl.ant-picker-calendar-full .ant-picker-panel .ant-picker-calendar-date-content { text-align: right; } .ant-card { box-sizing: border-box; margin: 0; padding: 0; color: rgba(255, 255, 255, 0.85); font-size: 14px; font-variant: tabular-nums; line-height: 1.5715; list-style: none; font-feature-settings: 'tnum'; position: relative; background: #141414; border-radius: 2px; } .ant-card-rtl { direction: rtl; } .ant-card-hoverable { cursor: pointer; transition: box-shadow 0.3s, border-color 0.3s; } .ant-card-hoverable:hover { border-color: transparent; box-shadow: 0 1px 2px -2px rgba(0, 0, 0, 0.64), 0 3px 6px 0 rgba(0, 0, 0, 0.48), 0 5px 12px 4px rgba(0, 0, 0, 0.36); } .ant-card-bordered { border: 1px solid #303030; } .ant-card-head { min-height: 48px; margin-bottom: -1px; padding: 0 24px; color: rgba(255, 255, 255, 0.85); font-weight: 500; font-size: 16px; background: transparent; border-bottom: 1px solid #303030; border-radius: 2px 2px 0 0; } .ant-card-head::before { display: table; content: ''; } .ant-card-head::after { display: table; clear: both; content: ''; } .ant-card-head::before { display: table; content: ''; } .ant-card-head::after { display: table; clear: both; content: ''; } .ant-card-head-wrapper { display: flex; align-items: center; } .ant-card-head-title { display: inline-block; flex: 1; padding: 16px 0; overflow: hidden; white-space: nowrap; text-overflow: ellipsis; } .ant-card-head-title > .ant-typography, .ant-card-head-title > .ant-typography-edit-content { left: 0; margin-top: 0; margin-bottom: 0; } .ant-card-head .ant-tabs-top { clear: both; margin-bottom: -17px; color: rgba(255, 255, 255, 0.85); font-weight: normal; font-size: 14px; } .ant-card-head .ant-tabs-top-bar { border-bottom: 1px solid #303030; } .ant-card-extra { margin-left: auto; padding: 16px 0; color: rgba(255, 255, 255, 0.85); font-weight: normal; font-size: 14px; } .ant-card-rtl .ant-card-extra { margin-right: auto; margin-left: 0; } .ant-card-body { padding: 24px; } .ant-card-body::before { display: table; content: ''; } .ant-card-body::after { display: table; clear: both; content: ''; } .ant-card-body::before { display: table; content: ''; } .ant-card-body::after { display: table; clear: both; content: ''; } .ant-card-contain-grid .ant-card-body { display: flex; flex-wrap: wrap; } .ant-card-contain-grid:not(.ant-card-loading) .ant-card-body { margin: -1px 0 0 -1px; padding: 0; } .ant-card-grid { width: 33.33%; padding: 24px; border: 0; border-radius: 0; box-shadow: 1px 0 0 0 #303030, 0 1px 0 0 #303030, 1px 1px 0 0 #303030, 1px 0 0 0 #303030 inset, 0 1px 0 0 #303030 inset; transition: all 0.3s; } .ant-card-grid-hoverable:hover { position: relative; z-index: 1; box-shadow: 0 1px 2px -2px rgba(0, 0, 0, 0.64), 0 3px 6px 0 rgba(0, 0, 0, 0.48), 0 5px 12px 4px rgba(0, 0, 0, 0.36); } .ant-card-contain-tabs > .ant-card-head .ant-card-head-title { min-height: 32px; padding-bottom: 0; } .ant-card-contain-tabs > .ant-card-head .ant-card-extra { padding-bottom: 0; } .ant-card-bordered .ant-card-cover { margin-top: -1px; margin-right: -1px; margin-left: -1px; } .ant-card-cover > * { display: block; width: 100%; } .ant-card-cover img { border-radius: 2px 2px 0 0; } .ant-card-actions { display: flex; margin: 0; padding: 0; list-style: none; background: #141414; border-top: 1px solid #303030; } .ant-card-actions::before { display: table; content: ''; } .ant-card-actions::after { display: table; clear: both; content: ''; } .ant-card-actions::before { display: table; content: ''; } .ant-card-actions::after { display: table; clear: both; content: ''; } .ant-card-actions > li { margin: 12px 0; color: rgba(255, 255, 255, 0.45); text-align: center; } .ant-card-actions > li > span { position: relative; display: block; min-width: 32px; font-size: 14px; line-height: 1.5715; cursor: pointer; } .ant-card-actions > li > span:hover { color: #177ddc; transition: color 0.3s; } .ant-card-actions > li > span a:not(.ant-btn), .ant-card-actions > li > span > .anticon { display: inline-block; width: 100%; color: rgba(255, 255, 255, 0.45); line-height: 22px; transition: color 0.3s; } .ant-card-actions > li > span a:not(.ant-btn):hover, .ant-card-actions > li > span > .anticon:hover { color: #177ddc; } .ant-card-actions > li > span > .anticon { font-size: 16px; line-height: 22px; } .ant-card-actions > li:not(:last-child) { border-right: 1px solid #303030; } .ant-card-rtl .ant-card-actions > li:not(:last-child) { border-right: none; border-left: 1px solid #303030; } .ant-card-type-inner .ant-card-head { padding: 0 24px; background: rgba(255, 255, 255, 0.04); } .ant-card-type-inner .ant-card-head-title { padding: 12px 0; font-size: 14px; } .ant-card-type-inner .ant-card-body { padding: 16px 24px; } .ant-card-type-inner .ant-card-extra { padding: 13.5px 0; } .ant-card-meta { display: flex; margin: -4px 0; } .ant-card-meta::before { display: table; content: ''; } .ant-card-meta::after { display: table; clear: both; content: ''; } .ant-card-meta::before { display: table; content: ''; } .ant-card-meta::after { display: table; clear: both; content: ''; } .ant-card-meta-avatar { padding-right: 16px; } .ant-card-rtl .ant-card-meta-avatar { padding-right: 0; padding-left: 16px; } .ant-card-meta-detail { flex: 1; overflow: hidden; } .ant-card-meta-detail > div:not(:last-child) { margin-bottom: 8px; } .ant-card-meta-title { overflow: hidden; color: rgba(255, 255, 255, 0.85); font-weight: 500; font-size: 16px; white-space: nowrap; text-overflow: ellipsis; } .ant-card-meta-description { color: rgba(255, 255, 255, 0.45); } .ant-card-loading { overflow: hidden; } .ant-card-loading .ant-card-body { user-select: none; } .ant-card-small > .ant-card-head { min-height: 36px; padding: 0 12px; font-size: 14px; } .ant-card-small > .ant-card-head > .ant-card-head-wrapper > .ant-card-head-title { padding: 8px 0; } .ant-card-small > .ant-card-head > .ant-card-head-wrapper > .ant-card-extra { padding: 8px 0; font-size: 14px; } .ant-card-small > .ant-card-body { padding: 12px; } .ant-carousel { box-sizing: border-box; margin: 0; padding: 0; color: rgba(255, 255, 255, 0.85); font-size: 14px; font-variant: tabular-nums; line-height: 1.5715; list-style: none; font-feature-settings: 'tnum'; } .ant-carousel .slick-slider { position: relative; display: block; box-sizing: border-box; touch-action: pan-y; -webkit-touch-callout: none; -webkit-tap-highlight-color: transparent; } .ant-carousel .slick-list { position: relative; display: block; margin: 0; padding: 0; overflow: hidden; } .ant-carousel .slick-list:focus { outline: none; } .ant-carousel .slick-list.dragging { cursor: pointer; } .ant-carousel .slick-list .slick-slide { pointer-events: none; } .ant-carousel .slick-list .slick-slide input.ant-radio-input, .ant-carousel .slick-list .slick-slide input.ant-checkbox-input { visibility: hidden; } .ant-carousel .slick-list .slick-slide.slick-active { pointer-events: auto; } .ant-carousel .slick-list .slick-slide.slick-active input.ant-radio-input, .ant-carousel .slick-list .slick-slide.slick-active input.ant-checkbox-input { visibility: visible; } .ant-carousel .slick-list .slick-slide > div > div { vertical-align: bottom; } .ant-carousel .slick-slider .slick-track, .ant-carousel .slick-slider .slick-list { transform: translate3d(0, 0, 0); touch-action: pan-y; } .ant-carousel .slick-track { position: relative; top: 0; left: 0; display: block; } .ant-carousel .slick-track::before, .ant-carousel .slick-track::after { display: table; content: ''; } .ant-carousel .slick-track::after { clear: both; } .slick-loading .ant-carousel .slick-track { visibility: hidden; } .ant-carousel .slick-slide { display: none; float: left; height: 100%; min-height: 1px; } .ant-carousel .slick-slide img { display: block; } .ant-carousel .slick-slide.slick-loading img { display: none; } .ant-carousel .slick-slide.dragging img { pointer-events: none; } .ant-carousel .slick-initialized .slick-slide { display: block; } .ant-carousel .slick-loading .slick-slide { visibility: hidden; } .ant-carousel .slick-vertical .slick-slide { display: block; height: auto; } .ant-carousel .slick-arrow.slick-hidden { display: none; } .ant-carousel .slick-prev, .ant-carousel .slick-next { position: absolute; top: 50%; display: block; width: 20px; height: 20px; margin-top: -10px; padding: 0; color: transparent; font-size: 0; line-height: 0; background: transparent; border: 0; outline: none; cursor: pointer; } .ant-carousel .slick-prev:hover, .ant-carousel .slick-next:hover, .ant-carousel .slick-prev:focus, .ant-carousel .slick-next:focus { color: transparent; background: transparent; outline: none; } .ant-carousel .slick-prev:hover::before, .ant-carousel .slick-next:hover::before, .ant-carousel .slick-prev:focus::before, .ant-carousel .slick-next:focus::before { opacity: 1; } .ant-carousel .slick-prev.slick-disabled::before, .ant-carousel .slick-next.slick-disabled::before { opacity: 0.25; } .ant-carousel .slick-prev { left: -25px; } .ant-carousel .slick-prev::before { content: '←'; } .ant-carousel .slick-next { right: -25px; } .ant-carousel .slick-next::before { content: '→'; } .ant-carousel .slick-dots { position: absolute; right: 0; bottom: 0; left: 0; z-index: 15; display: flex !important; justify-content: center; margin-right: 15%; margin-bottom: 0; margin-left: 15%; padding-left: 0; list-style: none; } .ant-carousel .slick-dots-bottom { bottom: 12px; } .ant-carousel .slick-dots-top { top: 12px; bottom: auto; } .ant-carousel .slick-dots li { position: relative; display: inline-block; flex: 0 1 auto; box-sizing: content-box; width: 16px; height: 3px; margin: 0 4px; padding: 0; text-align: center; text-indent: -999px; vertical-align: top; transition: all 0.5s; } .ant-carousel .slick-dots li button { position: relative; display: block; width: 100%; height: 3px; padding: 0; color: transparent; font-size: 0; background: #141414; border: 0; border-radius: 1px; outline: none; cursor: pointer; opacity: 0.3; transition: all 0.5s; } .ant-carousel .slick-dots li button:hover, .ant-carousel .slick-dots li button:focus { opacity: 0.75; } .ant-carousel .slick-dots li button::after { position: absolute; top: -4px; right: -4px; bottom: -4px; left: -4px; content: ''; } .ant-carousel .slick-dots li.slick-active { width: 24px; } .ant-carousel .slick-dots li.slick-active button { background: #141414; opacity: 1; } .ant-carousel .slick-dots li.slick-active:hover, .ant-carousel .slick-dots li.slick-active:focus { opacity: 1; } .ant-carousel-vertical .slick-dots { top: 50%; bottom: auto; flex-direction: column; width: 3px; height: auto; margin: 0; transform: translateY(-50%); } .ant-carousel-vertical .slick-dots-left { right: auto; left: 12px; } .ant-carousel-vertical .slick-dots-right { right: 12px; left: auto; } .ant-carousel-vertical .slick-dots li { width: 3px; height: 16px; margin: 4px 0; vertical-align: baseline; } .ant-carousel-vertical .slick-dots li button { width: 3px; height: 16px; } .ant-carousel-vertical .slick-dots li.slick-active { width: 3px; height: 24px; } .ant-carousel-vertical .slick-dots li.slick-active button { width: 3px; height: 24px; } .ant-carousel-rtl { direction: rtl; } .ant-carousel-rtl .ant-carousel .slick-track { right: 0; left: auto; } .ant-carousel-rtl .ant-carousel .slick-prev { right: -25px; left: auto; } .ant-carousel-rtl .ant-carousel .slick-prev::before { content: '→'; } .ant-carousel-rtl .ant-carousel .slick-next { right: auto; left: -25px; } .ant-carousel-rtl .ant-carousel .slick-next::before { content: '←'; } .ant-carousel-rtl.ant-carousel .slick-dots { flex-direction: row-reverse; } .ant-carousel-rtl.ant-carousel-vertical .slick-dots { flex-direction: column; } @keyframes antCheckboxEffect { 0% { transform: scale(1); opacity: 0.5; } 100% { transform: scale(1.6); opacity: 0; } } .ant-cascader-checkbox { box-sizing: border-box; margin: 0; padding: 0; color: rgba(255, 255, 255, 0.85); font-size: 14px; font-variant: tabular-nums; line-height: 1.5715; list-style: none; font-feature-settings: 'tnum'; position: relative; top: 0.2em; line-height: 1; white-space: nowrap; outline: none; cursor: pointer; } .ant-cascader-checkbox-wrapper:hover .ant-cascader-checkbox-inner, .ant-cascader-checkbox:hover .ant-cascader-checkbox-inner, .ant-cascader-checkbox-input:focus + .ant-cascader-checkbox-inner { border-color: #177ddc; } .ant-cascader-checkbox-checked::after { position: absolute; top: 0; left: 0; width: 100%; height: 100%; border: 1px solid #177ddc; border-radius: 2px; visibility: hidden; animation: antCheckboxEffect 0.36s ease-in-out; animation-fill-mode: backwards; content: ''; } .ant-cascader-checkbox:hover::after, .ant-cascader-checkbox-wrapper:hover .ant-cascader-checkbox::after { visibility: visible; } .ant-cascader-checkbox-inner { position: relative; top: 0; left: 0; display: block; width: 16px; height: 16px; direction: ltr; background-color: transparent; border: 1px solid #434343; border-radius: 2px; border-collapse: separate; transition: all 0.3s; } .ant-cascader-checkbox-inner::after { position: absolute; top: 50%; left: 21.5%; display: table; width: 5.71428571px; height: 9.14285714px; border: 2px solid #fff; border-top: 0; border-left: 0; transform: rotate(45deg) scale(0) translate(-50%, -50%); opacity: 0; transition: all 0.1s cubic-bezier(0.71, -0.46, 0.88, 0.6), opacity 0.1s; content: ' '; } .ant-cascader-checkbox-input { position: absolute; top: 0; right: 0; bottom: 0; left: 0; z-index: 1; width: 100%; height: 100%; cursor: pointer; opacity: 0; } .ant-cascader-checkbox-checked .ant-cascader-checkbox-inner::after { position: absolute; display: table; border: 2px solid #fff; border-top: 0; border-left: 0; transform: rotate(45deg) scale(1) translate(-50%, -50%); opacity: 1; transition: all 0.2s cubic-bezier(0.12, 0.4, 0.29, 1.46) 0.1s; content: ' '; } .ant-cascader-checkbox-checked .ant-cascader-checkbox-inner { background-color: #177ddc; border-color: #177ddc; } .ant-cascader-checkbox-disabled { cursor: not-allowed; } .ant-cascader-checkbox-disabled.ant-cascader-checkbox-checked .ant-cascader-checkbox-inner::after { border-color: rgba(255, 255, 255, 0.3); animation-name: none; } .ant-cascader-checkbox-disabled .ant-cascader-checkbox-input { cursor: not-allowed; pointer-events: none; } .ant-cascader-checkbox-disabled .ant-cascader-checkbox-inner { background-color: rgba(255, 255, 255, 0.08); border-color: #434343 !important; } .ant-cascader-checkbox-disabled .ant-cascader-checkbox-inner::after { border-color: rgba(255, 255, 255, 0.08); border-collapse: separate; animation-name: none; } .ant-cascader-checkbox-disabled + span { color: rgba(255, 255, 255, 0.3); cursor: not-allowed; } .ant-cascader-checkbox-disabled:hover::after, .ant-cascader-checkbox-wrapper:hover .ant-cascader-checkbox-disabled::after { visibility: hidden; } .ant-cascader-checkbox-wrapper { box-sizing: border-box; margin: 0; padding: 0; color: rgba(255, 255, 255, 0.85); font-size: 14px; font-variant: tabular-nums; line-height: 1.5715; list-style: none; font-feature-settings: 'tnum'; display: inline-flex; align-items: baseline; line-height: unset; cursor: pointer; } .ant-cascader-checkbox-wrapper::after { display: inline-block; width: 0; overflow: hidden; content: '\a0'; } .ant-cascader-checkbox-wrapper.ant-cascader-checkbox-wrapper-disabled { cursor: not-allowed; } .ant-cascader-checkbox-wrapper + .ant-cascader-checkbox-wrapper { margin-left: 8px; } .ant-cascader-checkbox-wrapper.ant-cascader-checkbox-wrapper-in-form-item input[type='checkbox'] { width: 14px; height: 14px; } .ant-cascader-checkbox + span { padding-right: 8px; padding-left: 8px; } .ant-cascader-checkbox-group { box-sizing: border-box; margin: 0; padding: 0; color: rgba(255, 255, 255, 0.85); font-size: 14px; font-variant: tabular-nums; line-height: 1.5715; list-style: none; font-feature-settings: 'tnum'; display: inline-block; } .ant-cascader-checkbox-group-item { margin-right: 8px; } .ant-cascader-checkbox-group-item:last-child { margin-right: 0; } .ant-cascader-checkbox-group-item + .ant-cascader-checkbox-group-item { margin-left: 0; } .ant-cascader-checkbox-indeterminate .ant-cascader-checkbox-inner { background-color: transparent; border-color: #434343; } .ant-cascader-checkbox-indeterminate .ant-cascader-checkbox-inner::after { top: 50%; left: 50%; width: 8px; height: 8px; background-color: #177ddc; border: 0; transform: translate(-50%, -50%) scale(1); opacity: 1; content: ' '; } .ant-cascader-checkbox-indeterminate.ant-cascader-checkbox-disabled .ant-cascader-checkbox-inner::after { background-color: rgba(255, 255, 255, 0.3); border-color: rgba(255, 255, 255, 0.3); } .ant-cascader-checkbox-rtl { direction: rtl; } .ant-cascader-checkbox-group-rtl .ant-cascader-checkbox-group-item { margin-right: 0; margin-left: 8px; } .ant-cascader-checkbox-group-rtl .ant-cascader-checkbox-group-item:last-child { margin-left: 0 !important; } .ant-cascader-checkbox-group-rtl .ant-cascader-checkbox-group-item + .ant-cascader-checkbox-group-item { margin-left: 8px; } .ant-cascader { width: 184px; } .ant-cascader-checkbox { top: 0; margin-right: 8px; } .ant-cascader-menus { display: flex; flex-wrap: nowrap; align-items: flex-start; } .ant-cascader-menus.ant-cascader-menu-empty .ant-cascader-menu { width: 100%; height: auto; } .ant-cascader-menu { flex-grow: 1; min-width: 111px; height: 180px; margin: 0; margin: -4px 0; padding: 4px 0; overflow: auto; vertical-align: top; list-style: none; border-right: 1px solid #303030; -ms-overflow-style: -ms-autohiding-scrollbar; } .ant-cascader-menu-item { display: flex; flex-wrap: nowrap; align-items: center; padding: 5px 12px; overflow: hidden; line-height: 22px; white-space: nowrap; text-overflow: ellipsis; cursor: pointer; transition: all 0.3s; } .ant-cascader-menu-item:hover { background: rgba(255, 255, 255, 0.08); } .ant-cascader-menu-item-disabled { color: rgba(255, 255, 255, 0.3); cursor: not-allowed; } .ant-cascader-menu-item-disabled:hover { background: transparent; } .ant-cascader-menu-empty .ant-cascader-menu-item { color: rgba(255, 255, 255, 0.3); cursor: default; pointer-events: none; } .ant-cascader-menu-item-active:not(.ant-cascader-menu-item-disabled), .ant-cascader-menu-item-active:not(.ant-cascader-menu-item-disabled):hover { font-weight: 600; background-color: #111b26; } .ant-cascader-menu-item-content { flex: auto; } .ant-cascader-menu-item-expand .ant-cascader-menu-item-expand-icon, .ant-cascader-menu-item-loading-icon { margin-left: 4px; color: rgba(255, 255, 255, 0.45); font-size: 10px; } .ant-cascader-menu-item-disabled.ant-cascader-menu-item-expand .ant-cascader-menu-item-expand-icon, .ant-cascader-menu-item-disabled.ant-cascader-menu-item-loading-icon { color: rgba(255, 255, 255, 0.3); } .ant-cascader-menu-item-keyword { color: #a61d24; } .ant-cascader-compact-item:not(.ant-cascader-compact-last-item):not(.ant-cascader-compact-item-rtl) { margin-right: -1px; } .ant-cascader-compact-item:not(.ant-cascader-compact-last-item).ant-cascader-compact-item-rtl { margin-left: -1px; } .ant-cascader-compact-item:hover, .ant-cascader-compact-item:focus, .ant-cascader-compact-item:active { z-index: 2; } .ant-cascader-compact-item[disabled] { z-index: 0; } .ant-cascader-compact-item:not(.ant-cascader-compact-first-item):not(.ant-cascader-compact-last-item).ant-cascader { border-radius: 0; } .ant-cascader-compact-item.ant-cascader.ant-cascader-compact-first-item:not(.ant-cascader-compact-last-item):not(.ant-cascader-compact-item-rtl) { border-top-right-radius: 0; border-bottom-right-radius: 0; } .ant-cascader-compact-item.ant-cascader.ant-cascader-compact-last-item:not(.ant-cascader-compact-first-item):not(.ant-cascader-compact-item-rtl) { border-top-left-radius: 0; border-bottom-left-radius: 0; } .ant-cascader-compact-item.ant-cascader.ant-cascader-compact-item-rtl.ant-cascader-compact-first-item:not(.ant-cascader-compact-last-item) { border-top-left-radius: 0; border-bottom-left-radius: 0; } .ant-cascader-compact-item.ant-cascader.ant-cascader-compact-item-rtl.ant-cascader-compact-last-item:not(.ant-cascader-compact-first-item) { border-top-right-radius: 0; border-bottom-right-radius: 0; } .ant-cascader-rtl .ant-cascader-menu-item-expand-icon, .ant-cascader-rtl .ant-cascader-menu-item-loading-icon { margin-right: 4px; margin-left: 0; } .ant-cascader-rtl .ant-cascader-checkbox { top: 0; margin-right: 0; margin-left: 8px; } .ant-checkbox { box-sizing: border-box; margin: 0; padding: 0; color: rgba(255, 255, 255, 0.85); font-size: 14px; font-variant: tabular-nums; line-height: 1.5715; list-style: none; font-feature-settings: 'tnum'; position: relative; top: 0.2em; line-height: 1; white-space: nowrap; outline: none; cursor: pointer; } .ant-checkbox-wrapper:hover .ant-checkbox-inner, .ant-checkbox:hover .ant-checkbox-inner, .ant-checkbox-input:focus + .ant-checkbox-inner { border-color: #177ddc; } .ant-checkbox-checked::after { position: absolute; top: 0; left: 0; width: 100%; height: 100%; border: 1px solid #177ddc; border-radius: 2px; visibility: hidden; animation: antCheckboxEffect 0.36s ease-in-out; animation-fill-mode: backwards; content: ''; } .ant-checkbox:hover::after, .ant-checkbox-wrapper:hover .ant-checkbox::after { visibility: visible; } .ant-checkbox-inner { position: relative; top: 0; left: 0; display: block; width: 16px; height: 16px; direction: ltr; background-color: transparent; border: 1px solid #434343; border-radius: 2px; border-collapse: separate; transition: all 0.3s; } .ant-checkbox-inner::after { position: absolute; top: 50%; left: 21.5%; display: table; width: 5.71428571px; height: 9.14285714px; border: 2px solid #fff; border-top: 0; border-left: 0; transform: rotate(45deg) scale(0) translate(-50%, -50%); opacity: 0; transition: all 0.1s cubic-bezier(0.71, -0.46, 0.88, 0.6), opacity 0.1s; content: ' '; } .ant-checkbox-input { position: absolute; top: 0; right: 0; bottom: 0; left: 0; z-index: 1; width: 100%; height: 100%; cursor: pointer; opacity: 0; } .ant-checkbox-checked .ant-checkbox-inner::after { position: absolute; display: table; border: 2px solid #fff; border-top: 0; border-left: 0; transform: rotate(45deg) scale(1) translate(-50%, -50%); opacity: 1; transition: all 0.2s cubic-bezier(0.12, 0.4, 0.29, 1.46) 0.1s; content: ' '; } .ant-checkbox-checked .ant-checkbox-inner { background-color: #177ddc; border-color: #177ddc; } .ant-checkbox-disabled { cursor: not-allowed; } .ant-checkbox-disabled.ant-checkbox-checked .ant-checkbox-inner::after { border-color: rgba(255, 255, 255, 0.3); animation-name: none; } .ant-checkbox-disabled .ant-checkbox-input { cursor: not-allowed; pointer-events: none; } .ant-checkbox-disabled .ant-checkbox-inner { background-color: rgba(255, 255, 255, 0.08); border-color: #434343 !important; } .ant-checkbox-disabled .ant-checkbox-inner::after { border-color: rgba(255, 255, 255, 0.08); border-collapse: separate; animation-name: none; } .ant-checkbox-disabled + span { color: rgba(255, 255, 255, 0.3); cursor: not-allowed; } .ant-checkbox-disabled:hover::after, .ant-checkbox-wrapper:hover .ant-checkbox-disabled::after { visibility: hidden; } .ant-checkbox-wrapper { box-sizing: border-box; margin: 0; padding: 0; color: rgba(255, 255, 255, 0.85); font-size: 14px; font-variant: tabular-nums; line-height: 1.5715; list-style: none; font-feature-settings: 'tnum'; display: inline-flex; align-items: baseline; line-height: unset; cursor: pointer; } .ant-checkbox-wrapper::after { display: inline-block; width: 0; overflow: hidden; content: '\a0'; } .ant-checkbox-wrapper.ant-checkbox-wrapper-disabled { cursor: not-allowed; } .ant-checkbox-wrapper + .ant-checkbox-wrapper { margin-left: 8px; } .ant-checkbox-wrapper.ant-checkbox-wrapper-in-form-item input[type='checkbox'] { width: 14px; height: 14px; } .ant-checkbox + span { padding-right: 8px; padding-left: 8px; } .ant-checkbox-group { box-sizing: border-box; margin: 0; padding: 0; color: rgba(255, 255, 255, 0.85); font-size: 14px; font-variant: tabular-nums; line-height: 1.5715; list-style: none; font-feature-settings: 'tnum'; display: inline-block; } .ant-checkbox-group-item { margin-right: 8px; } .ant-checkbox-group-item:last-child { margin-right: 0; } .ant-checkbox-group-item + .ant-checkbox-group-item { margin-left: 0; } .ant-checkbox-indeterminate .ant-checkbox-inner { background-color: transparent; border-color: #434343; } .ant-checkbox-indeterminate .ant-checkbox-inner::after { top: 50%; left: 50%; width: 8px; height: 8px; background-color: #177ddc; border: 0; transform: translate(-50%, -50%) scale(1); opacity: 1; content: ' '; } .ant-checkbox-indeterminate.ant-checkbox-disabled .ant-checkbox-inner::after { background-color: rgba(255, 255, 255, 0.3); border-color: rgba(255, 255, 255, 0.3); } .ant-checkbox-rtl { direction: rtl; } .ant-checkbox-group-rtl .ant-checkbox-group-item { margin-right: 0; margin-left: 8px; } .ant-checkbox-group-rtl .ant-checkbox-group-item:last-child { margin-left: 0 !important; } .ant-checkbox-group-rtl .ant-checkbox-group-item + .ant-checkbox-group-item { margin-left: 8px; } .ant-collapse { box-sizing: border-box; margin: 0; padding: 0; color: rgba(255, 255, 255, 0.85); font-size: 14px; font-variant: tabular-nums; line-height: 1.5715; list-style: none; font-feature-settings: 'tnum'; background-color: rgba(255, 255, 255, 0.04); border: 1px solid #434343; border-bottom: 0; border-radius: 2px; } .ant-collapse > .ant-collapse-item { border-bottom: 1px solid #434343; } .ant-collapse > .ant-collapse-item:last-child, .ant-collapse > .ant-collapse-item:last-child > .ant-collapse-header { border-radius: 0 0 2px 2px; } .ant-collapse > .ant-collapse-item > .ant-collapse-header { position: relative; display: flex; flex-wrap: nowrap; align-items: flex-start; padding: 12px 16px; color: rgba(255, 255, 255, 0.85); line-height: 1.5715; cursor: pointer; transition: all 0.3s, visibility 0s; } .ant-collapse > .ant-collapse-item > .ant-collapse-header .ant-collapse-arrow { display: inline-block; margin-right: 12px; font-size: 12px; vertical-align: -1px; } .ant-collapse > .ant-collapse-item > .ant-collapse-header .ant-collapse-arrow svg { transition: transform 0.24s; } .ant-collapse > .ant-collapse-item > .ant-collapse-header .ant-collapse-header-text { flex: auto; } .ant-collapse > .ant-collapse-item > .ant-collapse-header .ant-collapse-extra { margin-left: auto; } .ant-collapse > .ant-collapse-item > .ant-collapse-header:focus { outline: none; } .ant-collapse > .ant-collapse-item .ant-collapse-header-collapsible-only { cursor: default; } .ant-collapse > .ant-collapse-item .ant-collapse-header-collapsible-only .ant-collapse-header-text { flex: none; cursor: pointer; } .ant-collapse > .ant-collapse-item .ant-collapse-icon-collapsible-only { cursor: default; } .ant-collapse > .ant-collapse-item .ant-collapse-icon-collapsible-only .ant-collapse-expand-icon { cursor: pointer; } .ant-collapse > .ant-collapse-item.ant-collapse-no-arrow > .ant-collapse-header { padding-left: 12px; } .ant-collapse-icon-position-end > .ant-collapse-item > .ant-collapse-header { position: relative; padding: 12px 16px; padding-right: 40px; } .ant-collapse-icon-position-end > .ant-collapse-item > .ant-collapse-header .ant-collapse-arrow { position: absolute; top: 50%; right: 16px; left: auto; margin: 0; transform: translateY(-50%); } .ant-collapse-content { color: rgba(255, 255, 255, 0.85); background-color: #141414; border-top: 1px solid #434343; } .ant-collapse-content > .ant-collapse-content-box { padding: 16px; } .ant-collapse-content-hidden { display: none; } .ant-collapse-item:last-child > .ant-collapse-content { border-radius: 0 0 2px 2px; } .ant-collapse-borderless { background-color: rgba(255, 255, 255, 0.04); border: 0; } .ant-collapse-borderless > .ant-collapse-item { border-bottom: 1px solid #434343; } .ant-collapse-borderless > .ant-collapse-item:last-child, .ant-collapse-borderless > .ant-collapse-item:last-child .ant-collapse-header { border-radius: 0; } .ant-collapse-borderless > .ant-collapse-item:last-child { border-bottom: 0; } .ant-collapse-borderless > .ant-collapse-item > .ant-collapse-content { background-color: transparent; border-top: 0; } .ant-collapse-borderless > .ant-collapse-item > .ant-collapse-content > .ant-collapse-content-box { padding-top: 4px; } .ant-collapse-ghost { background-color: transparent; border: 0; } .ant-collapse-ghost > .ant-collapse-item { border-bottom: 0; } .ant-collapse-ghost > .ant-collapse-item > .ant-collapse-content { background-color: transparent; border-top: 0; } .ant-collapse-ghost > .ant-collapse-item > .ant-collapse-content > .ant-collapse-content-box { padding-top: 12px; padding-bottom: 12px; } .ant-collapse .ant-collapse-item-disabled > .ant-collapse-header, .ant-collapse .ant-collapse-item-disabled > .ant-collapse-header > .arrow { color: rgba(255, 255, 255, 0.3); cursor: not-allowed; } .ant-collapse-rtl { direction: rtl; } .ant-collapse-rtl.ant-collapse.ant-collapse-icon-position-end > .ant-collapse-item > .ant-collapse-header { position: relative; padding: 12px 16px; padding-left: 40px; } .ant-collapse-rtl.ant-collapse.ant-collapse-icon-position-end > .ant-collapse-item > .ant-collapse-header .ant-collapse-arrow { position: absolute; top: 50%; right: auto; left: 16px; margin: 0; transform: translateY(-50%); } .ant-collapse-rtl .ant-collapse > .ant-collapse-item > .ant-collapse-header { padding: 12px 16px; padding-right: 40px; } .ant-collapse-rtl.ant-collapse > .ant-collapse-item > .ant-collapse-header .ant-collapse-arrow { margin-right: 0; margin-left: 12px; } .ant-collapse-rtl.ant-collapse > .ant-collapse-item > .ant-collapse-header .ant-collapse-arrow svg { transform: rotate(180deg); } .ant-collapse-rtl.ant-collapse > .ant-collapse-item > .ant-collapse-header .ant-collapse-extra { margin-right: auto; margin-left: 0; } .ant-collapse-rtl.ant-collapse > .ant-collapse-item.ant-collapse-no-arrow > .ant-collapse-header { padding-right: 12px; padding-left: 0; } .ant-comment { position: relative; background-color: transparent; } .ant-comment-inner { display: flex; padding: 16px 0; } .ant-comment-avatar { position: relative; flex-shrink: 0; margin-right: 12px; cursor: pointer; } .ant-comment-avatar img { width: 32px; height: 32px; border-radius: 50%; } .ant-comment-content { position: relative; flex: 1 1 auto; min-width: 1px; font-size: 14px; word-wrap: break-word; } .ant-comment-content-author { display: flex; flex-wrap: wrap; justify-content: flex-start; margin-bottom: 4px; font-size: 14px; } .ant-comment-content-author > a, .ant-comment-content-author > span { padding-right: 8px; font-size: 12px; line-height: 18px; } .ant-comment-content-author-name { color: rgba(255, 255, 255, 0.45); font-size: 14px; transition: color 0.3s; } .ant-comment-content-author-name > * { color: rgba(255, 255, 255, 0.45); } .ant-comment-content-author-name > *:hover { color: rgba(255, 255, 255, 0.45); } .ant-comment-content-author-time { color: rgba(255, 255, 255, 0.3); white-space: nowrap; cursor: auto; } .ant-comment-content-detail p { margin-bottom: inherit; white-space: pre-wrap; } .ant-comment-actions { margin-top: 12px; margin-bottom: inherit; padding-left: 0; } .ant-comment-actions > li { display: inline-block; color: rgba(255, 255, 255, 0.45); } .ant-comment-actions > li > span { margin-right: 10px; color: rgba(255, 255, 255, 0.45); font-size: 12px; cursor: pointer; transition: color 0.3s; user-select: none; } .ant-comment-actions > li > span:hover { color: rgba(255, 255, 255, 0.65); } .ant-comment-nested { margin-left: 44px; } .ant-comment-rtl { direction: rtl; } .ant-comment-rtl .ant-comment-avatar { margin-right: 0; margin-left: 12px; } .ant-comment-rtl .ant-comment-content-author > a, .ant-comment-rtl .ant-comment-content-author > span { padding-right: 0; padding-left: 8px; } .ant-comment-rtl .ant-comment-actions { padding-right: 0; } .ant-comment-rtl .ant-comment-actions > li > span { margin-right: 0; margin-left: 10px; } .ant-comment-rtl .ant-comment-nested { margin-right: 44px; margin-left: 0; } .ant-picker-status-error.ant-picker, .ant-picker-status-error.ant-picker:not(.ant-picker-disabled):hover { background-color: transparent; border-color: #a61d24; } .ant-picker-status-error.ant-picker-focused, .ant-picker-status-error.ant-picker:focus { border-color: #a61d24; box-shadow: 0 0 0 2px rgba(166, 29, 36, 0.2); border-right-width: 1px; outline: 0; } .ant-picker-status-error.ant-picker .ant-picker-active-bar { background: #b33b3d; } .ant-picker-status-warning.ant-picker, .ant-picker-status-warning.ant-picker:not(.ant-picker-disabled):hover { background-color: transparent; border-color: #d89614; } .ant-picker-status-warning.ant-picker-focused, .ant-picker-status-warning.ant-picker:focus { border-color: #d89614; box-shadow: 0 0 0 2px rgba(216, 150, 20, 0.2); border-right-width: 1px; outline: 0; } .ant-picker-status-warning.ant-picker .ant-picker-active-bar { background: #e6b239; } .ant-picker { box-sizing: border-box; margin: 0; padding: 0; color: rgba(255, 255, 255, 0.85); font-size: 14px; font-variant: tabular-nums; line-height: 1.5715; list-style: none; font-feature-settings: 'tnum'; padding: 4px 11px 4px; position: relative; display: inline-flex; align-items: center; background: transparent; border: 1px solid #434343; border-radius: 2px; transition: border 0.3s, box-shadow 0.3s; } .ant-picker:hover, .ant-picker-focused { border-color: #165996; border-right-width: 1px; } .ant-input-rtl .ant-picker:hover, .ant-input-rtl .ant-picker-focused { border-right-width: 0; border-left-width: 1px !important; } .ant-picker-focused { border-color: #177ddc; box-shadow: 0 0 0 2px rgba(23, 125, 220, 0.2); border-right-width: 1px; outline: 0; } .ant-input-rtl .ant-picker-focused { border-right-width: 0; border-left-width: 1px !important; } .ant-picker.ant-picker-disabled { background: rgba(255, 255, 255, 0.08); border-color: #434343; cursor: not-allowed; } .ant-picker.ant-picker-disabled .ant-picker-suffix { color: rgba(255, 255, 255, 0.3); } .ant-picker.ant-picker-borderless { background-color: transparent !important; border-color: transparent !important; box-shadow: none !important; } .ant-picker-input { position: relative; display: inline-flex; align-items: center; width: 100%; } .ant-picker-input > input { position: relative; display: inline-block; width: 100%; min-width: 0; padding: 4px 11px; color: rgba(255, 255, 255, 0.85); font-size: 14px; line-height: 1.5715; background-color: transparent; background-image: none; border: 1px solid #434343; border-radius: 2px; transition: all 0.3s; flex: auto; min-width: 1px; height: auto; padding: 0; background: transparent; border: 0; } .ant-picker-input > input::placeholder { color: rgba(255, 255, 255, 0.3); user-select: none; } .ant-picker-input > input:placeholder-shown { text-overflow: ellipsis; } .ant-picker-input > input:hover { border-color: #165996; border-right-width: 1px; } .ant-input-rtl .ant-picker-input > input:hover { border-right-width: 0; border-left-width: 1px !important; } .ant-picker-input > input:focus, .ant-picker-input > input-focused { border-color: #177ddc; box-shadow: 0 0 0 2px rgba(23, 125, 220, 0.2); border-right-width: 1px; outline: 0; } .ant-input-rtl .ant-picker-input > input:focus, .ant-input-rtl .ant-picker-input > input-focused { border-right-width: 0; border-left-width: 1px !important; } .ant-picker-input > input-disabled { color: rgba(255, 255, 255, 0.3); background-color: rgba(255, 255, 255, 0.08); border-color: #434343; box-shadow: none; cursor: not-allowed; opacity: 1; } .ant-picker-input > input-disabled:hover { border-color: #434343; border-right-width: 1px; } .ant-picker-input > input[disabled] { color: rgba(255, 255, 255, 0.3); background-color: rgba(255, 255, 255, 0.08); border-color: #434343; box-shadow: none; cursor: not-allowed; opacity: 1; } .ant-picker-input > input[disabled]:hover { border-color: #434343; border-right-width: 1px; } .ant-picker-input > input-borderless, .ant-picker-input > input-borderless:hover, .ant-picker-input > input-borderless:focus, .ant-picker-input > input-borderless-focused, .ant-picker-input > input-borderless-disabled, .ant-picker-input > input-borderless[disabled] { background-color: transparent; border: none; box-shadow: none; } textarea.ant-picker-input > input { max-width: 100%; height: auto; min-height: 32px; line-height: 1.5715; vertical-align: bottom; transition: all 0.3s, height 0s; } .ant-picker-input > input-lg { padding: 6.5px 11px; font-size: 16px; } .ant-picker-input > input-sm { padding: 0px 7px; } .ant-picker-input > input-rtl { direction: rtl; } .ant-picker-input > input:focus { box-shadow: none; } .ant-picker-input > input[disabled] { background: transparent; } .ant-picker-input:hover .ant-picker-clear { opacity: 1; } .ant-picker-input-placeholder > input { color: rgba(255, 255, 255, 0.3); } .ant-picker-large { padding: 6.5px 11px 6.5px; } .ant-picker-large .ant-picker-input > input { font-size: 16px; } .ant-picker-small { padding: 0px 7px 0px; } .ant-picker-suffix { display: flex; flex: none; align-self: center; margin-left: 4px; color: rgba(255, 255, 255, 0.3); line-height: 1; pointer-events: none; } .ant-picker-suffix > * { vertical-align: top; } .ant-picker-suffix > *:not(:last-child) { margin-right: 8px; } .ant-picker-clear { position: absolute; top: 50%; right: 0; color: rgba(255, 255, 255, 0.3); line-height: 1; background: #141414; transform: translateY(-50%); cursor: pointer; opacity: 0; transition: opacity 0.3s, color 0.3s; } .ant-picker-clear > * { vertical-align: top; } .ant-picker-clear:hover { color: rgba(255, 255, 255, 0.45); } .ant-picker-separator { position: relative; display: inline-block; width: 1em; height: 16px; color: rgba(255, 255, 255, 0.3); font-size: 16px; vertical-align: top; cursor: default; } .ant-picker-focused .ant-picker-separator { color: rgba(255, 255, 255, 0.45); } .ant-picker-disabled .ant-picker-range-separator .ant-picker-separator { cursor: not-allowed; } .ant-picker-range { position: relative; display: inline-flex; } .ant-picker-range .ant-picker-clear { right: 11px; } .ant-picker-range:hover .ant-picker-clear { opacity: 1; } .ant-picker-range .ant-picker-active-bar { bottom: -1px; height: 2px; margin-left: 11px; background: #177ddc; opacity: 0; transition: all 0.3s ease-out; pointer-events: none; } .ant-picker-range.ant-picker-focused .ant-picker-active-bar { opacity: 1; } .ant-picker-range-separator { align-items: center; padding: 0 8px; line-height: 1; } .ant-picker-range.ant-picker-small .ant-picker-clear { right: 7px; } .ant-picker-range.ant-picker-small .ant-picker-active-bar { margin-left: 7px; } .ant-picker-dropdown { box-sizing: border-box; margin: 0; padding: 0; color: rgba(255, 255, 255, 0.85); font-size: 14px; font-variant: tabular-nums; line-height: 1.5715; list-style: none; font-feature-settings: 'tnum'; position: absolute; top: -9999px; left: -9999px; z-index: 1050; } .ant-picker-dropdown-hidden { display: none; } .ant-picker-dropdown-placement-bottomLeft .ant-picker-range-arrow { top: 2.58561808px; display: block; transform: rotate(-135deg) translateY(1px); } .ant-picker-dropdown-placement-topLeft .ant-picker-range-arrow { bottom: 2.58561808px; display: block; transform: rotate(45deg); } .ant-picker-dropdown.ant-slide-up-enter.ant-slide-up-enter-active.ant-picker-dropdown-placement-topLeft, .ant-picker-dropdown.ant-slide-up-enter.ant-slide-up-enter-active.ant-picker-dropdown-placement-topRight, .ant-picker-dropdown.ant-slide-up-appear.ant-slide-up-appear-active.ant-picker-dropdown-placement-topLeft, .ant-picker-dropdown.ant-slide-up-appear.ant-slide-up-appear-active.ant-picker-dropdown-placement-topRight { animation-name: antSlideDownIn; } .ant-picker-dropdown.ant-slide-up-enter.ant-slide-up-enter-active.ant-picker-dropdown-placement-bottomLeft, .ant-picker-dropdown.ant-slide-up-enter.ant-slide-up-enter-active.ant-picker-dropdown-placement-bottomRight, .ant-picker-dropdown.ant-slide-up-appear.ant-slide-up-appear-active.ant-picker-dropdown-placement-bottomLeft, .ant-picker-dropdown.ant-slide-up-appear.ant-slide-up-appear-active.ant-picker-dropdown-placement-bottomRight { animation-name: antSlideUpIn; } .ant-picker-dropdown.ant-slide-up-leave.ant-slide-up-leave-active.ant-picker-dropdown-placement-topLeft, .ant-picker-dropdown.ant-slide-up-leave.ant-slide-up-leave-active.ant-picker-dropdown-placement-topRight { animation-name: antSlideDownOut; } .ant-picker-dropdown.ant-slide-up-leave.ant-slide-up-leave-active.ant-picker-dropdown-placement-bottomLeft, .ant-picker-dropdown.ant-slide-up-leave.ant-slide-up-leave-active.ant-picker-dropdown-placement-bottomRight { animation-name: antSlideUpOut; } .ant-picker-dropdown-range { padding: 7.54247233px 0; } .ant-picker-dropdown-range-hidden { display: none; } .ant-picker-dropdown .ant-picker-panel > .ant-picker-time-panel { padding-top: 4px; } .ant-picker-ranges { margin-bottom: 0; padding: 4px 12px; overflow: hidden; line-height: 34px; text-align: left; list-style: none; } .ant-picker-ranges > li { display: inline-block; } .ant-picker-ranges .ant-picker-preset > .ant-tag-blue { color: #177ddc; background: #111b26; border-color: #153450; cursor: pointer; } .ant-picker-ranges .ant-picker-ok { float: right; margin-left: 8px; } .ant-picker-range-wrapper { display: flex; } .ant-picker-range-arrow { position: absolute; z-index: 1; width: 11.3137085px; height: 11.3137085px; margin-left: 16.5px; box-shadow: 2px 2px 6px -2px rgba(0, 0, 0, 0.1); transition: left 0.3s ease-out; border-radius: 0 0 2px; pointer-events: none; } .ant-picker-range-arrow::before { position: absolute; top: -11.3137085px; left: -11.3137085px; width: 33.9411255px; height: 33.9411255px; background: #1f1f1f; background-repeat: no-repeat; background-position: -10px -10px; content: ''; clip-path: inset(33% 33%); clip-path: path('M 9.849242404917499 24.091883092036785 A 5 5 0 0 1 13.384776310850237 22.627416997969522 L 20.627416997969522 22.627416997969522 A 2 2 0 0 0 22.627416997969522 20.627416997969522 L 22.627416997969522 13.384776310850237 A 5 5 0 0 1 24.091883092036785 9.849242404917499 L 23.091883092036785 9.849242404917499 L 9.849242404917499 23.091883092036785 Z'); } .ant-picker-panel-container { overflow: hidden; vertical-align: top; background: #1f1f1f; border-radius: 2px; box-shadow: 0 3px 6px -4px rgba(0, 0, 0, 0.48), 0 6px 16px 0 rgba(0, 0, 0, 0.32), 0 9px 28px 8px rgba(0, 0, 0, 0.2); transition: margin 0.3s; } .ant-picker-panel-container .ant-picker-panels { display: inline-flex; flex-wrap: nowrap; direction: ltr; } .ant-picker-panel-container .ant-picker-panel { vertical-align: top; background: transparent; border-width: 0 0 1px 0; border-radius: 0; } .ant-picker-panel-container .ant-picker-panel .ant-picker-content, .ant-picker-panel-container .ant-picker-panel table { text-align: center; } .ant-picker-panel-container .ant-picker-panel-focused { border-color: #303030; } .ant-picker-compact-item:not(.ant-picker-compact-last-item):not(.ant-picker-compact-item-rtl) { margin-right: -1px; } .ant-picker-compact-item:not(.ant-picker-compact-last-item).ant-picker-compact-item-rtl { margin-left: -1px; } .ant-picker-compact-item:hover, .ant-picker-compact-item:focus, .ant-picker-compact-item:active { z-index: 2; } .ant-picker-compact-item.ant-picker-focused { z-index: 2; } .ant-picker-compact-item[disabled] { z-index: 0; } .ant-picker-compact-item:not(.ant-picker-compact-first-item):not(.ant-picker-compact-last-item).ant-picker { border-radius: 0; } .ant-picker-compact-item.ant-picker.ant-picker-compact-first-item:not(.ant-picker-compact-last-item):not(.ant-picker-compact-item-rtl) { border-top-right-radius: 0; border-bottom-right-radius: 0; } .ant-picker-compact-item.ant-picker.ant-picker-compact-last-item:not(.ant-picker-compact-first-item):not(.ant-picker-compact-item-rtl) { border-top-left-radius: 0; border-bottom-left-radius: 0; } .ant-picker-compact-item.ant-picker.ant-picker-compact-item-rtl.ant-picker-compact-first-item:not(.ant-picker-compact-last-item) { border-top-left-radius: 0; border-bottom-left-radius: 0; } .ant-picker-compact-item.ant-picker.ant-picker-compact-item-rtl.ant-picker-compact-last-item:not(.ant-picker-compact-first-item) { border-top-right-radius: 0; border-bottom-right-radius: 0; } .ant-picker-panel { display: inline-flex; flex-direction: column; text-align: center; background: #1f1f1f; border: 1px solid #303030; border-radius: 2px; outline: none; } .ant-picker-panel-focused { border-color: #177ddc; } .ant-picker-decade-panel, .ant-picker-year-panel, .ant-picker-quarter-panel, .ant-picker-month-panel, .ant-picker-week-panel, .ant-picker-date-panel, .ant-picker-time-panel { display: flex; flex-direction: column; width: 280px; } .ant-picker-header { display: flex; padding: 0 8px; color: rgba(255, 255, 255, 0.85); border-bottom: 1px solid #303030; } .ant-picker-header > * { flex: none; } .ant-picker-header button { padding: 0; color: rgba(255, 255, 255, 0.3); line-height: 40px; background: transparent; border: 0; cursor: pointer; transition: color 0.3s; } .ant-picker-header > button { min-width: 1.6em; font-size: 14px; } .ant-picker-header > button:hover { color: rgba(255, 255, 255, 0.85); } .ant-picker-header-view { flex: auto; font-weight: 500; line-height: 40px; } .ant-picker-header-view button { color: inherit; font-weight: inherit; } .ant-picker-header-view button:not(:first-child) { margin-left: 8px; } .ant-picker-header-view button:hover { color: #177ddc; } .ant-picker-prev-icon, .ant-picker-next-icon, .ant-picker-super-prev-icon, .ant-picker-super-next-icon { position: relative; display: inline-block; width: 7px; height: 7px; } .ant-picker-prev-icon::before, .ant-picker-next-icon::before, .ant-picker-super-prev-icon::before, .ant-picker-super-next-icon::before { position: absolute; top: 0; left: 0; display: inline-block; width: 7px; height: 7px; border: 0 solid currentcolor; border-width: 1.5px 0 0 1.5px; content: ''; } .ant-picker-super-prev-icon::after, .ant-picker-super-next-icon::after { position: absolute; top: 4px; left: 4px; display: inline-block; width: 7px; height: 7px; border: 0 solid currentcolor; border-width: 1.5px 0 0 1.5px; content: ''; } .ant-picker-prev-icon, .ant-picker-super-prev-icon { transform: rotate(-45deg); } .ant-picker-next-icon, .ant-picker-super-next-icon { transform: rotate(135deg); } .ant-picker-content { width: 100%; table-layout: fixed; border-collapse: collapse; } .ant-picker-content th, .ant-picker-content td { position: relative; min-width: 24px; font-weight: 400; } .ant-picker-content th { height: 30px; color: rgba(255, 255, 255, 0.85); line-height: 30px; } .ant-picker-cell { padding: 3px 0; color: rgba(255, 255, 255, 0.3); cursor: pointer; } .ant-picker-cell-in-view { color: rgba(255, 255, 255, 0.85); } .ant-picker-cell::before { position: absolute; top: 50%; right: 0; left: 0; z-index: 1; height: 24px; transform: translateY(-50%); transition: all 0.3s; content: ''; } .ant-picker-cell .ant-picker-cell-inner { position: relative; z-index: 2; display: inline-block; min-width: 24px; height: 24px; line-height: 24px; border-radius: 2px; transition: background 0.3s, border 0.3s; } .ant-picker-cell:hover:not(.ant-picker-cell-in-view) .ant-picker-cell-inner, .ant-picker-cell:hover:not(.ant-picker-cell-selected):not(.ant-picker-cell-range-start):not(.ant-picker-cell-range-end):not(.ant-picker-cell-range-hover-start):not(.ant-picker-cell-range-hover-end) .ant-picker-cell-inner { background: rgba(255, 255, 255, 0.08); } .ant-picker-cell-in-view.ant-picker-cell-today .ant-picker-cell-inner::before { position: absolute; top: 0; right: 0; bottom: 0; left: 0; z-index: 1; border: 1px solid #177ddc; border-radius: 2px; content: ''; } .ant-picker-cell-in-view.ant-picker-cell-in-range { position: relative; } .ant-picker-cell-in-view.ant-picker-cell-in-range::before { background: #111b26; } .ant-picker-cell-in-view.ant-picker-cell-selected .ant-picker-cell-inner, .ant-picker-cell-in-view.ant-picker-cell-range-start .ant-picker-cell-inner, .ant-picker-cell-in-view.ant-picker-cell-range-end .ant-picker-cell-inner { color: #fff; background: #177ddc; } .ant-picker-cell-in-view.ant-picker-cell-range-start:not(.ant-picker-cell-range-start-single)::before, .ant-picker-cell-in-view.ant-picker-cell-range-end:not(.ant-picker-cell-range-end-single)::before { background: #111b26; } .ant-picker-cell-in-view.ant-picker-cell-range-start::before { left: 50%; } .ant-picker-cell-in-view.ant-picker-cell-range-end::before { right: 50%; } .ant-picker-cell-in-view.ant-picker-cell-range-hover-start:not(.ant-picker-cell-in-range):not(.ant-picker-cell-range-start):not(.ant-picker-cell-range-end)::after, .ant-picker-cell-in-view.ant-picker-cell-range-hover-end:not(.ant-picker-cell-in-range):not(.ant-picker-cell-range-start):not(.ant-picker-cell-range-end)::after, .ant-picker-cell-in-view.ant-picker-cell-range-hover-start.ant-picker-cell-range-start-single::after, .ant-picker-cell-in-view.ant-picker-cell-range-hover-start.ant-picker-cell-range-start.ant-picker-cell-range-end.ant-picker-cell-range-end-near-hover::after, .ant-picker-cell-in-view.ant-picker-cell-range-hover-end.ant-picker-cell-range-start.ant-picker-cell-range-end.ant-picker-cell-range-start-near-hover::after, .ant-picker-cell-in-view.ant-picker-cell-range-hover-end.ant-picker-cell-range-end-single::after, .ant-picker-cell-in-view.ant-picker-cell-range-hover:not(.ant-picker-cell-in-range)::after { position: absolute; top: 50%; z-index: 0; height: 24px; border-top: 1px dashed #0e4980; border-bottom: 1px dashed #0e4980; transform: translateY(-50%); transition: all 0.3s; content: ''; } .ant-picker-cell-range-hover-start::after, .ant-picker-cell-range-hover-end::after, .ant-picker-cell-range-hover::after { right: 0; left: 2px; } .ant-picker-cell-in-view.ant-picker-cell-in-range.ant-picker-cell-range-hover::before, .ant-picker-cell-in-view.ant-picker-cell-range-start.ant-picker-cell-range-hover::before, .ant-picker-cell-in-view.ant-picker-cell-range-end.ant-picker-cell-range-hover::before, .ant-picker-cell-in-view.ant-picker-cell-range-start:not(.ant-picker-cell-range-start-single).ant-picker-cell-range-hover-start::before, .ant-picker-cell-in-view.ant-picker-cell-range-end:not(.ant-picker-cell-range-end-single).ant-picker-cell-range-hover-end::before, .ant-picker-panel > :not(.ant-picker-date-panel) .ant-picker-cell-in-view.ant-picker-cell-in-range.ant-picker-cell-range-hover-start::before, .ant-picker-panel > :not(.ant-picker-date-panel) .ant-picker-cell-in-view.ant-picker-cell-in-range.ant-picker-cell-range-hover-end::before { background: #06213a; } .ant-picker-cell-in-view.ant-picker-cell-range-start:not(.ant-picker-cell-range-start-single):not(.ant-picker-cell-range-end) .ant-picker-cell-inner { border-radius: 2px 0 0 2px; } .ant-picker-cell-in-view.ant-picker-cell-range-end:not(.ant-picker-cell-range-end-single):not(.ant-picker-cell-range-start) .ant-picker-cell-inner { border-radius: 0 2px 2px 0; } .ant-picker-date-panel .ant-picker-cell-in-view.ant-picker-cell-in-range.ant-picker-cell-range-hover-start .ant-picker-cell-inner::after, .ant-picker-date-panel .ant-picker-cell-in-view.ant-picker-cell-in-range.ant-picker-cell-range-hover-end .ant-picker-cell-inner::after { position: absolute; top: 0; bottom: 0; z-index: -1; background: #06213a; transition: all 0.3s; content: ''; } .ant-picker-date-panel .ant-picker-cell-in-view.ant-picker-cell-in-range.ant-picker-cell-range-hover-start .ant-picker-cell-inner::after { right: -6px; left: 0; } .ant-picker-date-panel .ant-picker-cell-in-view.ant-picker-cell-in-range.ant-picker-cell-range-hover-end .ant-picker-cell-inner::after { right: 0; left: -6px; } .ant-picker-cell-range-hover.ant-picker-cell-range-start::after { right: 50%; } .ant-picker-cell-range-hover.ant-picker-cell-range-end::after { left: 50%; } tr > .ant-picker-cell-in-view.ant-picker-cell-range-hover:first-child::after, tr > .ant-picker-cell-in-view.ant-picker-cell-range-hover-end:first-child::after, .ant-picker-cell-in-view.ant-picker-cell-start.ant-picker-cell-range-hover-edge-start.ant-picker-cell-range-hover-edge-start-near-range::after, .ant-picker-cell-in-view.ant-picker-cell-range-hover-edge-start:not(.ant-picker-cell-range-hover-edge-start-near-range)::after, .ant-picker-cell-in-view.ant-picker-cell-range-hover-start::after { left: 6px; border-left: 1px dashed #0e4980; border-top-left-radius: 2px; border-bottom-left-radius: 2px; } tr > .ant-picker-cell-in-view.ant-picker-cell-range-hover:last-child::after, tr > .ant-picker-cell-in-view.ant-picker-cell-range-hover-start:last-child::after, .ant-picker-cell-in-view.ant-picker-cell-end.ant-picker-cell-range-hover-edge-end.ant-picker-cell-range-hover-edge-end-near-range::after, .ant-picker-cell-in-view.ant-picker-cell-range-hover-edge-end:not(.ant-picker-cell-range-hover-edge-end-near-range)::after, .ant-picker-cell-in-view.ant-picker-cell-range-hover-end::after { right: 6px; border-right: 1px dashed #0e4980; border-top-right-radius: 2px; border-bottom-right-radius: 2px; } .ant-picker-cell-disabled { color: rgba(255, 255, 255, 0.3); pointer-events: none; } .ant-picker-cell-disabled .ant-picker-cell-inner { background: transparent; } .ant-picker-cell-disabled::before { background: #303030; } .ant-picker-cell-disabled.ant-picker-cell-today .ant-picker-cell-inner::before { border-color: rgba(255, 255, 255, 0.3); } .ant-picker-decade-panel .ant-picker-content, .ant-picker-year-panel .ant-picker-content, .ant-picker-quarter-panel .ant-picker-content, .ant-picker-month-panel .ant-picker-content { height: 264px; } .ant-picker-decade-panel .ant-picker-cell-inner, .ant-picker-year-panel .ant-picker-cell-inner, .ant-picker-quarter-panel .ant-picker-cell-inner, .ant-picker-month-panel .ant-picker-cell-inner { padding: 0 8px; } .ant-picker-quarter-panel .ant-picker-content { height: 56px; } .ant-picker-footer { width: min-content; min-width: 100%; line-height: 38px; text-align: center; border-bottom: 1px solid transparent; } .ant-picker-panel .ant-picker-footer { border-top: 1px solid #303030; } .ant-picker-footer-extra { padding: 0 12px; line-height: 38px; text-align: left; } .ant-picker-footer-extra:not(:last-child) { border-bottom: 1px solid #303030; } .ant-picker-now { text-align: left; } .ant-picker-today-btn { color: #177ddc; } .ant-picker-today-btn:hover { color: #165996; } .ant-picker-today-btn:active { color: #388ed3; } .ant-picker-today-btn.ant-picker-today-btn-disabled { color: rgba(255, 255, 255, 0.3); cursor: not-allowed; } .ant-picker-decade-panel .ant-picker-cell-inner { padding: 0 4px; } .ant-picker-decade-panel .ant-picker-cell::before { display: none; } .ant-picker-year-panel .ant-picker-body, .ant-picker-quarter-panel .ant-picker-body, .ant-picker-month-panel .ant-picker-body { padding: 0 8px; } .ant-picker-year-panel .ant-picker-cell-inner, .ant-picker-quarter-panel .ant-picker-cell-inner, .ant-picker-month-panel .ant-picker-cell-inner { width: 60px; } .ant-picker-year-panel .ant-picker-cell-range-hover-start::after, .ant-picker-quarter-panel .ant-picker-cell-range-hover-start::after, .ant-picker-month-panel .ant-picker-cell-range-hover-start::after { left: 14px; border-left: 1px dashed #0e4980; border-radius: 2px 0 0 2px; } .ant-picker-panel-rtl .ant-picker-year-panel .ant-picker-cell-range-hover-start::after, .ant-picker-panel-rtl .ant-picker-quarter-panel .ant-picker-cell-range-hover-start::after, .ant-picker-panel-rtl .ant-picker-month-panel .ant-picker-cell-range-hover-start::after { right: 14px; border-right: 1px dashed #0e4980; border-radius: 0 2px 2px 0; } .ant-picker-year-panel .ant-picker-cell-range-hover-end::after, .ant-picker-quarter-panel .ant-picker-cell-range-hover-end::after, .ant-picker-month-panel .ant-picker-cell-range-hover-end::after { right: 14px; border-right: 1px dashed #0e4980; border-radius: 0 2px 2px 0; } .ant-picker-panel-rtl .ant-picker-year-panel .ant-picker-cell-range-hover-end::after, .ant-picker-panel-rtl .ant-picker-quarter-panel .ant-picker-cell-range-hover-end::after, .ant-picker-panel-rtl .ant-picker-month-panel .ant-picker-cell-range-hover-end::after { left: 14px; border-left: 1px dashed #0e4980; border-radius: 2px 0 0 2px; } .ant-picker-week-panel .ant-picker-body { padding: 8px 12px; } .ant-picker-week-panel .ant-picker-cell:hover .ant-picker-cell-inner, .ant-picker-week-panel .ant-picker-cell-selected .ant-picker-cell-inner, .ant-picker-week-panel .ant-picker-cell .ant-picker-cell-inner { background: transparent !important; } .ant-picker-week-panel-row td { transition: background 0.3s; } .ant-picker-week-panel-row:hover td { background: rgba(255, 255, 255, 0.08); } .ant-picker-week-panel-row-selected td, .ant-picker-week-panel-row-selected:hover td { background: #177ddc; } .ant-picker-week-panel-row-selected td.ant-picker-cell-week, .ant-picker-week-panel-row-selected:hover td.ant-picker-cell-week { color: rgba(255, 255, 255, 0.5); } .ant-picker-week-panel-row-selected td.ant-picker-cell-today .ant-picker-cell-inner::before, .ant-picker-week-panel-row-selected:hover td.ant-picker-cell-today .ant-picker-cell-inner::before { border-color: #fff; } .ant-picker-week-panel-row-selected td .ant-picker-cell-inner, .ant-picker-week-panel-row-selected:hover td .ant-picker-cell-inner { color: #fff; } .ant-picker-date-panel .ant-picker-body { padding: 8px 12px; } .ant-picker-date-panel .ant-picker-content { width: 252px; } .ant-picker-date-panel .ant-picker-content th { width: 36px; } .ant-picker-datetime-panel { display: flex; } .ant-picker-datetime-panel .ant-picker-time-panel { border-left: 1px solid #303030; } .ant-picker-datetime-panel .ant-picker-date-panel, .ant-picker-datetime-panel .ant-picker-time-panel { transition: opacity 0.3s; } .ant-picker-datetime-panel-active .ant-picker-date-panel, .ant-picker-datetime-panel-active .ant-picker-time-panel { opacity: 0.3; } .ant-picker-datetime-panel-active .ant-picker-date-panel-active, .ant-picker-datetime-panel-active .ant-picker-time-panel-active { opacity: 1; } .ant-picker-time-panel { width: auto; min-width: auto; } .ant-picker-time-panel .ant-picker-content { display: flex; flex: auto; height: 224px; } .ant-picker-time-panel-column { flex: 1 0 auto; width: 56px; margin: 0; padding: 0; overflow-y: hidden; text-align: left; list-style: none; transition: background 0.3s; } .ant-picker-time-panel-column::after { display: block; height: 196px; content: ''; } .ant-picker-datetime-panel .ant-picker-time-panel-column::after { height: 198px; } .ant-picker-time-panel-column:not(:first-child) { border-left: 1px solid #303030; } .ant-picker-time-panel-column-active { background: rgba(17, 27, 38, 0.2); } .ant-picker-time-panel-column:hover { overflow-y: auto; } .ant-picker-time-panel-column > li { margin: 0; padding: 0; } .ant-picker-time-panel-column > li.ant-picker-time-panel-cell .ant-picker-time-panel-cell-inner { display: block; width: 100%; height: 28px; margin: 0; padding: 0 0 0 14px; color: rgba(255, 255, 255, 0.85); line-height: 28px; border-radius: 0; cursor: pointer; transition: background 0.3s; } .ant-picker-time-panel-column > li.ant-picker-time-panel-cell .ant-picker-time-panel-cell-inner:hover { background: rgba(255, 255, 255, 0.08); } .ant-picker-time-panel-column > li.ant-picker-time-panel-cell-selected .ant-picker-time-panel-cell-inner { background: #111b26; } .ant-picker-time-panel-column > li.ant-picker-time-panel-cell-disabled .ant-picker-time-panel-cell-inner { color: rgba(255, 255, 255, 0.3); background: transparent; cursor: not-allowed; } /* stylelint-disable selector-type-no-unknown,selector-no-vendor-prefix */ _:-ms-fullscreen .ant-picker-range-wrapper .ant-picker-month-panel .ant-picker-cell, :root .ant-picker-range-wrapper .ant-picker-month-panel .ant-picker-cell, _:-ms-fullscreen .ant-picker-range-wrapper .ant-picker-year-panel .ant-picker-cell, :root .ant-picker-range-wrapper .ant-picker-year-panel .ant-picker-cell { padding: 21px 0; } .ant-picker-rtl { direction: rtl; } .ant-picker-rtl .ant-picker-suffix { margin-right: 4px; margin-left: 0; } .ant-picker-rtl .ant-picker-clear { right: auto; left: 0; } .ant-picker-rtl .ant-picker-separator { transform: rotate(180deg); } .ant-picker-panel-rtl .ant-picker-header-view button:not(:first-child) { margin-right: 8px; margin-left: 0; } .ant-picker-rtl.ant-picker-range .ant-picker-clear { right: auto; left: 11px; } .ant-picker-rtl.ant-picker-range .ant-picker-active-bar { margin-right: 11px; margin-left: 0; } .ant-picker-rtl.ant-picker-range.ant-picker-small .ant-picker-active-bar { margin-right: 7px; } .ant-picker-dropdown-rtl .ant-picker-ranges { text-align: right; } .ant-picker-dropdown-rtl .ant-picker-ranges .ant-picker-ok { float: left; margin-right: 8px; margin-left: 0; } .ant-picker-panel-rtl { direction: rtl; } .ant-picker-panel-rtl .ant-picker-prev-icon, .ant-picker-panel-rtl .ant-picker-super-prev-icon { transform: rotate(135deg); } .ant-picker-panel-rtl .ant-picker-next-icon, .ant-picker-panel-rtl .ant-picker-super-next-icon { transform: rotate(-45deg); } .ant-picker-cell .ant-picker-cell-inner { position: relative; z-index: 2; display: inline-block; min-width: 24px; height: 24px; line-height: 24px; border-radius: 2px; transition: background 0.3s, border 0.3s; } .ant-picker-panel-rtl .ant-picker-cell-in-view.ant-picker-cell-range-start::before { right: 50%; left: 0; } .ant-picker-panel-rtl .ant-picker-cell-in-view.ant-picker-cell-range-end::before { right: 0; left: 50%; } .ant-picker-panel-rtl .ant-picker-cell-in-view.ant-picker-cell-range-start.ant-picker-cell-range-end::before { right: 50%; left: 50%; } .ant-picker-panel-rtl .ant-picker-date-panel .ant-picker-cell-in-view.ant-picker-cell-in-range.ant-picker-cell-range-hover-start .ant-picker-cell-inner::after { right: 0; left: -6px; } .ant-picker-panel-rtl .ant-picker-date-panel .ant-picker-cell-in-view.ant-picker-cell-in-range.ant-picker-cell-range-hover-end .ant-picker-cell-inner::after { right: -6px; left: 0; } .ant-picker-panel-rtl .ant-picker-cell-range-hover.ant-picker-cell-range-start::after { right: 0; left: 50%; } .ant-picker-panel-rtl .ant-picker-cell-range-hover.ant-picker-cell-range-end::after { right: 50%; left: 0; } .ant-picker-panel-rtl .ant-picker-cell-in-view.ant-picker-cell-range-start:not(.ant-picker-cell-range-start-single):not(.ant-picker-cell-range-end) .ant-picker-cell-inner { border-radius: 0 2px 2px 0; } .ant-picker-panel-rtl .ant-picker-cell-in-view.ant-picker-cell-range-end:not(.ant-picker-cell-range-end-single):not(.ant-picker-cell-range-start) .ant-picker-cell-inner { border-radius: 2px 0 0 2px; } .ant-picker-panel-rtl tr > .ant-picker-cell-in-view.ant-picker-cell-range-hover:not(.ant-picker-cell-selected):first-child::after, .ant-picker-panel-rtl .ant-picker-cell-in-view.ant-picker-cell-start.ant-picker-cell-range-hover-edge-start.ant-picker-cell-range-hover-edge-start-near-range::after, .ant-picker-panel-rtl .ant-picker-cell-in-view.ant-picker-cell-range-hover-edge-start:not(.ant-picker-cell-range-hover-edge-start-near-range)::after, .ant-picker-panel-rtl .ant-picker-cell-in-view.ant-picker-cell-range-hover-start::after { right: 6px; left: 0; border-right: 1px dashed #0e4980; border-left: none; border-radius: 0 2px 2px 0; } .ant-picker-panel-rtl tr > .ant-picker-cell-in-view.ant-picker-cell-range-hover:not(.ant-picker-cell-selected):last-child::after, .ant-picker-panel-rtl .ant-picker-cell-in-view.ant-picker-cell-end.ant-picker-cell-range-hover-edge-end.ant-picker-cell-range-hover-edge-end-near-range::after, .ant-picker-panel-rtl .ant-picker-cell-in-view.ant-picker-cell-range-hover-edge-end:not(.ant-picker-cell-range-hover-edge-end-near-range)::after, .ant-picker-panel-rtl .ant-picker-cell-in-view.ant-picker-cell-range-hover-end::after { right: 0; left: 6px; border-right: none; border-left: 1px dashed #0e4980; border-radius: 2px 0 0 2px; } .ant-picker-panel-rtl tr > .ant-picker-cell-in-view.ant-picker-cell-range-hover-start:last-child::after, .ant-picker-panel-rtl tr > .ant-picker-cell-in-view.ant-picker-cell-range-hover-end:first-child::after, .ant-picker-panel-rtl .ant-picker-cell-in-view.ant-picker-cell-start.ant-picker-cell-range-hover-edge-start:not(.ant-picker-cell-range-hover)::after, .ant-picker-panel-rtl .ant-picker-cell-in-view.ant-picker-cell-start.ant-picker-cell-range-hover-end.ant-picker-cell-range-hover-edge-start:not(.ant-picker-cell-range-hover)::after, .ant-picker-panel-rtl .ant-picker-cell-in-view.ant-picker-cell-end.ant-picker-cell-range-hover-start.ant-picker-cell-range-hover-edge-end:not(.ant-picker-cell-range-hover)::after, .ant-picker-panel-rtl tr > .ant-picker-cell-in-view.ant-picker-cell-start.ant-picker-cell-range-hover.ant-picker-cell-range-hover-edge-start:last-child::after, .ant-picker-panel-rtl tr > .ant-picker-cell-in-view.ant-picker-cell-end.ant-picker-cell-range-hover.ant-picker-cell-range-hover-edge-end:first-child::after { right: 6px; left: 6px; border-right: 1px dashed #0e4980; border-left: 1px dashed #0e4980; border-radius: 2px; } .ant-picker-dropdown-rtl .ant-picker-footer-extra { direction: rtl; text-align: right; } .ant-picker-panel-rtl .ant-picker-time-panel { direction: ltr; } .ant-descriptions-header { display: flex; align-items: center; margin-bottom: 20px; } .ant-descriptions-title { flex: auto; overflow: hidden; color: rgba(255, 255, 255, 0.85); font-weight: bold; font-size: 16px; line-height: 1.5715; white-space: nowrap; text-overflow: ellipsis; } .ant-descriptions-extra { margin-left: auto; color: rgba(255, 255, 255, 0.85); font-size: 14px; } .ant-descriptions-view { width: 100%; border-radius: 2px; } .ant-descriptions-view table { width: 100%; table-layout: fixed; } .ant-descriptions-row > th, .ant-descriptions-row > td { padding-bottom: 16px; } .ant-descriptions-row:last-child { border-bottom: none; } .ant-descriptions-item-label { color: rgba(255, 255, 255, 0.85); font-weight: normal; font-size: 14px; line-height: 1.5715; text-align: start; } .ant-descriptions-item-label::after { content: ':'; position: relative; top: -0.5px; margin: 0 8px 0 2px; } .ant-descriptions-item-label.ant-descriptions-item-no-colon::after { content: ' '; } .ant-descriptions-item-no-label::after { margin: 0; content: ''; } .ant-descriptions-item-content { display: table-cell; flex: 1; color: rgba(255, 255, 255, 0.85); font-size: 14px; line-height: 1.5715; word-break: break-word; overflow-wrap: break-word; } .ant-descriptions-item { padding-bottom: 0; vertical-align: top; } .ant-descriptions-item-container { display: flex; } .ant-descriptions-item-container .ant-descriptions-item-label, .ant-descriptions-item-container .ant-descriptions-item-content { display: inline-flex; align-items: baseline; } .ant-descriptions-middle .ant-descriptions-row > th, .ant-descriptions-middle .ant-descriptions-row > td { padding-bottom: 12px; } .ant-descriptions-small .ant-descriptions-row > th, .ant-descriptions-small .ant-descriptions-row > td { padding-bottom: 8px; } .ant-descriptions-bordered .ant-descriptions-view { border: 1px solid #303030; } .ant-descriptions-bordered .ant-descriptions-view > table { table-layout: auto; border-collapse: collapse; } .ant-descriptions-bordered .ant-descriptions-item-label, .ant-descriptions-bordered .ant-descriptions-item-content { padding: 16px 24px; border-right: 1px solid #303030; } .ant-descriptions-bordered .ant-descriptions-item-label:last-child, .ant-descriptions-bordered .ant-descriptions-item-content:last-child { border-right: none; } .ant-descriptions-bordered .ant-descriptions-item-label { background-color: rgba(255, 255, 255, 0.04); } .ant-descriptions-bordered .ant-descriptions-item-label::after { display: none; } .ant-descriptions-bordered .ant-descriptions-row { border-bottom: 1px solid #303030; } .ant-descriptions-bordered .ant-descriptions-row:last-child { border-bottom: none; } .ant-descriptions-bordered.ant-descriptions-middle .ant-descriptions-item-label, .ant-descriptions-bordered.ant-descriptions-middle .ant-descriptions-item-content { padding: 12px 24px; } .ant-descriptions-bordered.ant-descriptions-small .ant-descriptions-item-label, .ant-descriptions-bordered.ant-descriptions-small .ant-descriptions-item-content { padding: 8px 16px; } .ant-descriptions-rtl { direction: rtl; } .ant-descriptions-rtl .ant-descriptions-item-label::after { margin: 0 2px 0 8px; } .ant-descriptions-rtl.ant-descriptions-bordered .ant-descriptions-item-label, .ant-descriptions-rtl.ant-descriptions-bordered .ant-descriptions-item-content { border-right: none; border-left: 1px solid #303030; } .ant-descriptions-rtl.ant-descriptions-bordered .ant-descriptions-item-label:last-child, .ant-descriptions-rtl.ant-descriptions-bordered .ant-descriptions-item-content:last-child { border-left: none; } .ant-divider { box-sizing: border-box; margin: 0; padding: 0; color: rgba(255, 255, 255, 0.85); font-size: 14px; font-variant: tabular-nums; line-height: 1.5715; list-style: none; font-feature-settings: 'tnum'; border-top: 1px solid rgba(255, 255, 255, 0.12); } .ant-divider-vertical { position: relative; top: -0.06em; display: inline-block; height: 0.9em; margin: 0 8px; vertical-align: middle; border-top: 0; border-left: 1px solid rgba(255, 255, 255, 0.12); } .ant-divider-horizontal { display: flex; clear: both; width: 100%; min-width: 100%; margin: 24px 0; } .ant-divider-horizontal.ant-divider-with-text { display: flex; align-items: center; margin: 16px 0; color: rgba(255, 255, 255, 0.85); font-weight: 500; font-size: 16px; white-space: nowrap; text-align: center; border-top: 0; border-top-color: rgba(255, 255, 255, 0.12); } .ant-divider-horizontal.ant-divider-with-text::before, .ant-divider-horizontal.ant-divider-with-text::after { position: relative; width: 50%; border-top: 1px solid transparent; border-top-color: inherit; border-bottom: 0; transform: translateY(50%); content: ''; } .ant-divider-horizontal.ant-divider-with-text-left::before { width: 5%; } .ant-divider-horizontal.ant-divider-with-text-left::after { width: 95%; } .ant-divider-horizontal.ant-divider-with-text-right::before { width: 95%; } .ant-divider-horizontal.ant-divider-with-text-right::after { width: 5%; } .ant-divider-inner-text { display: inline-block; padding: 0 1em; } .ant-divider-dashed { background: none; border-color: rgba(255, 255, 255, 0.12); border-style: dashed; border-width: 1px 0 0; } .ant-divider-horizontal.ant-divider-with-text.ant-divider-dashed::before, .ant-divider-horizontal.ant-divider-with-text.ant-divider-dashed::after { border-style: dashed none none; } .ant-divider-vertical.ant-divider-dashed { border-width: 0 0 0 1px; } .ant-divider-plain.ant-divider-with-text { color: rgba(255, 255, 255, 0.85); font-weight: normal; font-size: 14px; } .ant-divider-horizontal.ant-divider-with-text-left.ant-divider-no-default-orientation-margin-left::before { width: 0; } .ant-divider-horizontal.ant-divider-with-text-left.ant-divider-no-default-orientation-margin-left::after { width: 100%; } .ant-divider-horizontal.ant-divider-with-text-left.ant-divider-no-default-orientation-margin-left .ant-divider-inner-text { padding-left: 0; } .ant-divider-horizontal.ant-divider-with-text-right.ant-divider-no-default-orientation-margin-right::before { width: 100%; } .ant-divider-horizontal.ant-divider-with-text-right.ant-divider-no-default-orientation-margin-right::after { width: 0; } .ant-divider-horizontal.ant-divider-with-text-right.ant-divider-no-default-orientation-margin-right .ant-divider-inner-text { padding-right: 0; } .ant-divider-rtl { direction: rtl; } .ant-divider-rtl.ant-divider-horizontal.ant-divider-with-text-left::before { width: 95%; } .ant-divider-rtl.ant-divider-horizontal.ant-divider-with-text-left::after { width: 5%; } .ant-divider-rtl.ant-divider-horizontal.ant-divider-with-text-right::before { width: 5%; } .ant-divider-rtl.ant-divider-horizontal.ant-divider-with-text-right::after { width: 95%; } .ant-drawer { position: fixed; top: 0; right: 0; bottom: 0; left: 0; z-index: 1000; pointer-events: none; } .ant-drawer-inline { position: absolute; } .ant-drawer-mask { position: absolute; top: 0; right: 0; bottom: 0; left: 0; z-index: 1000; background: rgba(0, 0, 0, 0.45); pointer-events: auto; } .ant-drawer-content-wrapper { position: absolute; z-index: 1000; transition: all 0.3s; } .ant-drawer-content-wrapper-hidden { display: none; } .ant-drawer-left > .ant-drawer-content-wrapper { top: 0; bottom: 0; left: 0; box-shadow: 6px 0 16px -8px rgba(0, 0, 0, 0.32), 9px 0 28px 0 rgba(0, 0, 0, 0.2), 12px 0 48px 16px rgba(0, 0, 0, 0.12); } .ant-drawer-right > .ant-drawer-content-wrapper { top: 0; right: 0; bottom: 0; box-shadow: -6px 0 16px -8px rgba(0, 0, 0, 0.08), -9px 0 28px 0 rgba(0, 0, 0, 0.05), -12px 0 48px 16px rgba(0, 0, 0, 0.03); } .ant-drawer-top > .ant-drawer-content-wrapper { top: 0; right: 0; left: 0; box-shadow: 0 6px 16px -8px rgba(0, 0, 0, 0.32), 0 9px 28px 0 rgba(0, 0, 0, 0.2), 0 12px 48px 16px rgba(0, 0, 0, 0.12); } .ant-drawer-bottom > .ant-drawer-content-wrapper { right: 0; bottom: 0; left: 0; box-shadow: 0 -6px 16px -8px rgba(0, 0, 0, 0.32), 0 -9px 28px 0 rgba(0, 0, 0, 0.2), 0 -12px 48px 16px rgba(0, 0, 0, 0.12); } .ant-drawer-content { width: 100%; height: 100%; overflow: auto; background: #1f1f1f; pointer-events: auto; } .ant-drawer-wrapper-body { display: flex; flex-direction: column; width: 100%; height: 100%; } .ant-drawer-header { display: flex; flex: 0; align-items: center; padding: 16px 24px; font-size: 16px; line-height: 22px; border-bottom: 1px solid #303030; } .ant-drawer-header-title { display: flex; flex: 1; align-items: center; min-width: 0; min-height: 0; } .ant-drawer-extra { flex: none; } .ant-drawer-close { display: inline-block; margin-right: 12px; color: rgba(255, 255, 255, 0.45); font-weight: 700; font-size: 16px; font-style: normal; line-height: 1; text-align: center; text-transform: none; text-decoration: none; background: transparent; border: 0; outline: 0; cursor: pointer; transition: color 0.3s; text-rendering: auto; } .ant-drawer-close:focus, .ant-drawer-close:hover { color: rgba(255, 255, 255, 0.75); text-decoration: none; } .ant-drawer-title { flex: 1; margin: 0; color: rgba(255, 255, 255, 0.85); font-weight: 500; font-size: 16px; line-height: 22px; } .ant-drawer-body { flex: 1; min-width: 0; min-height: 0; padding: 24px; overflow: auto; } .ant-drawer-footer { flex-shrink: 0; padding: 10px 16px; border-top: 1px solid #303030; } .panel-motion-enter-start, .panel-motion-appear-start, .panel-motion-leave-start { transition: none; } .panel-motion-enter-active, .panel-motion-appear-active, .panel-motion-leave-active { transition: all 0.3s; } .ant-drawer-mask-motion-enter-active, .ant-drawer-mask-motion-appear-active, .ant-drawer-mask-motion-leave-active { transition: all 0.3s; } .ant-drawer-mask-motion-enter, .ant-drawer-mask-motion-appear { opacity: 0; } .ant-drawer-mask-motion-enter-active, .ant-drawer-mask-motion-appear-active { opacity: 1; } .ant-drawer-mask-motion-leave { opacity: 1; } .ant-drawer-mask-motion-leave-active { opacity: 0; } .ant-drawer-panel-motion-left-enter-start, .ant-drawer-panel-motion-left-appear-start, .ant-drawer-panel-motion-left-leave-start { transition: none; } .ant-drawer-panel-motion-left-enter-active, .ant-drawer-panel-motion-left-appear-active, .ant-drawer-panel-motion-left-leave-active { transition: all 0.3s; } .ant-drawer-panel-motion-left-enter-start, .ant-drawer-panel-motion-left-appear-start { transform: translateX(-100%) !important; } .ant-drawer-panel-motion-left-enter-active, .ant-drawer-panel-motion-left-appear-active { transform: translateX(0); } .ant-drawer-panel-motion-left-leave { transform: translateX(0); } .ant-drawer-panel-motion-left-leave-active { transform: translateX(-100%); } .ant-drawer-panel-motion-right-enter-start, .ant-drawer-panel-motion-right-appear-start, .ant-drawer-panel-motion-right-leave-start { transition: none; } .ant-drawer-panel-motion-right-enter-active, .ant-drawer-panel-motion-right-appear-active, .ant-drawer-panel-motion-right-leave-active { transition: all 0.3s; } .ant-drawer-panel-motion-right-enter-start, .ant-drawer-panel-motion-right-appear-start { transform: translateX(100%) !important; } .ant-drawer-panel-motion-right-enter-active, .ant-drawer-panel-motion-right-appear-active { transform: translateX(0); } .ant-drawer-panel-motion-right-leave { transform: translateX(0); } .ant-drawer-panel-motion-right-leave-active { transform: translateX(100%); } .ant-drawer-panel-motion-top-enter-start, .ant-drawer-panel-motion-top-appear-start, .ant-drawer-panel-motion-top-leave-start { transition: none; } .ant-drawer-panel-motion-top-enter-active, .ant-drawer-panel-motion-top-appear-active, .ant-drawer-panel-motion-top-leave-active { transition: all 0.3s; } .ant-drawer-panel-motion-top-enter-start, .ant-drawer-panel-motion-top-appear-start { transform: translateY(-100%) !important; } .ant-drawer-panel-motion-top-enter-active, .ant-drawer-panel-motion-top-appear-active { transform: translateY(0); } .ant-drawer-panel-motion-top-leave { transform: translateY(0); } .ant-drawer-panel-motion-top-leave-active { transform: translateY(-100%); } .ant-drawer-panel-motion-bottom-enter-start, .ant-drawer-panel-motion-bottom-appear-start, .ant-drawer-panel-motion-bottom-leave-start { transition: none; } .ant-drawer-panel-motion-bottom-enter-active, .ant-drawer-panel-motion-bottom-appear-active, .ant-drawer-panel-motion-bottom-leave-active { transition: all 0.3s; } .ant-drawer-panel-motion-bottom-enter-start, .ant-drawer-panel-motion-bottom-appear-start { transform: translateY(100%) !important; } .ant-drawer-panel-motion-bottom-enter-active, .ant-drawer-panel-motion-bottom-appear-active { transform: translateY(0); } .ant-drawer-panel-motion-bottom-leave { transform: translateY(0); } .ant-drawer-panel-motion-bottom-leave-active { transform: translateY(100%); } .ant-drawer-rtl { direction: rtl; } .ant-drawer-rtl .ant-drawer-close { margin-right: 0; margin-left: 12px; } .ant-drawer .ant-picker-clear, .ant-drawer .ant-slider-handle, .ant-drawer .ant-anchor-wrapper, .ant-drawer .ant-collapse-content, .ant-drawer .ant-timeline-item-head, .ant-drawer .ant-card { background-color: #1f1f1f; } .ant-drawer .ant-transfer-list-header { background: #1f1f1f; border-bottom: 1px solid #3a3a3a; } .ant-drawer .ant-transfer-list-content-item:not(.ant-transfer-list-content-item-disabled):hover { background-color: rgba(255, 255, 255, 0.08); } .ant-drawer tr.ant-table-expanded-row > td, .ant-drawer tr.ant-table-expanded-row:hover > td { background: #272727; } .ant-drawer .ant-table.ant-table-small thead > tr > th { background-color: #1f1f1f; border-bottom: 1px solid #3a3a3a; } .ant-drawer .ant-table { background-color: #1f1f1f; } .ant-drawer .ant-table .ant-table-row-expand-icon { border: 1px solid #3a3a3a; } .ant-drawer .ant-table tfoot > tr > th, .ant-drawer .ant-table tfoot > tr > td { border-bottom: 1px solid #3a3a3a; } .ant-drawer .ant-table thead > tr > th { background-color: #272727; border-bottom: 1px solid #3a3a3a; } .ant-drawer .ant-table tbody > tr > td { border-bottom: 1px solid #3a3a3a; } .ant-drawer .ant-table tbody > tr > td.ant-table-cell-fix-left, .ant-drawer .ant-table tbody > tr > td.ant-table-cell-fix-right { background-color: #1f1f1f; } .ant-drawer .ant-table tbody > tr.ant-table-row:hover > td { background: #303030; } .ant-drawer .ant-table.ant-table-bordered .ant-table-title { border: 1px solid #3a3a3a; } .ant-drawer .ant-table.ant-table-bordered thead > tr > th, .ant-drawer .ant-table.ant-table-bordered tbody > tr > td, .ant-drawer .ant-table.ant-table-bordered tfoot > tr > th, .ant-drawer .ant-table.ant-table-bordered tfoot > tr > td { border-right: 1px solid #3a3a3a; } .ant-drawer .ant-table.ant-table-bordered .ant-table-cell-fix-right-first::after { border-right: 1px solid #3a3a3a; } .ant-drawer .ant-table.ant-table-bordered table thead > tr:not(:last-child) > th { border-bottom: 1px solid #303030; } .ant-drawer .ant-table.ant-table-bordered .ant-table-container { border: 1px solid #3a3a3a; } .ant-drawer .ant-table.ant-table-bordered .ant-table-expanded-row-fixed::after { border-right: 1px solid #3a3a3a; } .ant-drawer .ant-table.ant-table-bordered .ant-table-footer { border: 1px solid #3a3a3a; } .ant-drawer .ant-table .ant-table-filter-trigger-container-open { background-color: #525252; } .ant-drawer .ant-picker-calendar-full { background-color: #1f1f1f; } .ant-drawer .ant-picker-calendar-full .ant-picker-panel { background-color: #1f1f1f; } .ant-drawer .ant-picker-calendar-full .ant-picker-panel .ant-picker-calendar-date { border-top: 2px solid #3a3a3a; } .ant-drawer .ant-tabs.ant-tabs-card .ant-tabs-card-bar .ant-tabs-tab-active { background-color: #1f1f1f; border-bottom: 1px solid #1f1f1f; } .ant-drawer .ant-badge-count { box-shadow: 0 0 0 1px #1f1f1f; } .ant-drawer .ant-tree-show-line .ant-tree-switcher { background: #1f1f1f; } .ant-dropdown-menu-item.ant-dropdown-menu-item-danger { color: #a61d24; } .ant-dropdown-menu-item.ant-dropdown-menu-item-danger:hover { color: #fff; background-color: #a61d24; } .ant-dropdown { box-sizing: border-box; margin: 0; padding: 0; color: rgba(255, 255, 255, 0.85); font-size: 14px; font-variant: tabular-nums; line-height: 1.5715; list-style: none; font-feature-settings: 'tnum'; position: absolute; top: -9999px; left: -9999px; z-index: 1050; display: block; } .ant-dropdown::before { position: absolute; top: -4px; right: 0; bottom: -4px; left: -7px; z-index: -9999; opacity: 0.0001; content: ' '; } .ant-dropdown-wrap { position: relative; } .ant-dropdown-wrap .ant-btn > .anticon-down { font-size: 10px; } .ant-dropdown-wrap .anticon-down::before { transition: transform 0.2s; } .ant-dropdown-wrap-open .anticon-down::before { transform: rotate(180deg); } .ant-dropdown-hidden, .ant-dropdown-menu-hidden, .ant-dropdown-menu-submenu-hidden { display: none; } .ant-dropdown-show-arrow.ant-dropdown-placement-topLeft, .ant-dropdown-show-arrow.ant-dropdown-placement-top, .ant-dropdown-show-arrow.ant-dropdown-placement-topRight { padding-bottom: 15.3137085px; } .ant-dropdown-show-arrow.ant-dropdown-placement-bottomLeft, .ant-dropdown-show-arrow.ant-dropdown-placement-bottom, .ant-dropdown-show-arrow.ant-dropdown-placement-bottomRight { padding-top: 15.3137085px; } .ant-dropdown-arrow { position: absolute; z-index: 1; display: block; width: 11.3137085px; height: 11.3137085px; border-radius: 0 0 2px; pointer-events: none; } .ant-dropdown-arrow::before { position: absolute; top: -11.3137085px; left: -11.3137085px; width: 33.9411255px; height: 33.9411255px; background: #1f1f1f; background-repeat: no-repeat; background-position: -10px -10px; content: ''; clip-path: inset(33% 33%); clip-path: path('M 9.849242404917499 24.091883092036785 A 5 5 0 0 1 13.384776310850237 22.627416997969522 L 20.627416997969522 22.627416997969522 A 2 2 0 0 0 22.627416997969522 20.627416997969522 L 22.627416997969522 13.384776310850237 A 5 5 0 0 1 24.091883092036785 9.849242404917499 L 23.091883092036785 9.849242404917499 L 9.849242404917499 23.091883092036785 Z'); } .ant-dropdown-placement-top > .ant-dropdown-arrow, .ant-dropdown-placement-topLeft > .ant-dropdown-arrow, .ant-dropdown-placement-topRight > .ant-dropdown-arrow { bottom: 10px; box-shadow: 3px 3px 7px -3px rgba(0, 0, 0, 0.1); transform: rotate(45deg); } .ant-dropdown-placement-top > .ant-dropdown-arrow { left: 50%; transform: translateX(-50%) rotate(45deg); } .ant-dropdown-placement-topLeft > .ant-dropdown-arrow { left: 16px; } .ant-dropdown-placement-topRight > .ant-dropdown-arrow { right: 16px; } .ant-dropdown-placement-bottom > .ant-dropdown-arrow, .ant-dropdown-placement-bottomLeft > .ant-dropdown-arrow, .ant-dropdown-placement-bottomRight > .ant-dropdown-arrow { top: 9.41421356px; box-shadow: 2px 2px 5px -2px rgba(0, 0, 0, 0.1); transform: rotate(-135deg) translateY(-0.5px); } .ant-dropdown-placement-bottom > .ant-dropdown-arrow { left: 50%; transform: translateX(-50%) rotate(-135deg) translateY(-0.5px); } .ant-dropdown-placement-bottomLeft > .ant-dropdown-arrow { left: 16px; } .ant-dropdown-placement-bottomRight > .ant-dropdown-arrow { right: 16px; } .ant-dropdown-menu { position: relative; margin: 0; padding: 4px 0; text-align: left; list-style-type: none; background-color: #1f1f1f; background-clip: padding-box; border-radius: 2px; outline: none; box-shadow: 0 3px 6px -4px rgba(0, 0, 0, 0.48), 0 6px 16px 0 rgba(0, 0, 0, 0.32), 0 9px 28px 8px rgba(0, 0, 0, 0.2); } .ant-dropdown-menu-item-group-title { padding: 5px 12px; color: rgba(255, 255, 255, 0.45); transition: all 0.3s; } .ant-dropdown-menu-submenu-popup { position: absolute; z-index: 1050; background: transparent; box-shadow: none; transform-origin: 0 0; } .ant-dropdown-menu-submenu-popup ul, .ant-dropdown-menu-submenu-popup li { list-style: none; } .ant-dropdown-menu-submenu-popup ul { margin-right: 0.3em; margin-left: 0.3em; } .ant-dropdown-menu-item { position: relative; display: flex; align-items: center; } .ant-dropdown-menu-item-icon { min-width: 12px; margin-right: 8px; font-size: 12px; } .ant-dropdown-menu-title-content { flex: auto; } .ant-dropdown-menu-title-content > a { color: inherit; transition: all 0.3s; } .ant-dropdown-menu-title-content > a:hover { color: inherit; } .ant-dropdown-menu-title-content > a::after { position: absolute; top: 0; right: 0; bottom: 0; left: 0; content: ''; } .ant-dropdown-menu-item, .ant-dropdown-menu-submenu-title { clear: both; margin: 0; padding: 5px 12px; color: rgba(255, 255, 255, 0.85); font-weight: normal; font-size: 14px; line-height: 22px; cursor: pointer; transition: all 0.3s; } .ant-dropdown-menu-item-selected, .ant-dropdown-menu-submenu-title-selected { color: #177ddc; background-color: #111b26; } .ant-dropdown-menu-item:hover, .ant-dropdown-menu-submenu-title:hover, .ant-dropdown-menu-item.ant-dropdown-menu-item-active, .ant-dropdown-menu-item.ant-dropdown-menu-submenu-title-active, .ant-dropdown-menu-submenu-title.ant-dropdown-menu-item-active, .ant-dropdown-menu-submenu-title.ant-dropdown-menu-submenu-title-active { background-color: rgba(255, 255, 255, 0.08); } .ant-dropdown-menu-item.ant-dropdown-menu-item-disabled, .ant-dropdown-menu-item.ant-dropdown-menu-submenu-title-disabled, .ant-dropdown-menu-submenu-title.ant-dropdown-menu-item-disabled, .ant-dropdown-menu-submenu-title.ant-dropdown-menu-submenu-title-disabled { color: rgba(255, 255, 255, 0.3); cursor: not-allowed; } .ant-dropdown-menu-item.ant-dropdown-menu-item-disabled:hover, .ant-dropdown-menu-item.ant-dropdown-menu-submenu-title-disabled:hover, .ant-dropdown-menu-submenu-title.ant-dropdown-menu-item-disabled:hover, .ant-dropdown-menu-submenu-title.ant-dropdown-menu-submenu-title-disabled:hover { color: rgba(255, 255, 255, 0.3); background-color: transparent; cursor: not-allowed; } .ant-dropdown-menu-item.ant-dropdown-menu-item-disabled a, .ant-dropdown-menu-item.ant-dropdown-menu-submenu-title-disabled a, .ant-dropdown-menu-submenu-title.ant-dropdown-menu-item-disabled a, .ant-dropdown-menu-submenu-title.ant-dropdown-menu-submenu-title-disabled a { pointer-events: none; } .ant-dropdown-menu-item-divider, .ant-dropdown-menu-submenu-title-divider { height: 1px; margin: 4px 0; overflow: hidden; line-height: 0; background-color: #303030; } .ant-dropdown-menu-item .ant-dropdown-menu-submenu-expand-icon, .ant-dropdown-menu-submenu-title .ant-dropdown-menu-submenu-expand-icon { position: absolute; right: 8px; } .ant-dropdown-menu-item .ant-dropdown-menu-submenu-expand-icon .ant-dropdown-menu-submenu-arrow-icon, .ant-dropdown-menu-submenu-title .ant-dropdown-menu-submenu-expand-icon .ant-dropdown-menu-submenu-arrow-icon { margin-right: 0 !important; color: rgba(255, 255, 255, 0.45); font-size: 10px; font-style: normal; } .ant-dropdown-menu-item-group-list { margin: 0 8px; padding: 0; list-style: none; } .ant-dropdown-menu-submenu-title { padding-right: 24px; } .ant-dropdown-menu-submenu-vertical { position: relative; } .ant-dropdown-menu-submenu-vertical > .ant-dropdown-menu { position: absolute; top: 0; left: 100%; min-width: 100%; margin-left: 4px; transform-origin: 0 0; } .ant-dropdown-menu-submenu.ant-dropdown-menu-submenu-disabled .ant-dropdown-menu-submenu-title, .ant-dropdown-menu-submenu.ant-dropdown-menu-submenu-disabled .ant-dropdown-menu-submenu-title .ant-dropdown-menu-submenu-arrow-icon { color: rgba(255, 255, 255, 0.3); background-color: transparent; cursor: not-allowed; } .ant-dropdown-menu-submenu-selected .ant-dropdown-menu-submenu-title { color: #177ddc; } .ant-dropdown.ant-slide-down-enter.ant-slide-down-enter-active.ant-dropdown-placement-bottomLeft, .ant-dropdown.ant-slide-down-appear.ant-slide-down-appear-active.ant-dropdown-placement-bottomLeft, .ant-dropdown.ant-slide-down-enter.ant-slide-down-enter-active.ant-dropdown-placement-bottom, .ant-dropdown.ant-slide-down-appear.ant-slide-down-appear-active.ant-dropdown-placement-bottom, .ant-dropdown.ant-slide-down-enter.ant-slide-down-enter-active.ant-dropdown-placement-bottomRight, .ant-dropdown.ant-slide-down-appear.ant-slide-down-appear-active.ant-dropdown-placement-bottomRight { animation-name: antSlideUpIn; } .ant-dropdown.ant-slide-up-enter.ant-slide-up-enter-active.ant-dropdown-placement-topLeft, .ant-dropdown.ant-slide-up-appear.ant-slide-up-appear-active.ant-dropdown-placement-topLeft, .ant-dropdown.ant-slide-up-enter.ant-slide-up-enter-active.ant-dropdown-placement-top, .ant-dropdown.ant-slide-up-appear.ant-slide-up-appear-active.ant-dropdown-placement-top, .ant-dropdown.ant-slide-up-enter.ant-slide-up-enter-active.ant-dropdown-placement-topRight, .ant-dropdown.ant-slide-up-appear.ant-slide-up-appear-active.ant-dropdown-placement-topRight { animation-name: antSlideDownIn; } .ant-dropdown.ant-slide-down-leave.ant-slide-down-leave-active.ant-dropdown-placement-bottomLeft, .ant-dropdown.ant-slide-down-leave.ant-slide-down-leave-active.ant-dropdown-placement-bottom, .ant-dropdown.ant-slide-down-leave.ant-slide-down-leave-active.ant-dropdown-placement-bottomRight { animation-name: antSlideUpOut; } .ant-dropdown.ant-slide-up-leave.ant-slide-up-leave-active.ant-dropdown-placement-topLeft, .ant-dropdown.ant-slide-up-leave.ant-slide-up-leave-active.ant-dropdown-placement-top, .ant-dropdown.ant-slide-up-leave.ant-slide-up-leave-active.ant-dropdown-placement-topRight { animation-name: antSlideDownOut; } .ant-dropdown-trigger > .anticon.anticon-down, .ant-dropdown-link > .anticon.anticon-down, .ant-dropdown-button > .anticon.anticon-down { font-size: 10px; vertical-align: baseline; } .ant-dropdown-button { white-space: nowrap; } .ant-dropdown-button.ant-btn-group > .ant-btn-loading, .ant-dropdown-button.ant-btn-group > .ant-btn-loading + .ant-btn { cursor: default; pointer-events: none; } .ant-dropdown-button.ant-btn-group > .ant-btn-loading + .ant-btn::before { display: block; } .ant-dropdown-button.ant-btn-group > .ant-btn:last-child:not(:first-child):not(.ant-btn-icon-only) { padding-right: 8px; padding-left: 8px; } .ant-dropdown-menu-dark, .ant-dropdown-menu-dark .ant-dropdown-menu { background: #1f1f1f; } .ant-dropdown-menu-dark .ant-dropdown-menu-item, .ant-dropdown-menu-dark .ant-dropdown-menu-submenu-title, .ant-dropdown-menu-dark .ant-dropdown-menu-item > a, .ant-dropdown-menu-dark .ant-dropdown-menu-item > .anticon + span > a { color: rgba(255, 255, 255, 0.65); } .ant-dropdown-menu-dark .ant-dropdown-menu-item .ant-dropdown-menu-submenu-arrow::after, .ant-dropdown-menu-dark .ant-dropdown-menu-submenu-title .ant-dropdown-menu-submenu-arrow::after, .ant-dropdown-menu-dark .ant-dropdown-menu-item > a .ant-dropdown-menu-submenu-arrow::after, .ant-dropdown-menu-dark .ant-dropdown-menu-item > .anticon + span > a .ant-dropdown-menu-submenu-arrow::after { color: rgba(255, 255, 255, 0.65); } .ant-dropdown-menu-dark .ant-dropdown-menu-item:hover, .ant-dropdown-menu-dark .ant-dropdown-menu-submenu-title:hover, .ant-dropdown-menu-dark .ant-dropdown-menu-item > a:hover, .ant-dropdown-menu-dark .ant-dropdown-menu-item > .anticon + span > a:hover { color: #fff; background: transparent; } .ant-dropdown-menu-dark .ant-dropdown-menu-item-selected, .ant-dropdown-menu-dark .ant-dropdown-menu-item-selected:hover, .ant-dropdown-menu-dark .ant-dropdown-menu-item-selected > a { color: #fff; background: #177ddc; } .ant-dropdown-rtl { direction: rtl; } .ant-dropdown-rtl.ant-dropdown::before { right: -7px; left: 0; } .ant-dropdown-menu.ant-dropdown-menu-rtl { direction: rtl; text-align: right; } .ant-dropdown-rtl .ant-dropdown-menu-item-group-title, .ant-dropdown-menu-submenu-rtl .ant-dropdown-menu-item-group-title { direction: rtl; text-align: right; } .ant-dropdown-menu-submenu-popup.ant-dropdown-menu-submenu-rtl { transform-origin: 100% 0; } .ant-dropdown-rtl .ant-dropdown-menu-submenu-popup ul, .ant-dropdown-rtl .ant-dropdown-menu-submenu-popup li { text-align: right; } .ant-dropdown-rtl .ant-dropdown-menu-item, .ant-dropdown-rtl .ant-dropdown-menu-submenu-title { text-align: right; } .ant-dropdown-rtl .ant-dropdown-menu-item > .anticon:first-child, .ant-dropdown-rtl .ant-dropdown-menu-submenu-title > .anticon:first-child, .ant-dropdown-rtl .ant-dropdown-menu-item > span > .anticon:first-child, .ant-dropdown-rtl .ant-dropdown-menu-submenu-title > span > .anticon:first-child { margin-right: 0; margin-left: 8px; } .ant-dropdown-rtl .ant-dropdown-menu-item .ant-dropdown-menu-submenu-expand-icon, .ant-dropdown-rtl .ant-dropdown-menu-submenu-title .ant-dropdown-menu-submenu-expand-icon { right: auto; left: 8px; } .ant-dropdown-rtl .ant-dropdown-menu-item .ant-dropdown-menu-submenu-expand-icon .ant-dropdown-menu-submenu-arrow-icon, .ant-dropdown-rtl .ant-dropdown-menu-submenu-title .ant-dropdown-menu-submenu-expand-icon .ant-dropdown-menu-submenu-arrow-icon { margin-left: 0 !important; transform: scaleX(-1); } .ant-dropdown-rtl .ant-dropdown-menu-submenu-title { padding-right: 12px; padding-left: 24px; } .ant-dropdown-rtl .ant-dropdown-menu-submenu-vertical > .ant-dropdown-menu { right: 100%; left: 0; margin-right: 4px; margin-left: 0; } .ant-empty { margin: 0 8px; font-size: 14px; line-height: 1.5715; text-align: center; } .ant-empty-image { height: 100px; margin-bottom: 8px; } .ant-empty-image img { height: 100%; } .ant-empty-image svg { height: 100%; margin: auto; } .ant-empty-footer { margin-top: 16px; } .ant-empty-normal { margin: 32px 0; color: rgba(255, 255, 255, 0.3); } .ant-empty-normal .ant-empty-image { height: 40px; } .ant-empty-small { margin: 8px 0; color: rgba(255, 255, 255, 0.3); } .ant-empty-small .ant-empty-image { height: 35px; } .ant-empty-img-default-ellipse { fill: #fff; fill-opacity: 0.08; } .ant-empty-img-default-path-1 { fill: #262626; } .ant-empty-img-default-path-2 { fill: url('#linearGradient-1'); } .ant-empty-img-default-path-3 { fill: #595959; } .ant-empty-img-default-path-4 { fill: #434343; } .ant-empty-img-default-path-5 { fill: #595959; } .ant-empty-img-default-g { fill: #434343; } .ant-empty-img-simple-ellipse { fill: #fff; fill-opacity: 0.08; } .ant-empty-img-simple-g { stroke: #434343; } .ant-empty-img-simple-path { fill: #262626; stroke: #434343; } .ant-empty-rtl { direction: rtl; } .ant-form-item .ant-input-number + .ant-form-text { margin-left: 8px; } .ant-form-inline { display: flex; flex-wrap: wrap; } .ant-form-inline .ant-form-item { flex: none; flex-wrap: nowrap; margin-right: 16px; margin-bottom: 0; } .ant-form-inline .ant-form-item-with-help { margin-bottom: 24px; } .ant-form-inline .ant-form-item > .ant-form-item-label, .ant-form-inline .ant-form-item > .ant-form-item-control { display: inline-block; vertical-align: top; } .ant-form-inline .ant-form-item > .ant-form-item-label { flex: none; } .ant-form-inline .ant-form-item .ant-form-text { display: inline-block; } .ant-form-inline .ant-form-item .ant-form-item-has-feedback { display: inline-block; } .ant-form-horizontal .ant-form-item-label { flex-grow: 0; } .ant-form-horizontal .ant-form-item-control { flex: 1 1 0; min-width: 0; } .ant-form-horizontal .ant-form-item-label[class$='-24'] + .ant-form-item-control, .ant-form-horizontal .ant-form-item-label[class*='-24 '] + .ant-form-item-control { min-width: unset; } .ant-form-vertical .ant-form-item-row { flex-direction: column; } .ant-form-vertical .ant-form-item-label > label { height: auto; } .ant-form-vertical .ant-form-item .ant-form-item-control { width: 100%; } .ant-form-vertical .ant-form-item-label, .ant-col-24.ant-form-item-label, .ant-col-xl-24.ant-form-item-label { padding: 0 0 8px; line-height: 1.5715; white-space: initial; text-align: left; } .ant-form-vertical .ant-form-item-label > label, .ant-col-24.ant-form-item-label > label, .ant-col-xl-24.ant-form-item-label > label { margin: 0; } .ant-form-vertical .ant-form-item-label > label::after, .ant-col-24.ant-form-item-label > label::after, .ant-col-xl-24.ant-form-item-label > label::after { display: none; } .ant-form-rtl.ant-form-vertical .ant-form-item-label, .ant-form-rtl.ant-col-24.ant-form-item-label, .ant-form-rtl.ant-col-xl-24.ant-form-item-label { text-align: right; } @media (max-width: 575px) { .ant-form-item .ant-form-item-label { padding: 0 0 8px; line-height: 1.5715; white-space: initial; text-align: left; } .ant-form-item .ant-form-item-label > label { margin: 0; } .ant-form-item .ant-form-item-label > label::after { display: none; } .ant-form-rtl.ant-form-item .ant-form-item-label { text-align: right; } .ant-form .ant-form-item { flex-wrap: wrap; } .ant-form .ant-form-item .ant-form-item-label, .ant-form .ant-form-item .ant-form-item-control { flex: 0 0 100%; max-width: 100%; } .ant-col-xs-24.ant-form-item-label { padding: 0 0 8px; line-height: 1.5715; white-space: initial; text-align: left; } .ant-col-xs-24.ant-form-item-label > label { margin: 0; } .ant-col-xs-24.ant-form-item-label > label::after { display: none; } .ant-form-rtl.ant-col-xs-24.ant-form-item-label { text-align: right; } } @media (max-width: 767px) { .ant-col-sm-24.ant-form-item-label { padding: 0 0 8px; line-height: 1.5715; white-space: initial; text-align: left; } .ant-col-sm-24.ant-form-item-label > label { margin: 0; } .ant-col-sm-24.ant-form-item-label > label::after { display: none; } .ant-form-rtl.ant-col-sm-24.ant-form-item-label { text-align: right; } } @media (max-width: 991px) { .ant-col-md-24.ant-form-item-label { padding: 0 0 8px; line-height: 1.5715; white-space: initial; text-align: left; } .ant-col-md-24.ant-form-item-label > label { margin: 0; } .ant-col-md-24.ant-form-item-label > label::after { display: none; } .ant-form-rtl.ant-col-md-24.ant-form-item-label { text-align: right; } } @media (max-width: 1199px) { .ant-col-lg-24.ant-form-item-label { padding: 0 0 8px; line-height: 1.5715; white-space: initial; text-align: left; } .ant-col-lg-24.ant-form-item-label > label { margin: 0; } .ant-col-lg-24.ant-form-item-label > label::after { display: none; } .ant-form-rtl.ant-col-lg-24.ant-form-item-label { text-align: right; } } @media (max-width: 1599px) { .ant-col-xl-24.ant-form-item-label { padding: 0 0 8px; line-height: 1.5715; white-space: initial; text-align: left; } .ant-col-xl-24.ant-form-item-label > label { margin: 0; } .ant-col-xl-24.ant-form-item-label > label::after { display: none; } .ant-form-rtl.ant-col-xl-24.ant-form-item-label { text-align: right; } } .ant-form-item { /* Some non-status related component style is in `components.less` */ /* To support leave along ErrorList. We add additional className to handle explain style */ } .ant-form-item-explain-error { color: #a61d24; } .ant-form-item-explain-warning { color: #d89614; } .ant-form-item-has-feedback .ant-switch { margin: 2px 0 4px; } .ant-form-item-has-warning .ant-form-item-split { color: #d89614; } .ant-form-item-has-error .ant-form-item-split { color: #a61d24; } .ant-form { box-sizing: border-box; margin: 0; padding: 0; color: rgba(255, 255, 255, 0.85); font-size: 14px; font-variant: tabular-nums; line-height: 1.5715; list-style: none; font-feature-settings: 'tnum'; } .ant-form legend { display: block; width: 100%; margin-bottom: 20px; padding: 0; color: rgba(255, 255, 255, 0.45); font-size: 16px; line-height: inherit; border: 0; border-bottom: 1px solid #434343; } .ant-form label { font-size: 14px; } .ant-form input[type='search'] { box-sizing: border-box; } .ant-form input[type='radio'], .ant-form input[type='checkbox'] { line-height: normal; } .ant-form input[type='file'] { display: block; } .ant-form input[type='range'] { display: block; width: 100%; } .ant-form select[multiple], .ant-form select[size] { height: auto; } .ant-form input[type='file']:focus, .ant-form input[type='radio']:focus, .ant-form input[type='checkbox']:focus { outline: thin dotted; outline: 5px auto -webkit-focus-ring-color; outline-offset: -2px; } .ant-form output { display: block; padding-top: 15px; color: rgba(255, 255, 255, 0.85); font-size: 14px; line-height: 1.5715; } .ant-form .ant-form-text { display: inline-block; padding-right: 8px; } .ant-form-small .ant-form-item-label > label { height: 24px; } .ant-form-small .ant-form-item-control-input { min-height: 24px; } .ant-form-large .ant-form-item-label > label { height: 40px; } .ant-form-large .ant-form-item-control-input { min-height: 40px; } .ant-form-item { box-sizing: border-box; margin: 0; padding: 0; color: rgba(255, 255, 255, 0.85); font-size: 14px; font-variant: tabular-nums; line-height: 1.5715; list-style: none; font-feature-settings: 'tnum'; margin-bottom: 24px; vertical-align: top; } .ant-form-item-with-help { transition: none; } .ant-form-item-hidden, .ant-form-item-hidden.ant-row { display: none; } .ant-form-item-label { display: inline-block; flex-grow: 0; overflow: hidden; white-space: nowrap; text-align: right; vertical-align: middle; } .ant-form-item-label-left { text-align: left; } .ant-form-item-label-wrap { overflow: unset; line-height: 1.3215em; white-space: unset; } .ant-form-item-label > label { position: relative; display: inline-flex; align-items: center; max-width: 100%; height: 32px; color: rgba(255, 255, 255, 0.85); font-size: 14px; } .ant-form-item-label > label > .anticon { font-size: 14px; vertical-align: top; } .ant-form-item-label > label.ant-form-item-required:not(.ant-form-item-required-mark-optional)::before { display: inline-block; margin-right: 4px; color: #a61d24; font-size: 14px; font-family: SimSun, sans-serif; line-height: 1; content: '*'; } .ant-form-hide-required-mark .ant-form-item-label > label.ant-form-item-required:not(.ant-form-item-required-mark-optional)::before { display: none; } .ant-form-item-label > label .ant-form-item-optional { display: inline-block; margin-left: 4px; color: rgba(255, 255, 255, 0.45); } .ant-form-hide-required-mark .ant-form-item-label > label .ant-form-item-optional { display: none; } .ant-form-item-label > label .ant-form-item-tooltip { color: rgba(255, 255, 255, 0.45); cursor: help; writing-mode: horizontal-tb; margin-inline-start: 4px; } .ant-form-item-label > label::after { content: ':'; position: relative; top: -0.5px; margin: 0 8px 0 2px; } .ant-form-item-label > label.ant-form-item-no-colon::after { content: ' '; } .ant-form-item-control { display: flex; flex-direction: column; flex-grow: 1; } .ant-form-item-control:first-child:not([class^='ant-col-']):not([class*=' ant-col-']) { width: 100%; } .ant-form-item-control-input { position: relative; display: flex; align-items: center; min-height: 32px; } .ant-form-item-control-input-content { flex: auto; max-width: 100%; } .ant-form-item-explain, .ant-form-item-extra { clear: both; color: rgba(255, 255, 255, 0.45); font-size: 14px; line-height: 1.5715; transition: color 0.3s cubic-bezier(0.215, 0.61, 0.355, 1); } .ant-form-item-explain-connected { width: 100%; } .ant-form-item-extra { min-height: 24px; } .ant-form-item-with-help .ant-form-item-explain { height: auto; opacity: 1; } .ant-form-item-feedback-icon { font-size: 14px; text-align: center; visibility: visible; animation: zoomIn 0.3s cubic-bezier(0.12, 0.4, 0.29, 1.46); pointer-events: none; } .ant-form-item-feedback-icon-success { color: #49aa19; } .ant-form-item-feedback-icon-error { color: #a61d24; } .ant-form-item-feedback-icon-warning { color: #d89614; } .ant-form-item-feedback-icon-validating { color: #177ddc; } .ant-show-help { transition: opacity 0.3s cubic-bezier(0.645, 0.045, 0.355, 1); } .ant-show-help-appear, .ant-show-help-enter { opacity: 0; } .ant-show-help-appear-active, .ant-show-help-enter-active { opacity: 1; } .ant-show-help-leave { opacity: 1; } .ant-show-help-leave-active { opacity: 0; } .ant-show-help-item { overflow: hidden; transition: height 0.3s cubic-bezier(0.645, 0.045, 0.355, 1), opacity 0.3s cubic-bezier(0.645, 0.045, 0.355, 1), transform 0.3s cubic-bezier(0.645, 0.045, 0.355, 1) !important; } .ant-show-help-item-appear, .ant-show-help-item-enter { transform: translateY(-5px); opacity: 0; } .ant-show-help-item-appear-active, .ant-show-help-item-enter-active { transform: translateY(0); opacity: 1; } .ant-show-help-item-leave { transition: height 0.2s cubic-bezier(0.645, 0.045, 0.355, 1), opacity 0.2s cubic-bezier(0.645, 0.045, 0.355, 1), transform 0.2s cubic-bezier(0.645, 0.045, 0.355, 1) !important; } .ant-show-help-item-leave-active { transform: translateY(-5px); } @keyframes diffZoomIn1 { 0% { transform: scale(0); opacity: 0; } 100% { transform: scale(1); opacity: 1; } } @keyframes diffZoomIn2 { 0% { transform: scale(0); opacity: 0; } 100% { transform: scale(1); opacity: 1; } } @keyframes diffZoomIn3 { 0% { transform: scale(0); opacity: 0; } 100% { transform: scale(1); opacity: 1; } } .ant-form-rtl { direction: rtl; } .ant-form-rtl .ant-form-item-label { text-align: left; } .ant-form-rtl .ant-form-item-label > label.ant-form-item-required::before { margin-right: 0; margin-left: 4px; } .ant-form-rtl .ant-form-item-label > label::after { margin: 0 2px 0 8px; } .ant-form-rtl .ant-form-item-label > label .ant-form-item-optional { margin-right: 4px; margin-left: 0; } .ant-col-rtl .ant-form-item-control:first-child { width: 100%; } .ant-form-rtl .ant-form-item-has-feedback .ant-input { padding-right: 11px; padding-left: 24px; } .ant-form-rtl .ant-form-item-has-feedback .ant-input-affix-wrapper .ant-input-suffix { padding-right: 11px; padding-left: 18px; } .ant-form-rtl .ant-form-item-has-feedback .ant-input-affix-wrapper .ant-input { padding: 0; } .ant-form-rtl .ant-form-item-has-feedback .ant-input-number-affix-wrapper .ant-input-number { padding: 0; } .ant-form-rtl .ant-form-item-has-feedback .ant-input-search:not(.ant-input-search-enter-button) .ant-input-suffix { right: auto; left: 28px; } .ant-form-rtl .ant-form-item-has-feedback .ant-input-number { padding-left: 18px; } .ant-form-rtl .ant-form-item-has-feedback > .ant-select .ant-select-arrow, .ant-form-rtl .ant-form-item-has-feedback > .ant-select .ant-select-clear, .ant-form-rtl .ant-form-item-has-feedback :not(.ant-input-group-addon) > .ant-select .ant-select-arrow, .ant-form-rtl .ant-form-item-has-feedback :not(.ant-input-group-addon) > .ant-select .ant-select-clear, .ant-form-rtl .ant-form-item-has-feedback :not(.ant-input-number-group-addon) > .ant-select .ant-select-arrow, .ant-form-rtl .ant-form-item-has-feedback :not(.ant-input-number-group-addon) > .ant-select .ant-select-clear { right: auto; left: 32px; } .ant-form-rtl .ant-form-item-has-feedback > .ant-select .ant-select-selection-selected-value, .ant-form-rtl .ant-form-item-has-feedback :not(.ant-input-group-addon) > .ant-select .ant-select-selection-selected-value, .ant-form-rtl .ant-form-item-has-feedback :not(.ant-input-number-group-addon) > .ant-select .ant-select-selection-selected-value { padding-right: 0; padding-left: 42px; } .ant-form-rtl .ant-form-item-has-feedback .ant-cascader-picker-arrow { margin-right: 0; margin-left: 19px; } .ant-form-rtl .ant-form-item-has-feedback .ant-cascader-picker-clear { right: auto; left: 32px; } .ant-form-rtl .ant-form-item-has-feedback .ant-picker { padding-right: 11px; padding-left: 29.2px; } .ant-form-rtl .ant-form-item-has-feedback .ant-picker-large { padding-right: 11px; padding-left: 29.2px; } .ant-form-rtl .ant-form-item-has-feedback .ant-picker-small { padding-right: 7px; padding-left: 25.2px; } .ant-form-rtl .ant-form-item-has-feedback.ant-form-item-has-success .ant-form-item-children-icon, .ant-form-rtl .ant-form-item-has-feedback.ant-form-item-has-warning .ant-form-item-children-icon, .ant-form-rtl .ant-form-item-has-feedback.ant-form-item-has-error .ant-form-item-children-icon, .ant-form-rtl .ant-form-item-has-feedback.ant-form-item-is-validating .ant-form-item-children-icon { right: auto; left: 0; } .ant-form-rtl.ant-form-inline .ant-form-item { margin-right: 0; margin-left: 16px; } .ant-row { display: flex; flex-flow: row wrap; min-width: 0; } .ant-row::before, .ant-row::after { display: flex; } .ant-row-no-wrap { flex-wrap: nowrap; } .ant-row-start { justify-content: flex-start; } .ant-row-center { justify-content: center; } .ant-row-end { justify-content: flex-end; } .ant-row-space-between { justify-content: space-between; } .ant-row-space-around { justify-content: space-around; } .ant-row-space-evenly { justify-content: space-evenly; } .ant-row-top { align-items: flex-start; } .ant-row-middle { align-items: center; } .ant-row-bottom { align-items: flex-end; } .ant-col { position: relative; max-width: 100%; min-height: 1px; } .ant-col-24 { display: block; flex: 0 0 100%; max-width: 100%; } .ant-col-push-24 { left: 100%; } .ant-col-pull-24 { right: 100%; } .ant-col-offset-24 { margin-left: 100%; } .ant-col-order-24 { order: 24; } .ant-col-23 { display: block; flex: 0 0 95.83333333%; max-width: 95.83333333%; } .ant-col-push-23 { left: 95.83333333%; } .ant-col-pull-23 { right: 95.83333333%; } .ant-col-offset-23 { margin-left: 95.83333333%; } .ant-col-order-23 { order: 23; } .ant-col-22 { display: block; flex: 0 0 91.66666667%; max-width: 91.66666667%; } .ant-col-push-22 { left: 91.66666667%; } .ant-col-pull-22 { right: 91.66666667%; } .ant-col-offset-22 { margin-left: 91.66666667%; } .ant-col-order-22 { order: 22; } .ant-col-21 { display: block; flex: 0 0 87.5%; max-width: 87.5%; } .ant-col-push-21 { left: 87.5%; } .ant-col-pull-21 { right: 87.5%; } .ant-col-offset-21 { margin-left: 87.5%; } .ant-col-order-21 { order: 21; } .ant-col-20 { display: block; flex: 0 0 83.33333333%; max-width: 83.33333333%; } .ant-col-push-20 { left: 83.33333333%; } .ant-col-pull-20 { right: 83.33333333%; } .ant-col-offset-20 { margin-left: 83.33333333%; } .ant-col-order-20 { order: 20; } .ant-col-19 { display: block; flex: 0 0 79.16666667%; max-width: 79.16666667%; } .ant-col-push-19 { left: 79.16666667%; } .ant-col-pull-19 { right: 79.16666667%; } .ant-col-offset-19 { margin-left: 79.16666667%; } .ant-col-order-19 { order: 19; } .ant-col-18 { display: block; flex: 0 0 75%; max-width: 75%; } .ant-col-push-18 { left: 75%; } .ant-col-pull-18 { right: 75%; } .ant-col-offset-18 { margin-left: 75%; } .ant-col-order-18 { order: 18; } .ant-col-17 { display: block; flex: 0 0 70.83333333%; max-width: 70.83333333%; } .ant-col-push-17 { left: 70.83333333%; } .ant-col-pull-17 { right: 70.83333333%; } .ant-col-offset-17 { margin-left: 70.83333333%; } .ant-col-order-17 { order: 17; } .ant-col-16 { display: block; flex: 0 0 66.66666667%; max-width: 66.66666667%; } .ant-col-push-16 { left: 66.66666667%; } .ant-col-pull-16 { right: 66.66666667%; } .ant-col-offset-16 { margin-left: 66.66666667%; } .ant-col-order-16 { order: 16; } .ant-col-15 { display: block; flex: 0 0 62.5%; max-width: 62.5%; } .ant-col-push-15 { left: 62.5%; } .ant-col-pull-15 { right: 62.5%; } .ant-col-offset-15 { margin-left: 62.5%; } .ant-col-order-15 { order: 15; } .ant-col-14 { display: block; flex: 0 0 58.33333333%; max-width: 58.33333333%; } .ant-col-push-14 { left: 58.33333333%; } .ant-col-pull-14 { right: 58.33333333%; } .ant-col-offset-14 { margin-left: 58.33333333%; } .ant-col-order-14 { order: 14; } .ant-col-13 { display: block; flex: 0 0 54.16666667%; max-width: 54.16666667%; } .ant-col-push-13 { left: 54.16666667%; } .ant-col-pull-13 { right: 54.16666667%; } .ant-col-offset-13 { margin-left: 54.16666667%; } .ant-col-order-13 { order: 13; } .ant-col-12 { display: block; flex: 0 0 50%; max-width: 50%; } .ant-col-push-12 { left: 50%; } .ant-col-pull-12 { right: 50%; } .ant-col-offset-12 { margin-left: 50%; } .ant-col-order-12 { order: 12; } .ant-col-11 { display: block; flex: 0 0 45.83333333%; max-width: 45.83333333%; } .ant-col-push-11 { left: 45.83333333%; } .ant-col-pull-11 { right: 45.83333333%; } .ant-col-offset-11 { margin-left: 45.83333333%; } .ant-col-order-11 { order: 11; } .ant-col-10 { display: block; flex: 0 0 41.66666667%; max-width: 41.66666667%; } .ant-col-push-10 { left: 41.66666667%; } .ant-col-pull-10 { right: 41.66666667%; } .ant-col-offset-10 { margin-left: 41.66666667%; } .ant-col-order-10 { order: 10; } .ant-col-9 { display: block; flex: 0 0 37.5%; max-width: 37.5%; } .ant-col-push-9 { left: 37.5%; } .ant-col-pull-9 { right: 37.5%; } .ant-col-offset-9 { margin-left: 37.5%; } .ant-col-order-9 { order: 9; } .ant-col-8 { display: block; flex: 0 0 33.33333333%; max-width: 33.33333333%; } .ant-col-push-8 { left: 33.33333333%; } .ant-col-pull-8 { right: 33.33333333%; } .ant-col-offset-8 { margin-left: 33.33333333%; } .ant-col-order-8 { order: 8; } .ant-col-7 { display: block; flex: 0 0 29.16666667%; max-width: 29.16666667%; } .ant-col-push-7 { left: 29.16666667%; } .ant-col-pull-7 { right: 29.16666667%; } .ant-col-offset-7 { margin-left: 29.16666667%; } .ant-col-order-7 { order: 7; } .ant-col-6 { display: block; flex: 0 0 25%; max-width: 25%; } .ant-col-push-6 { left: 25%; } .ant-col-pull-6 { right: 25%; } .ant-col-offset-6 { margin-left: 25%; } .ant-col-order-6 { order: 6; } .ant-col-5 { display: block; flex: 0 0 20.83333333%; max-width: 20.83333333%; } .ant-col-push-5 { left: 20.83333333%; } .ant-col-pull-5 { right: 20.83333333%; } .ant-col-offset-5 { margin-left: 20.83333333%; } .ant-col-order-5 { order: 5; } .ant-col-4 { display: block; flex: 0 0 16.66666667%; max-width: 16.66666667%; } .ant-col-push-4 { left: 16.66666667%; } .ant-col-pull-4 { right: 16.66666667%; } .ant-col-offset-4 { margin-left: 16.66666667%; } .ant-col-order-4 { order: 4; } .ant-col-3 { display: block; flex: 0 0 12.5%; max-width: 12.5%; } .ant-col-push-3 { left: 12.5%; } .ant-col-pull-3 { right: 12.5%; } .ant-col-offset-3 { margin-left: 12.5%; } .ant-col-order-3 { order: 3; } .ant-col-2 { display: block; flex: 0 0 8.33333333%; max-width: 8.33333333%; } .ant-col-push-2 { left: 8.33333333%; } .ant-col-pull-2 { right: 8.33333333%; } .ant-col-offset-2 { margin-left: 8.33333333%; } .ant-col-order-2 { order: 2; } .ant-col-1 { display: block; flex: 0 0 4.16666667%; max-width: 4.16666667%; } .ant-col-push-1 { left: 4.16666667%; } .ant-col-pull-1 { right: 4.16666667%; } .ant-col-offset-1 { margin-left: 4.16666667%; } .ant-col-order-1 { order: 1; } .ant-col-0 { display: none; } .ant-col-push-0 { left: auto; } .ant-col-pull-0 { right: auto; } .ant-col-push-0 { left: auto; } .ant-col-pull-0 { right: auto; } .ant-col-offset-0 { margin-left: 0; } .ant-col-order-0 { order: 0; } .ant-col-push-0.ant-col-rtl { right: auto; } .ant-col-pull-0.ant-col-rtl { left: auto; } .ant-col-push-0.ant-col-rtl { right: auto; } .ant-col-pull-0.ant-col-rtl { left: auto; } .ant-col-offset-0.ant-col-rtl { margin-right: 0; } .ant-col-push-1.ant-col-rtl { right: 4.16666667%; left: auto; } .ant-col-pull-1.ant-col-rtl { right: auto; left: 4.16666667%; } .ant-col-offset-1.ant-col-rtl { margin-right: 4.16666667%; margin-left: 0; } .ant-col-push-2.ant-col-rtl { right: 8.33333333%; left: auto; } .ant-col-pull-2.ant-col-rtl { right: auto; left: 8.33333333%; } .ant-col-offset-2.ant-col-rtl { margin-right: 8.33333333%; margin-left: 0; } .ant-col-push-3.ant-col-rtl { right: 12.5%; left: auto; } .ant-col-pull-3.ant-col-rtl { right: auto; left: 12.5%; } .ant-col-offset-3.ant-col-rtl { margin-right: 12.5%; margin-left: 0; } .ant-col-push-4.ant-col-rtl { right: 16.66666667%; left: auto; } .ant-col-pull-4.ant-col-rtl { right: auto; left: 16.66666667%; } .ant-col-offset-4.ant-col-rtl { margin-right: 16.66666667%; margin-left: 0; } .ant-col-push-5.ant-col-rtl { right: 20.83333333%; left: auto; } .ant-col-pull-5.ant-col-rtl { right: auto; left: 20.83333333%; } .ant-col-offset-5.ant-col-rtl { margin-right: 20.83333333%; margin-left: 0; } .ant-col-push-6.ant-col-rtl { right: 25%; left: auto; } .ant-col-pull-6.ant-col-rtl { right: auto; left: 25%; } .ant-col-offset-6.ant-col-rtl { margin-right: 25%; margin-left: 0; } .ant-col-push-7.ant-col-rtl { right: 29.16666667%; left: auto; } .ant-col-pull-7.ant-col-rtl { right: auto; left: 29.16666667%; } .ant-col-offset-7.ant-col-rtl { margin-right: 29.16666667%; margin-left: 0; } .ant-col-push-8.ant-col-rtl { right: 33.33333333%; left: auto; } .ant-col-pull-8.ant-col-rtl { right: auto; left: 33.33333333%; } .ant-col-offset-8.ant-col-rtl { margin-right: 33.33333333%; margin-left: 0; } .ant-col-push-9.ant-col-rtl { right: 37.5%; left: auto; } .ant-col-pull-9.ant-col-rtl { right: auto; left: 37.5%; } .ant-col-offset-9.ant-col-rtl { margin-right: 37.5%; margin-left: 0; } .ant-col-push-10.ant-col-rtl { right: 41.66666667%; left: auto; } .ant-col-pull-10.ant-col-rtl { right: auto; left: 41.66666667%; } .ant-col-offset-10.ant-col-rtl { margin-right: 41.66666667%; margin-left: 0; } .ant-col-push-11.ant-col-rtl { right: 45.83333333%; left: auto; } .ant-col-pull-11.ant-col-rtl { right: auto; left: 45.83333333%; } .ant-col-offset-11.ant-col-rtl { margin-right: 45.83333333%; margin-left: 0; } .ant-col-push-12.ant-col-rtl { right: 50%; left: auto; } .ant-col-pull-12.ant-col-rtl { right: auto; left: 50%; } .ant-col-offset-12.ant-col-rtl { margin-right: 50%; margin-left: 0; } .ant-col-push-13.ant-col-rtl { right: 54.16666667%; left: auto; } .ant-col-pull-13.ant-col-rtl { right: auto; left: 54.16666667%; } .ant-col-offset-13.ant-col-rtl { margin-right: 54.16666667%; margin-left: 0; } .ant-col-push-14.ant-col-rtl { right: 58.33333333%; left: auto; } .ant-col-pull-14.ant-col-rtl { right: auto; left: 58.33333333%; } .ant-col-offset-14.ant-col-rtl { margin-right: 58.33333333%; margin-left: 0; } .ant-col-push-15.ant-col-rtl { right: 62.5%; left: auto; } .ant-col-pull-15.ant-col-rtl { right: auto; left: 62.5%; } .ant-col-offset-15.ant-col-rtl { margin-right: 62.5%; margin-left: 0; } .ant-col-push-16.ant-col-rtl { right: 66.66666667%; left: auto; } .ant-col-pull-16.ant-col-rtl { right: auto; left: 66.66666667%; } .ant-col-offset-16.ant-col-rtl { margin-right: 66.66666667%; margin-left: 0; } .ant-col-push-17.ant-col-rtl { right: 70.83333333%; left: auto; } .ant-col-pull-17.ant-col-rtl { right: auto; left: 70.83333333%; } .ant-col-offset-17.ant-col-rtl { margin-right: 70.83333333%; margin-left: 0; } .ant-col-push-18.ant-col-rtl { right: 75%; left: auto; } .ant-col-pull-18.ant-col-rtl { right: auto; left: 75%; } .ant-col-offset-18.ant-col-rtl { margin-right: 75%; margin-left: 0; } .ant-col-push-19.ant-col-rtl { right: 79.16666667%; left: auto; } .ant-col-pull-19.ant-col-rtl { right: auto; left: 79.16666667%; } .ant-col-offset-19.ant-col-rtl { margin-right: 79.16666667%; margin-left: 0; } .ant-col-push-20.ant-col-rtl { right: 83.33333333%; left: auto; } .ant-col-pull-20.ant-col-rtl { right: auto; left: 83.33333333%; } .ant-col-offset-20.ant-col-rtl { margin-right: 83.33333333%; margin-left: 0; } .ant-col-push-21.ant-col-rtl { right: 87.5%; left: auto; } .ant-col-pull-21.ant-col-rtl { right: auto; left: 87.5%; } .ant-col-offset-21.ant-col-rtl { margin-right: 87.5%; margin-left: 0; } .ant-col-push-22.ant-col-rtl { right: 91.66666667%; left: auto; } .ant-col-pull-22.ant-col-rtl { right: auto; left: 91.66666667%; } .ant-col-offset-22.ant-col-rtl { margin-right: 91.66666667%; margin-left: 0; } .ant-col-push-23.ant-col-rtl { right: 95.83333333%; left: auto; } .ant-col-pull-23.ant-col-rtl { right: auto; left: 95.83333333%; } .ant-col-offset-23.ant-col-rtl { margin-right: 95.83333333%; margin-left: 0; } .ant-col-push-24.ant-col-rtl { right: 100%; left: auto; } .ant-col-pull-24.ant-col-rtl { right: auto; left: 100%; } .ant-col-offset-24.ant-col-rtl { margin-right: 100%; margin-left: 0; } .ant-col-xs-24 { display: block; flex: 0 0 100%; max-width: 100%; } .ant-col-xs-push-24 { left: 100%; } .ant-col-xs-pull-24 { right: 100%; } .ant-col-xs-offset-24 { margin-left: 100%; } .ant-col-xs-order-24 { order: 24; } .ant-col-xs-23 { display: block; flex: 0 0 95.83333333%; max-width: 95.83333333%; } .ant-col-xs-push-23 { left: 95.83333333%; } .ant-col-xs-pull-23 { right: 95.83333333%; } .ant-col-xs-offset-23 { margin-left: 95.83333333%; } .ant-col-xs-order-23 { order: 23; } .ant-col-xs-22 { display: block; flex: 0 0 91.66666667%; max-width: 91.66666667%; } .ant-col-xs-push-22 { left: 91.66666667%; } .ant-col-xs-pull-22 { right: 91.66666667%; } .ant-col-xs-offset-22 { margin-left: 91.66666667%; } .ant-col-xs-order-22 { order: 22; } .ant-col-xs-21 { display: block; flex: 0 0 87.5%; max-width: 87.5%; } .ant-col-xs-push-21 { left: 87.5%; } .ant-col-xs-pull-21 { right: 87.5%; } .ant-col-xs-offset-21 { margin-left: 87.5%; } .ant-col-xs-order-21 { order: 21; } .ant-col-xs-20 { display: block; flex: 0 0 83.33333333%; max-width: 83.33333333%; } .ant-col-xs-push-20 { left: 83.33333333%; } .ant-col-xs-pull-20 { right: 83.33333333%; } .ant-col-xs-offset-20 { margin-left: 83.33333333%; } .ant-col-xs-order-20 { order: 20; } .ant-col-xs-19 { display: block; flex: 0 0 79.16666667%; max-width: 79.16666667%; } .ant-col-xs-push-19 { left: 79.16666667%; } .ant-col-xs-pull-19 { right: 79.16666667%; } .ant-col-xs-offset-19 { margin-left: 79.16666667%; } .ant-col-xs-order-19 { order: 19; } .ant-col-xs-18 { display: block; flex: 0 0 75%; max-width: 75%; } .ant-col-xs-push-18 { left: 75%; } .ant-col-xs-pull-18 { right: 75%; } .ant-col-xs-offset-18 { margin-left: 75%; } .ant-col-xs-order-18 { order: 18; } .ant-col-xs-17 { display: block; flex: 0 0 70.83333333%; max-width: 70.83333333%; } .ant-col-xs-push-17 { left: 70.83333333%; } .ant-col-xs-pull-17 { right: 70.83333333%; } .ant-col-xs-offset-17 { margin-left: 70.83333333%; } .ant-col-xs-order-17 { order: 17; } .ant-col-xs-16 { display: block; flex: 0 0 66.66666667%; max-width: 66.66666667%; } .ant-col-xs-push-16 { left: 66.66666667%; } .ant-col-xs-pull-16 { right: 66.66666667%; } .ant-col-xs-offset-16 { margin-left: 66.66666667%; } .ant-col-xs-order-16 { order: 16; } .ant-col-xs-15 { display: block; flex: 0 0 62.5%; max-width: 62.5%; } .ant-col-xs-push-15 { left: 62.5%; } .ant-col-xs-pull-15 { right: 62.5%; } .ant-col-xs-offset-15 { margin-left: 62.5%; } .ant-col-xs-order-15 { order: 15; } .ant-col-xs-14 { display: block; flex: 0 0 58.33333333%; max-width: 58.33333333%; } .ant-col-xs-push-14 { left: 58.33333333%; } .ant-col-xs-pull-14 { right: 58.33333333%; } .ant-col-xs-offset-14 { margin-left: 58.33333333%; } .ant-col-xs-order-14 { order: 14; } .ant-col-xs-13 { display: block; flex: 0 0 54.16666667%; max-width: 54.16666667%; } .ant-col-xs-push-13 { left: 54.16666667%; } .ant-col-xs-pull-13 { right: 54.16666667%; } .ant-col-xs-offset-13 { margin-left: 54.16666667%; } .ant-col-xs-order-13 { order: 13; } .ant-col-xs-12 { display: block; flex: 0 0 50%; max-width: 50%; } .ant-col-xs-push-12 { left: 50%; } .ant-col-xs-pull-12 { right: 50%; } .ant-col-xs-offset-12 { margin-left: 50%; } .ant-col-xs-order-12 { order: 12; } .ant-col-xs-11 { display: block; flex: 0 0 45.83333333%; max-width: 45.83333333%; } .ant-col-xs-push-11 { left: 45.83333333%; } .ant-col-xs-pull-11 { right: 45.83333333%; } .ant-col-xs-offset-11 { margin-left: 45.83333333%; } .ant-col-xs-order-11 { order: 11; } .ant-col-xs-10 { display: block; flex: 0 0 41.66666667%; max-width: 41.66666667%; } .ant-col-xs-push-10 { left: 41.66666667%; } .ant-col-xs-pull-10 { right: 41.66666667%; } .ant-col-xs-offset-10 { margin-left: 41.66666667%; } .ant-col-xs-order-10 { order: 10; } .ant-col-xs-9 { display: block; flex: 0 0 37.5%; max-width: 37.5%; } .ant-col-xs-push-9 { left: 37.5%; } .ant-col-xs-pull-9 { right: 37.5%; } .ant-col-xs-offset-9 { margin-left: 37.5%; } .ant-col-xs-order-9 { order: 9; } .ant-col-xs-8 { display: block; flex: 0 0 33.33333333%; max-width: 33.33333333%; } .ant-col-xs-push-8 { left: 33.33333333%; } .ant-col-xs-pull-8 { right: 33.33333333%; } .ant-col-xs-offset-8 { margin-left: 33.33333333%; } .ant-col-xs-order-8 { order: 8; } .ant-col-xs-7 { display: block; flex: 0 0 29.16666667%; max-width: 29.16666667%; } .ant-col-xs-push-7 { left: 29.16666667%; } .ant-col-xs-pull-7 { right: 29.16666667%; } .ant-col-xs-offset-7 { margin-left: 29.16666667%; } .ant-col-xs-order-7 { order: 7; } .ant-col-xs-6 { display: block; flex: 0 0 25%; max-width: 25%; } .ant-col-xs-push-6 { left: 25%; } .ant-col-xs-pull-6 { right: 25%; } .ant-col-xs-offset-6 { margin-left: 25%; } .ant-col-xs-order-6 { order: 6; } .ant-col-xs-5 { display: block; flex: 0 0 20.83333333%; max-width: 20.83333333%; } .ant-col-xs-push-5 { left: 20.83333333%; } .ant-col-xs-pull-5 { right: 20.83333333%; } .ant-col-xs-offset-5 { margin-left: 20.83333333%; } .ant-col-xs-order-5 { order: 5; } .ant-col-xs-4 { display: block; flex: 0 0 16.66666667%; max-width: 16.66666667%; } .ant-col-xs-push-4 { left: 16.66666667%; } .ant-col-xs-pull-4 { right: 16.66666667%; } .ant-col-xs-offset-4 { margin-left: 16.66666667%; } .ant-col-xs-order-4 { order: 4; } .ant-col-xs-3 { display: block; flex: 0 0 12.5%; max-width: 12.5%; } .ant-col-xs-push-3 { left: 12.5%; } .ant-col-xs-pull-3 { right: 12.5%; } .ant-col-xs-offset-3 { margin-left: 12.5%; } .ant-col-xs-order-3 { order: 3; } .ant-col-xs-2 { display: block; flex: 0 0 8.33333333%; max-width: 8.33333333%; } .ant-col-xs-push-2 { left: 8.33333333%; } .ant-col-xs-pull-2 { right: 8.33333333%; } .ant-col-xs-offset-2 { margin-left: 8.33333333%; } .ant-col-xs-order-2 { order: 2; } .ant-col-xs-1 { display: block; flex: 0 0 4.16666667%; max-width: 4.16666667%; } .ant-col-xs-push-1 { left: 4.16666667%; } .ant-col-xs-pull-1 { right: 4.16666667%; } .ant-col-xs-offset-1 { margin-left: 4.16666667%; } .ant-col-xs-order-1 { order: 1; } .ant-col-xs-0 { display: none; } .ant-col-push-0 { left: auto; } .ant-col-pull-0 { right: auto; } .ant-col-xs-push-0 { left: auto; } .ant-col-xs-pull-0 { right: auto; } .ant-col-xs-offset-0 { margin-left: 0; } .ant-col-xs-order-0 { order: 0; } .ant-col-push-0.ant-col-rtl { right: auto; } .ant-col-pull-0.ant-col-rtl { left: auto; } .ant-col-xs-push-0.ant-col-rtl { right: auto; } .ant-col-xs-pull-0.ant-col-rtl { left: auto; } .ant-col-xs-offset-0.ant-col-rtl { margin-right: 0; } .ant-col-xs-push-1.ant-col-rtl { right: 4.16666667%; left: auto; } .ant-col-xs-pull-1.ant-col-rtl { right: auto; left: 4.16666667%; } .ant-col-xs-offset-1.ant-col-rtl { margin-right: 4.16666667%; margin-left: 0; } .ant-col-xs-push-2.ant-col-rtl { right: 8.33333333%; left: auto; } .ant-col-xs-pull-2.ant-col-rtl { right: auto; left: 8.33333333%; } .ant-col-xs-offset-2.ant-col-rtl { margin-right: 8.33333333%; margin-left: 0; } .ant-col-xs-push-3.ant-col-rtl { right: 12.5%; left: auto; } .ant-col-xs-pull-3.ant-col-rtl { right: auto; left: 12.5%; } .ant-col-xs-offset-3.ant-col-rtl { margin-right: 12.5%; margin-left: 0; } .ant-col-xs-push-4.ant-col-rtl { right: 16.66666667%; left: auto; } .ant-col-xs-pull-4.ant-col-rtl { right: auto; left: 16.66666667%; } .ant-col-xs-offset-4.ant-col-rtl { margin-right: 16.66666667%; margin-left: 0; } .ant-col-xs-push-5.ant-col-rtl { right: 20.83333333%; left: auto; } .ant-col-xs-pull-5.ant-col-rtl { right: auto; left: 20.83333333%; } .ant-col-xs-offset-5.ant-col-rtl { margin-right: 20.83333333%; margin-left: 0; } .ant-col-xs-push-6.ant-col-rtl { right: 25%; left: auto; } .ant-col-xs-pull-6.ant-col-rtl { right: auto; left: 25%; } .ant-col-xs-offset-6.ant-col-rtl { margin-right: 25%; margin-left: 0; } .ant-col-xs-push-7.ant-col-rtl { right: 29.16666667%; left: auto; } .ant-col-xs-pull-7.ant-col-rtl { right: auto; left: 29.16666667%; } .ant-col-xs-offset-7.ant-col-rtl { margin-right: 29.16666667%; margin-left: 0; } .ant-col-xs-push-8.ant-col-rtl { right: 33.33333333%; left: auto; } .ant-col-xs-pull-8.ant-col-rtl { right: auto; left: 33.33333333%; } .ant-col-xs-offset-8.ant-col-rtl { margin-right: 33.33333333%; margin-left: 0; } .ant-col-xs-push-9.ant-col-rtl { right: 37.5%; left: auto; } .ant-col-xs-pull-9.ant-col-rtl { right: auto; left: 37.5%; } .ant-col-xs-offset-9.ant-col-rtl { margin-right: 37.5%; margin-left: 0; } .ant-col-xs-push-10.ant-col-rtl { right: 41.66666667%; left: auto; } .ant-col-xs-pull-10.ant-col-rtl { right: auto; left: 41.66666667%; } .ant-col-xs-offset-10.ant-col-rtl { margin-right: 41.66666667%; margin-left: 0; } .ant-col-xs-push-11.ant-col-rtl { right: 45.83333333%; left: auto; } .ant-col-xs-pull-11.ant-col-rtl { right: auto; left: 45.83333333%; } .ant-col-xs-offset-11.ant-col-rtl { margin-right: 45.83333333%; margin-left: 0; } .ant-col-xs-push-12.ant-col-rtl { right: 50%; left: auto; } .ant-col-xs-pull-12.ant-col-rtl { right: auto; left: 50%; } .ant-col-xs-offset-12.ant-col-rtl { margin-right: 50%; margin-left: 0; } .ant-col-xs-push-13.ant-col-rtl { right: 54.16666667%; left: auto; } .ant-col-xs-pull-13.ant-col-rtl { right: auto; left: 54.16666667%; } .ant-col-xs-offset-13.ant-col-rtl { margin-right: 54.16666667%; margin-left: 0; } .ant-col-xs-push-14.ant-col-rtl { right: 58.33333333%; left: auto; } .ant-col-xs-pull-14.ant-col-rtl { right: auto; left: 58.33333333%; } .ant-col-xs-offset-14.ant-col-rtl { margin-right: 58.33333333%; margin-left: 0; } .ant-col-xs-push-15.ant-col-rtl { right: 62.5%; left: auto; } .ant-col-xs-pull-15.ant-col-rtl { right: auto; left: 62.5%; } .ant-col-xs-offset-15.ant-col-rtl { margin-right: 62.5%; margin-left: 0; } .ant-col-xs-push-16.ant-col-rtl { right: 66.66666667%; left: auto; } .ant-col-xs-pull-16.ant-col-rtl { right: auto; left: 66.66666667%; } .ant-col-xs-offset-16.ant-col-rtl { margin-right: 66.66666667%; margin-left: 0; } .ant-col-xs-push-17.ant-col-rtl { right: 70.83333333%; left: auto; } .ant-col-xs-pull-17.ant-col-rtl { right: auto; left: 70.83333333%; } .ant-col-xs-offset-17.ant-col-rtl { margin-right: 70.83333333%; margin-left: 0; } .ant-col-xs-push-18.ant-col-rtl { right: 75%; left: auto; } .ant-col-xs-pull-18.ant-col-rtl { right: auto; left: 75%; } .ant-col-xs-offset-18.ant-col-rtl { margin-right: 75%; margin-left: 0; } .ant-col-xs-push-19.ant-col-rtl { right: 79.16666667%; left: auto; } .ant-col-xs-pull-19.ant-col-rtl { right: auto; left: 79.16666667%; } .ant-col-xs-offset-19.ant-col-rtl { margin-right: 79.16666667%; margin-left: 0; } .ant-col-xs-push-20.ant-col-rtl { right: 83.33333333%; left: auto; } .ant-col-xs-pull-20.ant-col-rtl { right: auto; left: 83.33333333%; } .ant-col-xs-offset-20.ant-col-rtl { margin-right: 83.33333333%; margin-left: 0; } .ant-col-xs-push-21.ant-col-rtl { right: 87.5%; left: auto; } .ant-col-xs-pull-21.ant-col-rtl { right: auto; left: 87.5%; } .ant-col-xs-offset-21.ant-col-rtl { margin-right: 87.5%; margin-left: 0; } .ant-col-xs-push-22.ant-col-rtl { right: 91.66666667%; left: auto; } .ant-col-xs-pull-22.ant-col-rtl { right: auto; left: 91.66666667%; } .ant-col-xs-offset-22.ant-col-rtl { margin-right: 91.66666667%; margin-left: 0; } .ant-col-xs-push-23.ant-col-rtl { right: 95.83333333%; left: auto; } .ant-col-xs-pull-23.ant-col-rtl { right: auto; left: 95.83333333%; } .ant-col-xs-offset-23.ant-col-rtl { margin-right: 95.83333333%; margin-left: 0; } .ant-col-xs-push-24.ant-col-rtl { right: 100%; left: auto; } .ant-col-xs-pull-24.ant-col-rtl { right: auto; left: 100%; } .ant-col-xs-offset-24.ant-col-rtl { margin-right: 100%; margin-left: 0; } @media (min-width: 576px) { .ant-col-sm-24 { display: block; flex: 0 0 100%; max-width: 100%; } .ant-col-sm-push-24 { left: 100%; } .ant-col-sm-pull-24 { right: 100%; } .ant-col-sm-offset-24 { margin-left: 100%; } .ant-col-sm-order-24 { order: 24; } .ant-col-sm-23 { display: block; flex: 0 0 95.83333333%; max-width: 95.83333333%; } .ant-col-sm-push-23 { left: 95.83333333%; } .ant-col-sm-pull-23 { right: 95.83333333%; } .ant-col-sm-offset-23 { margin-left: 95.83333333%; } .ant-col-sm-order-23 { order: 23; } .ant-col-sm-22 { display: block; flex: 0 0 91.66666667%; max-width: 91.66666667%; } .ant-col-sm-push-22 { left: 91.66666667%; } .ant-col-sm-pull-22 { right: 91.66666667%; } .ant-col-sm-offset-22 { margin-left: 91.66666667%; } .ant-col-sm-order-22 { order: 22; } .ant-col-sm-21 { display: block; flex: 0 0 87.5%; max-width: 87.5%; } .ant-col-sm-push-21 { left: 87.5%; } .ant-col-sm-pull-21 { right: 87.5%; } .ant-col-sm-offset-21 { margin-left: 87.5%; } .ant-col-sm-order-21 { order: 21; } .ant-col-sm-20 { display: block; flex: 0 0 83.33333333%; max-width: 83.33333333%; } .ant-col-sm-push-20 { left: 83.33333333%; } .ant-col-sm-pull-20 { right: 83.33333333%; } .ant-col-sm-offset-20 { margin-left: 83.33333333%; } .ant-col-sm-order-20 { order: 20; } .ant-col-sm-19 { display: block; flex: 0 0 79.16666667%; max-width: 79.16666667%; } .ant-col-sm-push-19 { left: 79.16666667%; } .ant-col-sm-pull-19 { right: 79.16666667%; } .ant-col-sm-offset-19 { margin-left: 79.16666667%; } .ant-col-sm-order-19 { order: 19; } .ant-col-sm-18 { display: block; flex: 0 0 75%; max-width: 75%; } .ant-col-sm-push-18 { left: 75%; } .ant-col-sm-pull-18 { right: 75%; } .ant-col-sm-offset-18 { margin-left: 75%; } .ant-col-sm-order-18 { order: 18; } .ant-col-sm-17 { display: block; flex: 0 0 70.83333333%; max-width: 70.83333333%; } .ant-col-sm-push-17 { left: 70.83333333%; } .ant-col-sm-pull-17 { right: 70.83333333%; } .ant-col-sm-offset-17 { margin-left: 70.83333333%; } .ant-col-sm-order-17 { order: 17; } .ant-col-sm-16 { display: block; flex: 0 0 66.66666667%; max-width: 66.66666667%; } .ant-col-sm-push-16 { left: 66.66666667%; } .ant-col-sm-pull-16 { right: 66.66666667%; } .ant-col-sm-offset-16 { margin-left: 66.66666667%; } .ant-col-sm-order-16 { order: 16; } .ant-col-sm-15 { display: block; flex: 0 0 62.5%; max-width: 62.5%; } .ant-col-sm-push-15 { left: 62.5%; } .ant-col-sm-pull-15 { right: 62.5%; } .ant-col-sm-offset-15 { margin-left: 62.5%; } .ant-col-sm-order-15 { order: 15; } .ant-col-sm-14 { display: block; flex: 0 0 58.33333333%; max-width: 58.33333333%; } .ant-col-sm-push-14 { left: 58.33333333%; } .ant-col-sm-pull-14 { right: 58.33333333%; } .ant-col-sm-offset-14 { margin-left: 58.33333333%; } .ant-col-sm-order-14 { order: 14; } .ant-col-sm-13 { display: block; flex: 0 0 54.16666667%; max-width: 54.16666667%; } .ant-col-sm-push-13 { left: 54.16666667%; } .ant-col-sm-pull-13 { right: 54.16666667%; } .ant-col-sm-offset-13 { margin-left: 54.16666667%; } .ant-col-sm-order-13 { order: 13; } .ant-col-sm-12 { display: block; flex: 0 0 50%; max-width: 50%; } .ant-col-sm-push-12 { left: 50%; } .ant-col-sm-pull-12 { right: 50%; } .ant-col-sm-offset-12 { margin-left: 50%; } .ant-col-sm-order-12 { order: 12; } .ant-col-sm-11 { display: block; flex: 0 0 45.83333333%; max-width: 45.83333333%; } .ant-col-sm-push-11 { left: 45.83333333%; } .ant-col-sm-pull-11 { right: 45.83333333%; } .ant-col-sm-offset-11 { margin-left: 45.83333333%; } .ant-col-sm-order-11 { order: 11; } .ant-col-sm-10 { display: block; flex: 0 0 41.66666667%; max-width: 41.66666667%; } .ant-col-sm-push-10 { left: 41.66666667%; } .ant-col-sm-pull-10 { right: 41.66666667%; } .ant-col-sm-offset-10 { margin-left: 41.66666667%; } .ant-col-sm-order-10 { order: 10; } .ant-col-sm-9 { display: block; flex: 0 0 37.5%; max-width: 37.5%; } .ant-col-sm-push-9 { left: 37.5%; } .ant-col-sm-pull-9 { right: 37.5%; } .ant-col-sm-offset-9 { margin-left: 37.5%; } .ant-col-sm-order-9 { order: 9; } .ant-col-sm-8 { display: block; flex: 0 0 33.33333333%; max-width: 33.33333333%; } .ant-col-sm-push-8 { left: 33.33333333%; } .ant-col-sm-pull-8 { right: 33.33333333%; } .ant-col-sm-offset-8 { margin-left: 33.33333333%; } .ant-col-sm-order-8 { order: 8; } .ant-col-sm-7 { display: block; flex: 0 0 29.16666667%; max-width: 29.16666667%; } .ant-col-sm-push-7 { left: 29.16666667%; } .ant-col-sm-pull-7 { right: 29.16666667%; } .ant-col-sm-offset-7 { margin-left: 29.16666667%; } .ant-col-sm-order-7 { order: 7; } .ant-col-sm-6 { display: block; flex: 0 0 25%; max-width: 25%; } .ant-col-sm-push-6 { left: 25%; } .ant-col-sm-pull-6 { right: 25%; } .ant-col-sm-offset-6 { margin-left: 25%; } .ant-col-sm-order-6 { order: 6; } .ant-col-sm-5 { display: block; flex: 0 0 20.83333333%; max-width: 20.83333333%; } .ant-col-sm-push-5 { left: 20.83333333%; } .ant-col-sm-pull-5 { right: 20.83333333%; } .ant-col-sm-offset-5 { margin-left: 20.83333333%; } .ant-col-sm-order-5 { order: 5; } .ant-col-sm-4 { display: block; flex: 0 0 16.66666667%; max-width: 16.66666667%; } .ant-col-sm-push-4 { left: 16.66666667%; } .ant-col-sm-pull-4 { right: 16.66666667%; } .ant-col-sm-offset-4 { margin-left: 16.66666667%; } .ant-col-sm-order-4 { order: 4; } .ant-col-sm-3 { display: block; flex: 0 0 12.5%; max-width: 12.5%; } .ant-col-sm-push-3 { left: 12.5%; } .ant-col-sm-pull-3 { right: 12.5%; } .ant-col-sm-offset-3 { margin-left: 12.5%; } .ant-col-sm-order-3 { order: 3; } .ant-col-sm-2 { display: block; flex: 0 0 8.33333333%; max-width: 8.33333333%; } .ant-col-sm-push-2 { left: 8.33333333%; } .ant-col-sm-pull-2 { right: 8.33333333%; } .ant-col-sm-offset-2 { margin-left: 8.33333333%; } .ant-col-sm-order-2 { order: 2; } .ant-col-sm-1 { display: block; flex: 0 0 4.16666667%; max-width: 4.16666667%; } .ant-col-sm-push-1 { left: 4.16666667%; } .ant-col-sm-pull-1 { right: 4.16666667%; } .ant-col-sm-offset-1 { margin-left: 4.16666667%; } .ant-col-sm-order-1 { order: 1; } .ant-col-sm-0 { display: none; } .ant-col-push-0 { left: auto; } .ant-col-pull-0 { right: auto; } .ant-col-sm-push-0 { left: auto; } .ant-col-sm-pull-0 { right: auto; } .ant-col-sm-offset-0 { margin-left: 0; } .ant-col-sm-order-0 { order: 0; } .ant-col-push-0.ant-col-rtl { right: auto; } .ant-col-pull-0.ant-col-rtl { left: auto; } .ant-col-sm-push-0.ant-col-rtl { right: auto; } .ant-col-sm-pull-0.ant-col-rtl { left: auto; } .ant-col-sm-offset-0.ant-col-rtl { margin-right: 0; } .ant-col-sm-push-1.ant-col-rtl { right: 4.16666667%; left: auto; } .ant-col-sm-pull-1.ant-col-rtl { right: auto; left: 4.16666667%; } .ant-col-sm-offset-1.ant-col-rtl { margin-right: 4.16666667%; margin-left: 0; } .ant-col-sm-push-2.ant-col-rtl { right: 8.33333333%; left: auto; } .ant-col-sm-pull-2.ant-col-rtl { right: auto; left: 8.33333333%; } .ant-col-sm-offset-2.ant-col-rtl { margin-right: 8.33333333%; margin-left: 0; } .ant-col-sm-push-3.ant-col-rtl { right: 12.5%; left: auto; } .ant-col-sm-pull-3.ant-col-rtl { right: auto; left: 12.5%; } .ant-col-sm-offset-3.ant-col-rtl { margin-right: 12.5%; margin-left: 0; } .ant-col-sm-push-4.ant-col-rtl { right: 16.66666667%; left: auto; } .ant-col-sm-pull-4.ant-col-rtl { right: auto; left: 16.66666667%; } .ant-col-sm-offset-4.ant-col-rtl { margin-right: 16.66666667%; margin-left: 0; } .ant-col-sm-push-5.ant-col-rtl { right: 20.83333333%; left: auto; } .ant-col-sm-pull-5.ant-col-rtl { right: auto; left: 20.83333333%; } .ant-col-sm-offset-5.ant-col-rtl { margin-right: 20.83333333%; margin-left: 0; } .ant-col-sm-push-6.ant-col-rtl { right: 25%; left: auto; } .ant-col-sm-pull-6.ant-col-rtl { right: auto; left: 25%; } .ant-col-sm-offset-6.ant-col-rtl { margin-right: 25%; margin-left: 0; } .ant-col-sm-push-7.ant-col-rtl { right: 29.16666667%; left: auto; } .ant-col-sm-pull-7.ant-col-rtl { right: auto; left: 29.16666667%; } .ant-col-sm-offset-7.ant-col-rtl { margin-right: 29.16666667%; margin-left: 0; } .ant-col-sm-push-8.ant-col-rtl { right: 33.33333333%; left: auto; } .ant-col-sm-pull-8.ant-col-rtl { right: auto; left: 33.33333333%; } .ant-col-sm-offset-8.ant-col-rtl { margin-right: 33.33333333%; margin-left: 0; } .ant-col-sm-push-9.ant-col-rtl { right: 37.5%; left: auto; } .ant-col-sm-pull-9.ant-col-rtl { right: auto; left: 37.5%; } .ant-col-sm-offset-9.ant-col-rtl { margin-right: 37.5%; margin-left: 0; } .ant-col-sm-push-10.ant-col-rtl { right: 41.66666667%; left: auto; } .ant-col-sm-pull-10.ant-col-rtl { right: auto; left: 41.66666667%; } .ant-col-sm-offset-10.ant-col-rtl { margin-right: 41.66666667%; margin-left: 0; } .ant-col-sm-push-11.ant-col-rtl { right: 45.83333333%; left: auto; } .ant-col-sm-pull-11.ant-col-rtl { right: auto; left: 45.83333333%; } .ant-col-sm-offset-11.ant-col-rtl { margin-right: 45.83333333%; margin-left: 0; } .ant-col-sm-push-12.ant-col-rtl { right: 50%; left: auto; } .ant-col-sm-pull-12.ant-col-rtl { right: auto; left: 50%; } .ant-col-sm-offset-12.ant-col-rtl { margin-right: 50%; margin-left: 0; } .ant-col-sm-push-13.ant-col-rtl { right: 54.16666667%; left: auto; } .ant-col-sm-pull-13.ant-col-rtl { right: auto; left: 54.16666667%; } .ant-col-sm-offset-13.ant-col-rtl { margin-right: 54.16666667%; margin-left: 0; } .ant-col-sm-push-14.ant-col-rtl { right: 58.33333333%; left: auto; } .ant-col-sm-pull-14.ant-col-rtl { right: auto; left: 58.33333333%; } .ant-col-sm-offset-14.ant-col-rtl { margin-right: 58.33333333%; margin-left: 0; } .ant-col-sm-push-15.ant-col-rtl { right: 62.5%; left: auto; } .ant-col-sm-pull-15.ant-col-rtl { right: auto; left: 62.5%; } .ant-col-sm-offset-15.ant-col-rtl { margin-right: 62.5%; margin-left: 0; } .ant-col-sm-push-16.ant-col-rtl { right: 66.66666667%; left: auto; } .ant-col-sm-pull-16.ant-col-rtl { right: auto; left: 66.66666667%; } .ant-col-sm-offset-16.ant-col-rtl { margin-right: 66.66666667%; margin-left: 0; } .ant-col-sm-push-17.ant-col-rtl { right: 70.83333333%; left: auto; } .ant-col-sm-pull-17.ant-col-rtl { right: auto; left: 70.83333333%; } .ant-col-sm-offset-17.ant-col-rtl { margin-right: 70.83333333%; margin-left: 0; } .ant-col-sm-push-18.ant-col-rtl { right: 75%; left: auto; } .ant-col-sm-pull-18.ant-col-rtl { right: auto; left: 75%; } .ant-col-sm-offset-18.ant-col-rtl { margin-right: 75%; margin-left: 0; } .ant-col-sm-push-19.ant-col-rtl { right: 79.16666667%; left: auto; } .ant-col-sm-pull-19.ant-col-rtl { right: auto; left: 79.16666667%; } .ant-col-sm-offset-19.ant-col-rtl { margin-right: 79.16666667%; margin-left: 0; } .ant-col-sm-push-20.ant-col-rtl { right: 83.33333333%; left: auto; } .ant-col-sm-pull-20.ant-col-rtl { right: auto; left: 83.33333333%; } .ant-col-sm-offset-20.ant-col-rtl { margin-right: 83.33333333%; margin-left: 0; } .ant-col-sm-push-21.ant-col-rtl { right: 87.5%; left: auto; } .ant-col-sm-pull-21.ant-col-rtl { right: auto; left: 87.5%; } .ant-col-sm-offset-21.ant-col-rtl { margin-right: 87.5%; margin-left: 0; } .ant-col-sm-push-22.ant-col-rtl { right: 91.66666667%; left: auto; } .ant-col-sm-pull-22.ant-col-rtl { right: auto; left: 91.66666667%; } .ant-col-sm-offset-22.ant-col-rtl { margin-right: 91.66666667%; margin-left: 0; } .ant-col-sm-push-23.ant-col-rtl { right: 95.83333333%; left: auto; } .ant-col-sm-pull-23.ant-col-rtl { right: auto; left: 95.83333333%; } .ant-col-sm-offset-23.ant-col-rtl { margin-right: 95.83333333%; margin-left: 0; } .ant-col-sm-push-24.ant-col-rtl { right: 100%; left: auto; } .ant-col-sm-pull-24.ant-col-rtl { right: auto; left: 100%; } .ant-col-sm-offset-24.ant-col-rtl { margin-right: 100%; margin-left: 0; } } @media (min-width: 768px) { .ant-col-md-24 { display: block; flex: 0 0 100%; max-width: 100%; } .ant-col-md-push-24 { left: 100%; } .ant-col-md-pull-24 { right: 100%; } .ant-col-md-offset-24 { margin-left: 100%; } .ant-col-md-order-24 { order: 24; } .ant-col-md-23 { display: block; flex: 0 0 95.83333333%; max-width: 95.83333333%; } .ant-col-md-push-23 { left: 95.83333333%; } .ant-col-md-pull-23 { right: 95.83333333%; } .ant-col-md-offset-23 { margin-left: 95.83333333%; } .ant-col-md-order-23 { order: 23; } .ant-col-md-22 { display: block; flex: 0 0 91.66666667%; max-width: 91.66666667%; } .ant-col-md-push-22 { left: 91.66666667%; } .ant-col-md-pull-22 { right: 91.66666667%; } .ant-col-md-offset-22 { margin-left: 91.66666667%; } .ant-col-md-order-22 { order: 22; } .ant-col-md-21 { display: block; flex: 0 0 87.5%; max-width: 87.5%; } .ant-col-md-push-21 { left: 87.5%; } .ant-col-md-pull-21 { right: 87.5%; } .ant-col-md-offset-21 { margin-left: 87.5%; } .ant-col-md-order-21 { order: 21; } .ant-col-md-20 { display: block; flex: 0 0 83.33333333%; max-width: 83.33333333%; } .ant-col-md-push-20 { left: 83.33333333%; } .ant-col-md-pull-20 { right: 83.33333333%; } .ant-col-md-offset-20 { margin-left: 83.33333333%; } .ant-col-md-order-20 { order: 20; } .ant-col-md-19 { display: block; flex: 0 0 79.16666667%; max-width: 79.16666667%; } .ant-col-md-push-19 { left: 79.16666667%; } .ant-col-md-pull-19 { right: 79.16666667%; } .ant-col-md-offset-19 { margin-left: 79.16666667%; } .ant-col-md-order-19 { order: 19; } .ant-col-md-18 { display: block; flex: 0 0 75%; max-width: 75%; } .ant-col-md-push-18 { left: 75%; } .ant-col-md-pull-18 { right: 75%; } .ant-col-md-offset-18 { margin-left: 75%; } .ant-col-md-order-18 { order: 18; } .ant-col-md-17 { display: block; flex: 0 0 70.83333333%; max-width: 70.83333333%; } .ant-col-md-push-17 { left: 70.83333333%; } .ant-col-md-pull-17 { right: 70.83333333%; } .ant-col-md-offset-17 { margin-left: 70.83333333%; } .ant-col-md-order-17 { order: 17; } .ant-col-md-16 { display: block; flex: 0 0 66.66666667%; max-width: 66.66666667%; } .ant-col-md-push-16 { left: 66.66666667%; } .ant-col-md-pull-16 { right: 66.66666667%; } .ant-col-md-offset-16 { margin-left: 66.66666667%; } .ant-col-md-order-16 { order: 16; } .ant-col-md-15 { display: block; flex: 0 0 62.5%; max-width: 62.5%; } .ant-col-md-push-15 { left: 62.5%; } .ant-col-md-pull-15 { right: 62.5%; } .ant-col-md-offset-15 { margin-left: 62.5%; } .ant-col-md-order-15 { order: 15; } .ant-col-md-14 { display: block; flex: 0 0 58.33333333%; max-width: 58.33333333%; } .ant-col-md-push-14 { left: 58.33333333%; } .ant-col-md-pull-14 { right: 58.33333333%; } .ant-col-md-offset-14 { margin-left: 58.33333333%; } .ant-col-md-order-14 { order: 14; } .ant-col-md-13 { display: block; flex: 0 0 54.16666667%; max-width: 54.16666667%; } .ant-col-md-push-13 { left: 54.16666667%; } .ant-col-md-pull-13 { right: 54.16666667%; } .ant-col-md-offset-13 { margin-left: 54.16666667%; } .ant-col-md-order-13 { order: 13; } .ant-col-md-12 { display: block; flex: 0 0 50%; max-width: 50%; } .ant-col-md-push-12 { left: 50%; } .ant-col-md-pull-12 { right: 50%; } .ant-col-md-offset-12 { margin-left: 50%; } .ant-col-md-order-12 { order: 12; } .ant-col-md-11 { display: block; flex: 0 0 45.83333333%; max-width: 45.83333333%; } .ant-col-md-push-11 { left: 45.83333333%; } .ant-col-md-pull-11 { right: 45.83333333%; } .ant-col-md-offset-11 { margin-left: 45.83333333%; } .ant-col-md-order-11 { order: 11; } .ant-col-md-10 { display: block; flex: 0 0 41.66666667%; max-width: 41.66666667%; } .ant-col-md-push-10 { left: 41.66666667%; } .ant-col-md-pull-10 { right: 41.66666667%; } .ant-col-md-offset-10 { margin-left: 41.66666667%; } .ant-col-md-order-10 { order: 10; } .ant-col-md-9 { display: block; flex: 0 0 37.5%; max-width: 37.5%; } .ant-col-md-push-9 { left: 37.5%; } .ant-col-md-pull-9 { right: 37.5%; } .ant-col-md-offset-9 { margin-left: 37.5%; } .ant-col-md-order-9 { order: 9; } .ant-col-md-8 { display: block; flex: 0 0 33.33333333%; max-width: 33.33333333%; } .ant-col-md-push-8 { left: 33.33333333%; } .ant-col-md-pull-8 { right: 33.33333333%; } .ant-col-md-offset-8 { margin-left: 33.33333333%; } .ant-col-md-order-8 { order: 8; } .ant-col-md-7 { display: block; flex: 0 0 29.16666667%; max-width: 29.16666667%; } .ant-col-md-push-7 { left: 29.16666667%; } .ant-col-md-pull-7 { right: 29.16666667%; } .ant-col-md-offset-7 { margin-left: 29.16666667%; } .ant-col-md-order-7 { order: 7; } .ant-col-md-6 { display: block; flex: 0 0 25%; max-width: 25%; } .ant-col-md-push-6 { left: 25%; } .ant-col-md-pull-6 { right: 25%; } .ant-col-md-offset-6 { margin-left: 25%; } .ant-col-md-order-6 { order: 6; } .ant-col-md-5 { display: block; flex: 0 0 20.83333333%; max-width: 20.83333333%; } .ant-col-md-push-5 { left: 20.83333333%; } .ant-col-md-pull-5 { right: 20.83333333%; } .ant-col-md-offset-5 { margin-left: 20.83333333%; } .ant-col-md-order-5 { order: 5; } .ant-col-md-4 { display: block; flex: 0 0 16.66666667%; max-width: 16.66666667%; } .ant-col-md-push-4 { left: 16.66666667%; } .ant-col-md-pull-4 { right: 16.66666667%; } .ant-col-md-offset-4 { margin-left: 16.66666667%; } .ant-col-md-order-4 { order: 4; } .ant-col-md-3 { display: block; flex: 0 0 12.5%; max-width: 12.5%; } .ant-col-md-push-3 { left: 12.5%; } .ant-col-md-pull-3 { right: 12.5%; } .ant-col-md-offset-3 { margin-left: 12.5%; } .ant-col-md-order-3 { order: 3; } .ant-col-md-2 { display: block; flex: 0 0 8.33333333%; max-width: 8.33333333%; } .ant-col-md-push-2 { left: 8.33333333%; } .ant-col-md-pull-2 { right: 8.33333333%; } .ant-col-md-offset-2 { margin-left: 8.33333333%; } .ant-col-md-order-2 { order: 2; } .ant-col-md-1 { display: block; flex: 0 0 4.16666667%; max-width: 4.16666667%; } .ant-col-md-push-1 { left: 4.16666667%; } .ant-col-md-pull-1 { right: 4.16666667%; } .ant-col-md-offset-1 { margin-left: 4.16666667%; } .ant-col-md-order-1 { order: 1; } .ant-col-md-0 { display: none; } .ant-col-push-0 { left: auto; } .ant-col-pull-0 { right: auto; } .ant-col-md-push-0 { left: auto; } .ant-col-md-pull-0 { right: auto; } .ant-col-md-offset-0 { margin-left: 0; } .ant-col-md-order-0 { order: 0; } .ant-col-push-0.ant-col-rtl { right: auto; } .ant-col-pull-0.ant-col-rtl { left: auto; } .ant-col-md-push-0.ant-col-rtl { right: auto; } .ant-col-md-pull-0.ant-col-rtl { left: auto; } .ant-col-md-offset-0.ant-col-rtl { margin-right: 0; } .ant-col-md-push-1.ant-col-rtl { right: 4.16666667%; left: auto; } .ant-col-md-pull-1.ant-col-rtl { right: auto; left: 4.16666667%; } .ant-col-md-offset-1.ant-col-rtl { margin-right: 4.16666667%; margin-left: 0; } .ant-col-md-push-2.ant-col-rtl { right: 8.33333333%; left: auto; } .ant-col-md-pull-2.ant-col-rtl { right: auto; left: 8.33333333%; } .ant-col-md-offset-2.ant-col-rtl { margin-right: 8.33333333%; margin-left: 0; } .ant-col-md-push-3.ant-col-rtl { right: 12.5%; left: auto; } .ant-col-md-pull-3.ant-col-rtl { right: auto; left: 12.5%; } .ant-col-md-offset-3.ant-col-rtl { margin-right: 12.5%; margin-left: 0; } .ant-col-md-push-4.ant-col-rtl { right: 16.66666667%; left: auto; } .ant-col-md-pull-4.ant-col-rtl { right: auto; left: 16.66666667%; } .ant-col-md-offset-4.ant-col-rtl { margin-right: 16.66666667%; margin-left: 0; } .ant-col-md-push-5.ant-col-rtl { right: 20.83333333%; left: auto; } .ant-col-md-pull-5.ant-col-rtl { right: auto; left: 20.83333333%; } .ant-col-md-offset-5.ant-col-rtl { margin-right: 20.83333333%; margin-left: 0; } .ant-col-md-push-6.ant-col-rtl { right: 25%; left: auto; } .ant-col-md-pull-6.ant-col-rtl { right: auto; left: 25%; } .ant-col-md-offset-6.ant-col-rtl { margin-right: 25%; margin-left: 0; } .ant-col-md-push-7.ant-col-rtl { right: 29.16666667%; left: auto; } .ant-col-md-pull-7.ant-col-rtl { right: auto; left: 29.16666667%; } .ant-col-md-offset-7.ant-col-rtl { margin-right: 29.16666667%; margin-left: 0; } .ant-col-md-push-8.ant-col-rtl { right: 33.33333333%; left: auto; } .ant-col-md-pull-8.ant-col-rtl { right: auto; left: 33.33333333%; } .ant-col-md-offset-8.ant-col-rtl { margin-right: 33.33333333%; margin-left: 0; } .ant-col-md-push-9.ant-col-rtl { right: 37.5%; left: auto; } .ant-col-md-pull-9.ant-col-rtl { right: auto; left: 37.5%; } .ant-col-md-offset-9.ant-col-rtl { margin-right: 37.5%; margin-left: 0; } .ant-col-md-push-10.ant-col-rtl { right: 41.66666667%; left: auto; } .ant-col-md-pull-10.ant-col-rtl { right: auto; left: 41.66666667%; } .ant-col-md-offset-10.ant-col-rtl { margin-right: 41.66666667%; margin-left: 0; } .ant-col-md-push-11.ant-col-rtl { right: 45.83333333%; left: auto; } .ant-col-md-pull-11.ant-col-rtl { right: auto; left: 45.83333333%; } .ant-col-md-offset-11.ant-col-rtl { margin-right: 45.83333333%; margin-left: 0; } .ant-col-md-push-12.ant-col-rtl { right: 50%; left: auto; } .ant-col-md-pull-12.ant-col-rtl { right: auto; left: 50%; } .ant-col-md-offset-12.ant-col-rtl { margin-right: 50%; margin-left: 0; } .ant-col-md-push-13.ant-col-rtl { right: 54.16666667%; left: auto; } .ant-col-md-pull-13.ant-col-rtl { right: auto; left: 54.16666667%; } .ant-col-md-offset-13.ant-col-rtl { margin-right: 54.16666667%; margin-left: 0; } .ant-col-md-push-14.ant-col-rtl { right: 58.33333333%; left: auto; } .ant-col-md-pull-14.ant-col-rtl { right: auto; left: 58.33333333%; } .ant-col-md-offset-14.ant-col-rtl { margin-right: 58.33333333%; margin-left: 0; } .ant-col-md-push-15.ant-col-rtl { right: 62.5%; left: auto; } .ant-col-md-pull-15.ant-col-rtl { right: auto; left: 62.5%; } .ant-col-md-offset-15.ant-col-rtl { margin-right: 62.5%; margin-left: 0; } .ant-col-md-push-16.ant-col-rtl { right: 66.66666667%; left: auto; } .ant-col-md-pull-16.ant-col-rtl { right: auto; left: 66.66666667%; } .ant-col-md-offset-16.ant-col-rtl { margin-right: 66.66666667%; margin-left: 0; } .ant-col-md-push-17.ant-col-rtl { right: 70.83333333%; left: auto; } .ant-col-md-pull-17.ant-col-rtl { right: auto; left: 70.83333333%; } .ant-col-md-offset-17.ant-col-rtl { margin-right: 70.83333333%; margin-left: 0; } .ant-col-md-push-18.ant-col-rtl { right: 75%; left: auto; } .ant-col-md-pull-18.ant-col-rtl { right: auto; left: 75%; } .ant-col-md-offset-18.ant-col-rtl { margin-right: 75%; margin-left: 0; } .ant-col-md-push-19.ant-col-rtl { right: 79.16666667%; left: auto; } .ant-col-md-pull-19.ant-col-rtl { right: auto; left: 79.16666667%; } .ant-col-md-offset-19.ant-col-rtl { margin-right: 79.16666667%; margin-left: 0; } .ant-col-md-push-20.ant-col-rtl { right: 83.33333333%; left: auto; } .ant-col-md-pull-20.ant-col-rtl { right: auto; left: 83.33333333%; } .ant-col-md-offset-20.ant-col-rtl { margin-right: 83.33333333%; margin-left: 0; } .ant-col-md-push-21.ant-col-rtl { right: 87.5%; left: auto; } .ant-col-md-pull-21.ant-col-rtl { right: auto; left: 87.5%; } .ant-col-md-offset-21.ant-col-rtl { margin-right: 87.5%; margin-left: 0; } .ant-col-md-push-22.ant-col-rtl { right: 91.66666667%; left: auto; } .ant-col-md-pull-22.ant-col-rtl { right: auto; left: 91.66666667%; } .ant-col-md-offset-22.ant-col-rtl { margin-right: 91.66666667%; margin-left: 0; } .ant-col-md-push-23.ant-col-rtl { right: 95.83333333%; left: auto; } .ant-col-md-pull-23.ant-col-rtl { right: auto; left: 95.83333333%; } .ant-col-md-offset-23.ant-col-rtl { margin-right: 95.83333333%; margin-left: 0; } .ant-col-md-push-24.ant-col-rtl { right: 100%; left: auto; } .ant-col-md-pull-24.ant-col-rtl { right: auto; left: 100%; } .ant-col-md-offset-24.ant-col-rtl { margin-right: 100%; margin-left: 0; } } @media (min-width: 992px) { .ant-col-lg-24 { display: block; flex: 0 0 100%; max-width: 100%; } .ant-col-lg-push-24 { left: 100%; } .ant-col-lg-pull-24 { right: 100%; } .ant-col-lg-offset-24 { margin-left: 100%; } .ant-col-lg-order-24 { order: 24; } .ant-col-lg-23 { display: block; flex: 0 0 95.83333333%; max-width: 95.83333333%; } .ant-col-lg-push-23 { left: 95.83333333%; } .ant-col-lg-pull-23 { right: 95.83333333%; } .ant-col-lg-offset-23 { margin-left: 95.83333333%; } .ant-col-lg-order-23 { order: 23; } .ant-col-lg-22 { display: block; flex: 0 0 91.66666667%; max-width: 91.66666667%; } .ant-col-lg-push-22 { left: 91.66666667%; } .ant-col-lg-pull-22 { right: 91.66666667%; } .ant-col-lg-offset-22 { margin-left: 91.66666667%; } .ant-col-lg-order-22 { order: 22; } .ant-col-lg-21 { display: block; flex: 0 0 87.5%; max-width: 87.5%; } .ant-col-lg-push-21 { left: 87.5%; } .ant-col-lg-pull-21 { right: 87.5%; } .ant-col-lg-offset-21 { margin-left: 87.5%; } .ant-col-lg-order-21 { order: 21; } .ant-col-lg-20 { display: block; flex: 0 0 83.33333333%; max-width: 83.33333333%; } .ant-col-lg-push-20 { left: 83.33333333%; } .ant-col-lg-pull-20 { right: 83.33333333%; } .ant-col-lg-offset-20 { margin-left: 83.33333333%; } .ant-col-lg-order-20 { order: 20; } .ant-col-lg-19 { display: block; flex: 0 0 79.16666667%; max-width: 79.16666667%; } .ant-col-lg-push-19 { left: 79.16666667%; } .ant-col-lg-pull-19 { right: 79.16666667%; } .ant-col-lg-offset-19 { margin-left: 79.16666667%; } .ant-col-lg-order-19 { order: 19; } .ant-col-lg-18 { display: block; flex: 0 0 75%; max-width: 75%; } .ant-col-lg-push-18 { left: 75%; } .ant-col-lg-pull-18 { right: 75%; } .ant-col-lg-offset-18 { margin-left: 75%; } .ant-col-lg-order-18 { order: 18; } .ant-col-lg-17 { display: block; flex: 0 0 70.83333333%; max-width: 70.83333333%; } .ant-col-lg-push-17 { left: 70.83333333%; } .ant-col-lg-pull-17 { right: 70.83333333%; } .ant-col-lg-offset-17 { margin-left: 70.83333333%; } .ant-col-lg-order-17 { order: 17; } .ant-col-lg-16 { display: block; flex: 0 0 66.66666667%; max-width: 66.66666667%; } .ant-col-lg-push-16 { left: 66.66666667%; } .ant-col-lg-pull-16 { right: 66.66666667%; } .ant-col-lg-offset-16 { margin-left: 66.66666667%; } .ant-col-lg-order-16 { order: 16; } .ant-col-lg-15 { display: block; flex: 0 0 62.5%; max-width: 62.5%; } .ant-col-lg-push-15 { left: 62.5%; } .ant-col-lg-pull-15 { right: 62.5%; } .ant-col-lg-offset-15 { margin-left: 62.5%; } .ant-col-lg-order-15 { order: 15; } .ant-col-lg-14 { display: block; flex: 0 0 58.33333333%; max-width: 58.33333333%; } .ant-col-lg-push-14 { left: 58.33333333%; } .ant-col-lg-pull-14 { right: 58.33333333%; } .ant-col-lg-offset-14 { margin-left: 58.33333333%; } .ant-col-lg-order-14 { order: 14; } .ant-col-lg-13 { display: block; flex: 0 0 54.16666667%; max-width: 54.16666667%; } .ant-col-lg-push-13 { left: 54.16666667%; } .ant-col-lg-pull-13 { right: 54.16666667%; } .ant-col-lg-offset-13 { margin-left: 54.16666667%; } .ant-col-lg-order-13 { order: 13; } .ant-col-lg-12 { display: block; flex: 0 0 50%; max-width: 50%; } .ant-col-lg-push-12 { left: 50%; } .ant-col-lg-pull-12 { right: 50%; } .ant-col-lg-offset-12 { margin-left: 50%; } .ant-col-lg-order-12 { order: 12; } .ant-col-lg-11 { display: block; flex: 0 0 45.83333333%; max-width: 45.83333333%; } .ant-col-lg-push-11 { left: 45.83333333%; } .ant-col-lg-pull-11 { right: 45.83333333%; } .ant-col-lg-offset-11 { margin-left: 45.83333333%; } .ant-col-lg-order-11 { order: 11; } .ant-col-lg-10 { display: block; flex: 0 0 41.66666667%; max-width: 41.66666667%; } .ant-col-lg-push-10 { left: 41.66666667%; } .ant-col-lg-pull-10 { right: 41.66666667%; } .ant-col-lg-offset-10 { margin-left: 41.66666667%; } .ant-col-lg-order-10 { order: 10; } .ant-col-lg-9 { display: block; flex: 0 0 37.5%; max-width: 37.5%; } .ant-col-lg-push-9 { left: 37.5%; } .ant-col-lg-pull-9 { right: 37.5%; } .ant-col-lg-offset-9 { margin-left: 37.5%; } .ant-col-lg-order-9 { order: 9; } .ant-col-lg-8 { display: block; flex: 0 0 33.33333333%; max-width: 33.33333333%; } .ant-col-lg-push-8 { left: 33.33333333%; } .ant-col-lg-pull-8 { right: 33.33333333%; } .ant-col-lg-offset-8 { margin-left: 33.33333333%; } .ant-col-lg-order-8 { order: 8; } .ant-col-lg-7 { display: block; flex: 0 0 29.16666667%; max-width: 29.16666667%; } .ant-col-lg-push-7 { left: 29.16666667%; } .ant-col-lg-pull-7 { right: 29.16666667%; } .ant-col-lg-offset-7 { margin-left: 29.16666667%; } .ant-col-lg-order-7 { order: 7; } .ant-col-lg-6 { display: block; flex: 0 0 25%; max-width: 25%; } .ant-col-lg-push-6 { left: 25%; } .ant-col-lg-pull-6 { right: 25%; } .ant-col-lg-offset-6 { margin-left: 25%; } .ant-col-lg-order-6 { order: 6; } .ant-col-lg-5 { display: block; flex: 0 0 20.83333333%; max-width: 20.83333333%; } .ant-col-lg-push-5 { left: 20.83333333%; } .ant-col-lg-pull-5 { right: 20.83333333%; } .ant-col-lg-offset-5 { margin-left: 20.83333333%; } .ant-col-lg-order-5 { order: 5; } .ant-col-lg-4 { display: block; flex: 0 0 16.66666667%; max-width: 16.66666667%; } .ant-col-lg-push-4 { left: 16.66666667%; } .ant-col-lg-pull-4 { right: 16.66666667%; } .ant-col-lg-offset-4 { margin-left: 16.66666667%; } .ant-col-lg-order-4 { order: 4; } .ant-col-lg-3 { display: block; flex: 0 0 12.5%; max-width: 12.5%; } .ant-col-lg-push-3 { left: 12.5%; } .ant-col-lg-pull-3 { right: 12.5%; } .ant-col-lg-offset-3 { margin-left: 12.5%; } .ant-col-lg-order-3 { order: 3; } .ant-col-lg-2 { display: block; flex: 0 0 8.33333333%; max-width: 8.33333333%; } .ant-col-lg-push-2 { left: 8.33333333%; } .ant-col-lg-pull-2 { right: 8.33333333%; } .ant-col-lg-offset-2 { margin-left: 8.33333333%; } .ant-col-lg-order-2 { order: 2; } .ant-col-lg-1 { display: block; flex: 0 0 4.16666667%; max-width: 4.16666667%; } .ant-col-lg-push-1 { left: 4.16666667%; } .ant-col-lg-pull-1 { right: 4.16666667%; } .ant-col-lg-offset-1 { margin-left: 4.16666667%; } .ant-col-lg-order-1 { order: 1; } .ant-col-lg-0 { display: none; } .ant-col-push-0 { left: auto; } .ant-col-pull-0 { right: auto; } .ant-col-lg-push-0 { left: auto; } .ant-col-lg-pull-0 { right: auto; } .ant-col-lg-offset-0 { margin-left: 0; } .ant-col-lg-order-0 { order: 0; } .ant-col-push-0.ant-col-rtl { right: auto; } .ant-col-pull-0.ant-col-rtl { left: auto; } .ant-col-lg-push-0.ant-col-rtl { right: auto; } .ant-col-lg-pull-0.ant-col-rtl { left: auto; } .ant-col-lg-offset-0.ant-col-rtl { margin-right: 0; } .ant-col-lg-push-1.ant-col-rtl { right: 4.16666667%; left: auto; } .ant-col-lg-pull-1.ant-col-rtl { right: auto; left: 4.16666667%; } .ant-col-lg-offset-1.ant-col-rtl { margin-right: 4.16666667%; margin-left: 0; } .ant-col-lg-push-2.ant-col-rtl { right: 8.33333333%; left: auto; } .ant-col-lg-pull-2.ant-col-rtl { right: auto; left: 8.33333333%; } .ant-col-lg-offset-2.ant-col-rtl { margin-right: 8.33333333%; margin-left: 0; } .ant-col-lg-push-3.ant-col-rtl { right: 12.5%; left: auto; } .ant-col-lg-pull-3.ant-col-rtl { right: auto; left: 12.5%; } .ant-col-lg-offset-3.ant-col-rtl { margin-right: 12.5%; margin-left: 0; } .ant-col-lg-push-4.ant-col-rtl { right: 16.66666667%; left: auto; } .ant-col-lg-pull-4.ant-col-rtl { right: auto; left: 16.66666667%; } .ant-col-lg-offset-4.ant-col-rtl { margin-right: 16.66666667%; margin-left: 0; } .ant-col-lg-push-5.ant-col-rtl { right: 20.83333333%; left: auto; } .ant-col-lg-pull-5.ant-col-rtl { right: auto; left: 20.83333333%; } .ant-col-lg-offset-5.ant-col-rtl { margin-right: 20.83333333%; margin-left: 0; } .ant-col-lg-push-6.ant-col-rtl { right: 25%; left: auto; } .ant-col-lg-pull-6.ant-col-rtl { right: auto; left: 25%; } .ant-col-lg-offset-6.ant-col-rtl { margin-right: 25%; margin-left: 0; } .ant-col-lg-push-7.ant-col-rtl { right: 29.16666667%; left: auto; } .ant-col-lg-pull-7.ant-col-rtl { right: auto; left: 29.16666667%; } .ant-col-lg-offset-7.ant-col-rtl { margin-right: 29.16666667%; margin-left: 0; } .ant-col-lg-push-8.ant-col-rtl { right: 33.33333333%; left: auto; } .ant-col-lg-pull-8.ant-col-rtl { right: auto; left: 33.33333333%; } .ant-col-lg-offset-8.ant-col-rtl { margin-right: 33.33333333%; margin-left: 0; } .ant-col-lg-push-9.ant-col-rtl { right: 37.5%; left: auto; } .ant-col-lg-pull-9.ant-col-rtl { right: auto; left: 37.5%; } .ant-col-lg-offset-9.ant-col-rtl { margin-right: 37.5%; margin-left: 0; } .ant-col-lg-push-10.ant-col-rtl { right: 41.66666667%; left: auto; } .ant-col-lg-pull-10.ant-col-rtl { right: auto; left: 41.66666667%; } .ant-col-lg-offset-10.ant-col-rtl { margin-right: 41.66666667%; margin-left: 0; } .ant-col-lg-push-11.ant-col-rtl { right: 45.83333333%; left: auto; } .ant-col-lg-pull-11.ant-col-rtl { right: auto; left: 45.83333333%; } .ant-col-lg-offset-11.ant-col-rtl { margin-right: 45.83333333%; margin-left: 0; } .ant-col-lg-push-12.ant-col-rtl { right: 50%; left: auto; } .ant-col-lg-pull-12.ant-col-rtl { right: auto; left: 50%; } .ant-col-lg-offset-12.ant-col-rtl { margin-right: 50%; margin-left: 0; } .ant-col-lg-push-13.ant-col-rtl { right: 54.16666667%; left: auto; } .ant-col-lg-pull-13.ant-col-rtl { right: auto; left: 54.16666667%; } .ant-col-lg-offset-13.ant-col-rtl { margin-right: 54.16666667%; margin-left: 0; } .ant-col-lg-push-14.ant-col-rtl { right: 58.33333333%; left: auto; } .ant-col-lg-pull-14.ant-col-rtl { right: auto; left: 58.33333333%; } .ant-col-lg-offset-14.ant-col-rtl { margin-right: 58.33333333%; margin-left: 0; } .ant-col-lg-push-15.ant-col-rtl { right: 62.5%; left: auto; } .ant-col-lg-pull-15.ant-col-rtl { right: auto; left: 62.5%; } .ant-col-lg-offset-15.ant-col-rtl { margin-right: 62.5%; margin-left: 0; } .ant-col-lg-push-16.ant-col-rtl { right: 66.66666667%; left: auto; } .ant-col-lg-pull-16.ant-col-rtl { right: auto; left: 66.66666667%; } .ant-col-lg-offset-16.ant-col-rtl { margin-right: 66.66666667%; margin-left: 0; } .ant-col-lg-push-17.ant-col-rtl { right: 70.83333333%; left: auto; } .ant-col-lg-pull-17.ant-col-rtl { right: auto; left: 70.83333333%; } .ant-col-lg-offset-17.ant-col-rtl { margin-right: 70.83333333%; margin-left: 0; } .ant-col-lg-push-18.ant-col-rtl { right: 75%; left: auto; } .ant-col-lg-pull-18.ant-col-rtl { right: auto; left: 75%; } .ant-col-lg-offset-18.ant-col-rtl { margin-right: 75%; margin-left: 0; } .ant-col-lg-push-19.ant-col-rtl { right: 79.16666667%; left: auto; } .ant-col-lg-pull-19.ant-col-rtl { right: auto; left: 79.16666667%; } .ant-col-lg-offset-19.ant-col-rtl { margin-right: 79.16666667%; margin-left: 0; } .ant-col-lg-push-20.ant-col-rtl { right: 83.33333333%; left: auto; } .ant-col-lg-pull-20.ant-col-rtl { right: auto; left: 83.33333333%; } .ant-col-lg-offset-20.ant-col-rtl { margin-right: 83.33333333%; margin-left: 0; } .ant-col-lg-push-21.ant-col-rtl { right: 87.5%; left: auto; } .ant-col-lg-pull-21.ant-col-rtl { right: auto; left: 87.5%; } .ant-col-lg-offset-21.ant-col-rtl { margin-right: 87.5%; margin-left: 0; } .ant-col-lg-push-22.ant-col-rtl { right: 91.66666667%; left: auto; } .ant-col-lg-pull-22.ant-col-rtl { right: auto; left: 91.66666667%; } .ant-col-lg-offset-22.ant-col-rtl { margin-right: 91.66666667%; margin-left: 0; } .ant-col-lg-push-23.ant-col-rtl { right: 95.83333333%; left: auto; } .ant-col-lg-pull-23.ant-col-rtl { right: auto; left: 95.83333333%; } .ant-col-lg-offset-23.ant-col-rtl { margin-right: 95.83333333%; margin-left: 0; } .ant-col-lg-push-24.ant-col-rtl { right: 100%; left: auto; } .ant-col-lg-pull-24.ant-col-rtl { right: auto; left: 100%; } .ant-col-lg-offset-24.ant-col-rtl { margin-right: 100%; margin-left: 0; } } @media (min-width: 1200px) { .ant-col-xl-24 { display: block; flex: 0 0 100%; max-width: 100%; } .ant-col-xl-push-24 { left: 100%; } .ant-col-xl-pull-24 { right: 100%; } .ant-col-xl-offset-24 { margin-left: 100%; } .ant-col-xl-order-24 { order: 24; } .ant-col-xl-23 { display: block; flex: 0 0 95.83333333%; max-width: 95.83333333%; } .ant-col-xl-push-23 { left: 95.83333333%; } .ant-col-xl-pull-23 { right: 95.83333333%; } .ant-col-xl-offset-23 { margin-left: 95.83333333%; } .ant-col-xl-order-23 { order: 23; } .ant-col-xl-22 { display: block; flex: 0 0 91.66666667%; max-width: 91.66666667%; } .ant-col-xl-push-22 { left: 91.66666667%; } .ant-col-xl-pull-22 { right: 91.66666667%; } .ant-col-xl-offset-22 { margin-left: 91.66666667%; } .ant-col-xl-order-22 { order: 22; } .ant-col-xl-21 { display: block; flex: 0 0 87.5%; max-width: 87.5%; } .ant-col-xl-push-21 { left: 87.5%; } .ant-col-xl-pull-21 { right: 87.5%; } .ant-col-xl-offset-21 { margin-left: 87.5%; } .ant-col-xl-order-21 { order: 21; } .ant-col-xl-20 { display: block; flex: 0 0 83.33333333%; max-width: 83.33333333%; } .ant-col-xl-push-20 { left: 83.33333333%; } .ant-col-xl-pull-20 { right: 83.33333333%; } .ant-col-xl-offset-20 { margin-left: 83.33333333%; } .ant-col-xl-order-20 { order: 20; } .ant-col-xl-19 { display: block; flex: 0 0 79.16666667%; max-width: 79.16666667%; } .ant-col-xl-push-19 { left: 79.16666667%; } .ant-col-xl-pull-19 { right: 79.16666667%; } .ant-col-xl-offset-19 { margin-left: 79.16666667%; } .ant-col-xl-order-19 { order: 19; } .ant-col-xl-18 { display: block; flex: 0 0 75%; max-width: 75%; } .ant-col-xl-push-18 { left: 75%; } .ant-col-xl-pull-18 { right: 75%; } .ant-col-xl-offset-18 { margin-left: 75%; } .ant-col-xl-order-18 { order: 18; } .ant-col-xl-17 { display: block; flex: 0 0 70.83333333%; max-width: 70.83333333%; } .ant-col-xl-push-17 { left: 70.83333333%; } .ant-col-xl-pull-17 { right: 70.83333333%; } .ant-col-xl-offset-17 { margin-left: 70.83333333%; } .ant-col-xl-order-17 { order: 17; } .ant-col-xl-16 { display: block; flex: 0 0 66.66666667%; max-width: 66.66666667%; } .ant-col-xl-push-16 { left: 66.66666667%; } .ant-col-xl-pull-16 { right: 66.66666667%; } .ant-col-xl-offset-16 { margin-left: 66.66666667%; } .ant-col-xl-order-16 { order: 16; } .ant-col-xl-15 { display: block; flex: 0 0 62.5%; max-width: 62.5%; } .ant-col-xl-push-15 { left: 62.5%; } .ant-col-xl-pull-15 { right: 62.5%; } .ant-col-xl-offset-15 { margin-left: 62.5%; } .ant-col-xl-order-15 { order: 15; } .ant-col-xl-14 { display: block; flex: 0 0 58.33333333%; max-width: 58.33333333%; } .ant-col-xl-push-14 { left: 58.33333333%; } .ant-col-xl-pull-14 { right: 58.33333333%; } .ant-col-xl-offset-14 { margin-left: 58.33333333%; } .ant-col-xl-order-14 { order: 14; } .ant-col-xl-13 { display: block; flex: 0 0 54.16666667%; max-width: 54.16666667%; } .ant-col-xl-push-13 { left: 54.16666667%; } .ant-col-xl-pull-13 { right: 54.16666667%; } .ant-col-xl-offset-13 { margin-left: 54.16666667%; } .ant-col-xl-order-13 { order: 13; } .ant-col-xl-12 { display: block; flex: 0 0 50%; max-width: 50%; } .ant-col-xl-push-12 { left: 50%; } .ant-col-xl-pull-12 { right: 50%; } .ant-col-xl-offset-12 { margin-left: 50%; } .ant-col-xl-order-12 { order: 12; } .ant-col-xl-11 { display: block; flex: 0 0 45.83333333%; max-width: 45.83333333%; } .ant-col-xl-push-11 { left: 45.83333333%; } .ant-col-xl-pull-11 { right: 45.83333333%; } .ant-col-xl-offset-11 { margin-left: 45.83333333%; } .ant-col-xl-order-11 { order: 11; } .ant-col-xl-10 { display: block; flex: 0 0 41.66666667%; max-width: 41.66666667%; } .ant-col-xl-push-10 { left: 41.66666667%; } .ant-col-xl-pull-10 { right: 41.66666667%; } .ant-col-xl-offset-10 { margin-left: 41.66666667%; } .ant-col-xl-order-10 { order: 10; } .ant-col-xl-9 { display: block; flex: 0 0 37.5%; max-width: 37.5%; } .ant-col-xl-push-9 { left: 37.5%; } .ant-col-xl-pull-9 { right: 37.5%; } .ant-col-xl-offset-9 { margin-left: 37.5%; } .ant-col-xl-order-9 { order: 9; } .ant-col-xl-8 { display: block; flex: 0 0 33.33333333%; max-width: 33.33333333%; } .ant-col-xl-push-8 { left: 33.33333333%; } .ant-col-xl-pull-8 { right: 33.33333333%; } .ant-col-xl-offset-8 { margin-left: 33.33333333%; } .ant-col-xl-order-8 { order: 8; } .ant-col-xl-7 { display: block; flex: 0 0 29.16666667%; max-width: 29.16666667%; } .ant-col-xl-push-7 { left: 29.16666667%; } .ant-col-xl-pull-7 { right: 29.16666667%; } .ant-col-xl-offset-7 { margin-left: 29.16666667%; } .ant-col-xl-order-7 { order: 7; } .ant-col-xl-6 { display: block; flex: 0 0 25%; max-width: 25%; } .ant-col-xl-push-6 { left: 25%; } .ant-col-xl-pull-6 { right: 25%; } .ant-col-xl-offset-6 { margin-left: 25%; } .ant-col-xl-order-6 { order: 6; } .ant-col-xl-5 { display: block; flex: 0 0 20.83333333%; max-width: 20.83333333%; } .ant-col-xl-push-5 { left: 20.83333333%; } .ant-col-xl-pull-5 { right: 20.83333333%; } .ant-col-xl-offset-5 { margin-left: 20.83333333%; } .ant-col-xl-order-5 { order: 5; } .ant-col-xl-4 { display: block; flex: 0 0 16.66666667%; max-width: 16.66666667%; } .ant-col-xl-push-4 { left: 16.66666667%; } .ant-col-xl-pull-4 { right: 16.66666667%; } .ant-col-xl-offset-4 { margin-left: 16.66666667%; } .ant-col-xl-order-4 { order: 4; } .ant-col-xl-3 { display: block; flex: 0 0 12.5%; max-width: 12.5%; } .ant-col-xl-push-3 { left: 12.5%; } .ant-col-xl-pull-3 { right: 12.5%; } .ant-col-xl-offset-3 { margin-left: 12.5%; } .ant-col-xl-order-3 { order: 3; } .ant-col-xl-2 { display: block; flex: 0 0 8.33333333%; max-width: 8.33333333%; } .ant-col-xl-push-2 { left: 8.33333333%; } .ant-col-xl-pull-2 { right: 8.33333333%; } .ant-col-xl-offset-2 { margin-left: 8.33333333%; } .ant-col-xl-order-2 { order: 2; } .ant-col-xl-1 { display: block; flex: 0 0 4.16666667%; max-width: 4.16666667%; } .ant-col-xl-push-1 { left: 4.16666667%; } .ant-col-xl-pull-1 { right: 4.16666667%; } .ant-col-xl-offset-1 { margin-left: 4.16666667%; } .ant-col-xl-order-1 { order: 1; } .ant-col-xl-0 { display: none; } .ant-col-push-0 { left: auto; } .ant-col-pull-0 { right: auto; } .ant-col-xl-push-0 { left: auto; } .ant-col-xl-pull-0 { right: auto; } .ant-col-xl-offset-0 { margin-left: 0; } .ant-col-xl-order-0 { order: 0; } .ant-col-push-0.ant-col-rtl { right: auto; } .ant-col-pull-0.ant-col-rtl { left: auto; } .ant-col-xl-push-0.ant-col-rtl { right: auto; } .ant-col-xl-pull-0.ant-col-rtl { left: auto; } .ant-col-xl-offset-0.ant-col-rtl { margin-right: 0; } .ant-col-xl-push-1.ant-col-rtl { right: 4.16666667%; left: auto; } .ant-col-xl-pull-1.ant-col-rtl { right: auto; left: 4.16666667%; } .ant-col-xl-offset-1.ant-col-rtl { margin-right: 4.16666667%; margin-left: 0; } .ant-col-xl-push-2.ant-col-rtl { right: 8.33333333%; left: auto; } .ant-col-xl-pull-2.ant-col-rtl { right: auto; left: 8.33333333%; } .ant-col-xl-offset-2.ant-col-rtl { margin-right: 8.33333333%; margin-left: 0; } .ant-col-xl-push-3.ant-col-rtl { right: 12.5%; left: auto; } .ant-col-xl-pull-3.ant-col-rtl { right: auto; left: 12.5%; } .ant-col-xl-offset-3.ant-col-rtl { margin-right: 12.5%; margin-left: 0; } .ant-col-xl-push-4.ant-col-rtl { right: 16.66666667%; left: auto; } .ant-col-xl-pull-4.ant-col-rtl { right: auto; left: 16.66666667%; } .ant-col-xl-offset-4.ant-col-rtl { margin-right: 16.66666667%; margin-left: 0; } .ant-col-xl-push-5.ant-col-rtl { right: 20.83333333%; left: auto; } .ant-col-xl-pull-5.ant-col-rtl { right: auto; left: 20.83333333%; } .ant-col-xl-offset-5.ant-col-rtl { margin-right: 20.83333333%; margin-left: 0; } .ant-col-xl-push-6.ant-col-rtl { right: 25%; left: auto; } .ant-col-xl-pull-6.ant-col-rtl { right: auto; left: 25%; } .ant-col-xl-offset-6.ant-col-rtl { margin-right: 25%; margin-left: 0; } .ant-col-xl-push-7.ant-col-rtl { right: 29.16666667%; left: auto; } .ant-col-xl-pull-7.ant-col-rtl { right: auto; left: 29.16666667%; } .ant-col-xl-offset-7.ant-col-rtl { margin-right: 29.16666667%; margin-left: 0; } .ant-col-xl-push-8.ant-col-rtl { right: 33.33333333%; left: auto; } .ant-col-xl-pull-8.ant-col-rtl { right: auto; left: 33.33333333%; } .ant-col-xl-offset-8.ant-col-rtl { margin-right: 33.33333333%; margin-left: 0; } .ant-col-xl-push-9.ant-col-rtl { right: 37.5%; left: auto; } .ant-col-xl-pull-9.ant-col-rtl { right: auto; left: 37.5%; } .ant-col-xl-offset-9.ant-col-rtl { margin-right: 37.5%; margin-left: 0; } .ant-col-xl-push-10.ant-col-rtl { right: 41.66666667%; left: auto; } .ant-col-xl-pull-10.ant-col-rtl { right: auto; left: 41.66666667%; } .ant-col-xl-offset-10.ant-col-rtl { margin-right: 41.66666667%; margin-left: 0; } .ant-col-xl-push-11.ant-col-rtl { right: 45.83333333%; left: auto; } .ant-col-xl-pull-11.ant-col-rtl { right: auto; left: 45.83333333%; } .ant-col-xl-offset-11.ant-col-rtl { margin-right: 45.83333333%; margin-left: 0; } .ant-col-xl-push-12.ant-col-rtl { right: 50%; left: auto; } .ant-col-xl-pull-12.ant-col-rtl { right: auto; left: 50%; } .ant-col-xl-offset-12.ant-col-rtl { margin-right: 50%; margin-left: 0; } .ant-col-xl-push-13.ant-col-rtl { right: 54.16666667%; left: auto; } .ant-col-xl-pull-13.ant-col-rtl { right: auto; left: 54.16666667%; } .ant-col-xl-offset-13.ant-col-rtl { margin-right: 54.16666667%; margin-left: 0; } .ant-col-xl-push-14.ant-col-rtl { right: 58.33333333%; left: auto; } .ant-col-xl-pull-14.ant-col-rtl { right: auto; left: 58.33333333%; } .ant-col-xl-offset-14.ant-col-rtl { margin-right: 58.33333333%; margin-left: 0; } .ant-col-xl-push-15.ant-col-rtl { right: 62.5%; left: auto; } .ant-col-xl-pull-15.ant-col-rtl { right: auto; left: 62.5%; } .ant-col-xl-offset-15.ant-col-rtl { margin-right: 62.5%; margin-left: 0; } .ant-col-xl-push-16.ant-col-rtl { right: 66.66666667%; left: auto; } .ant-col-xl-pull-16.ant-col-rtl { right: auto; left: 66.66666667%; } .ant-col-xl-offset-16.ant-col-rtl { margin-right: 66.66666667%; margin-left: 0; } .ant-col-xl-push-17.ant-col-rtl { right: 70.83333333%; left: auto; } .ant-col-xl-pull-17.ant-col-rtl { right: auto; left: 70.83333333%; } .ant-col-xl-offset-17.ant-col-rtl { margin-right: 70.83333333%; margin-left: 0; } .ant-col-xl-push-18.ant-col-rtl { right: 75%; left: auto; } .ant-col-xl-pull-18.ant-col-rtl { right: auto; left: 75%; } .ant-col-xl-offset-18.ant-col-rtl { margin-right: 75%; margin-left: 0; } .ant-col-xl-push-19.ant-col-rtl { right: 79.16666667%; left: auto; } .ant-col-xl-pull-19.ant-col-rtl { right: auto; left: 79.16666667%; } .ant-col-xl-offset-19.ant-col-rtl { margin-right: 79.16666667%; margin-left: 0; } .ant-col-xl-push-20.ant-col-rtl { right: 83.33333333%; left: auto; } .ant-col-xl-pull-20.ant-col-rtl { right: auto; left: 83.33333333%; } .ant-col-xl-offset-20.ant-col-rtl { margin-right: 83.33333333%; margin-left: 0; } .ant-col-xl-push-21.ant-col-rtl { right: 87.5%; left: auto; } .ant-col-xl-pull-21.ant-col-rtl { right: auto; left: 87.5%; } .ant-col-xl-offset-21.ant-col-rtl { margin-right: 87.5%; margin-left: 0; } .ant-col-xl-push-22.ant-col-rtl { right: 91.66666667%; left: auto; } .ant-col-xl-pull-22.ant-col-rtl { right: auto; left: 91.66666667%; } .ant-col-xl-offset-22.ant-col-rtl { margin-right: 91.66666667%; margin-left: 0; } .ant-col-xl-push-23.ant-col-rtl { right: 95.83333333%; left: auto; } .ant-col-xl-pull-23.ant-col-rtl { right: auto; left: 95.83333333%; } .ant-col-xl-offset-23.ant-col-rtl { margin-right: 95.83333333%; margin-left: 0; } .ant-col-xl-push-24.ant-col-rtl { right: 100%; left: auto; } .ant-col-xl-pull-24.ant-col-rtl { right: auto; left: 100%; } .ant-col-xl-offset-24.ant-col-rtl { margin-right: 100%; margin-left: 0; } } @media (min-width: 1600px) { .ant-col-xxl-24 { display: block; flex: 0 0 100%; max-width: 100%; } .ant-col-xxl-push-24 { left: 100%; } .ant-col-xxl-pull-24 { right: 100%; } .ant-col-xxl-offset-24 { margin-left: 100%; } .ant-col-xxl-order-24 { order: 24; } .ant-col-xxl-23 { display: block; flex: 0 0 95.83333333%; max-width: 95.83333333%; } .ant-col-xxl-push-23 { left: 95.83333333%; } .ant-col-xxl-pull-23 { right: 95.83333333%; } .ant-col-xxl-offset-23 { margin-left: 95.83333333%; } .ant-col-xxl-order-23 { order: 23; } .ant-col-xxl-22 { display: block; flex: 0 0 91.66666667%; max-width: 91.66666667%; } .ant-col-xxl-push-22 { left: 91.66666667%; } .ant-col-xxl-pull-22 { right: 91.66666667%; } .ant-col-xxl-offset-22 { margin-left: 91.66666667%; } .ant-col-xxl-order-22 { order: 22; } .ant-col-xxl-21 { display: block; flex: 0 0 87.5%; max-width: 87.5%; } .ant-col-xxl-push-21 { left: 87.5%; } .ant-col-xxl-pull-21 { right: 87.5%; } .ant-col-xxl-offset-21 { margin-left: 87.5%; } .ant-col-xxl-order-21 { order: 21; } .ant-col-xxl-20 { display: block; flex: 0 0 83.33333333%; max-width: 83.33333333%; } .ant-col-xxl-push-20 { left: 83.33333333%; } .ant-col-xxl-pull-20 { right: 83.33333333%; } .ant-col-xxl-offset-20 { margin-left: 83.33333333%; } .ant-col-xxl-order-20 { order: 20; } .ant-col-xxl-19 { display: block; flex: 0 0 79.16666667%; max-width: 79.16666667%; } .ant-col-xxl-push-19 { left: 79.16666667%; } .ant-col-xxl-pull-19 { right: 79.16666667%; } .ant-col-xxl-offset-19 { margin-left: 79.16666667%; } .ant-col-xxl-order-19 { order: 19; } .ant-col-xxl-18 { display: block; flex: 0 0 75%; max-width: 75%; } .ant-col-xxl-push-18 { left: 75%; } .ant-col-xxl-pull-18 { right: 75%; } .ant-col-xxl-offset-18 { margin-left: 75%; } .ant-col-xxl-order-18 { order: 18; } .ant-col-xxl-17 { display: block; flex: 0 0 70.83333333%; max-width: 70.83333333%; } .ant-col-xxl-push-17 { left: 70.83333333%; } .ant-col-xxl-pull-17 { right: 70.83333333%; } .ant-col-xxl-offset-17 { margin-left: 70.83333333%; } .ant-col-xxl-order-17 { order: 17; } .ant-col-xxl-16 { display: block; flex: 0 0 66.66666667%; max-width: 66.66666667%; } .ant-col-xxl-push-16 { left: 66.66666667%; } .ant-col-xxl-pull-16 { right: 66.66666667%; } .ant-col-xxl-offset-16 { margin-left: 66.66666667%; } .ant-col-xxl-order-16 { order: 16; } .ant-col-xxl-15 { display: block; flex: 0 0 62.5%; max-width: 62.5%; } .ant-col-xxl-push-15 { left: 62.5%; } .ant-col-xxl-pull-15 { right: 62.5%; } .ant-col-xxl-offset-15 { margin-left: 62.5%; } .ant-col-xxl-order-15 { order: 15; } .ant-col-xxl-14 { display: block; flex: 0 0 58.33333333%; max-width: 58.33333333%; } .ant-col-xxl-push-14 { left: 58.33333333%; } .ant-col-xxl-pull-14 { right: 58.33333333%; } .ant-col-xxl-offset-14 { margin-left: 58.33333333%; } .ant-col-xxl-order-14 { order: 14; } .ant-col-xxl-13 { display: block; flex: 0 0 54.16666667%; max-width: 54.16666667%; } .ant-col-xxl-push-13 { left: 54.16666667%; } .ant-col-xxl-pull-13 { right: 54.16666667%; } .ant-col-xxl-offset-13 { margin-left: 54.16666667%; } .ant-col-xxl-order-13 { order: 13; } .ant-col-xxl-12 { display: block; flex: 0 0 50%; max-width: 50%; } .ant-col-xxl-push-12 { left: 50%; } .ant-col-xxl-pull-12 { right: 50%; } .ant-col-xxl-offset-12 { margin-left: 50%; } .ant-col-xxl-order-12 { order: 12; } .ant-col-xxl-11 { display: block; flex: 0 0 45.83333333%; max-width: 45.83333333%; } .ant-col-xxl-push-11 { left: 45.83333333%; } .ant-col-xxl-pull-11 { right: 45.83333333%; } .ant-col-xxl-offset-11 { margin-left: 45.83333333%; } .ant-col-xxl-order-11 { order: 11; } .ant-col-xxl-10 { display: block; flex: 0 0 41.66666667%; max-width: 41.66666667%; } .ant-col-xxl-push-10 { left: 41.66666667%; } .ant-col-xxl-pull-10 { right: 41.66666667%; } .ant-col-xxl-offset-10 { margin-left: 41.66666667%; } .ant-col-xxl-order-10 { order: 10; } .ant-col-xxl-9 { display: block; flex: 0 0 37.5%; max-width: 37.5%; } .ant-col-xxl-push-9 { left: 37.5%; } .ant-col-xxl-pull-9 { right: 37.5%; } .ant-col-xxl-offset-9 { margin-left: 37.5%; } .ant-col-xxl-order-9 { order: 9; } .ant-col-xxl-8 { display: block; flex: 0 0 33.33333333%; max-width: 33.33333333%; } .ant-col-xxl-push-8 { left: 33.33333333%; } .ant-col-xxl-pull-8 { right: 33.33333333%; } .ant-col-xxl-offset-8 { margin-left: 33.33333333%; } .ant-col-xxl-order-8 { order: 8; } .ant-col-xxl-7 { display: block; flex: 0 0 29.16666667%; max-width: 29.16666667%; } .ant-col-xxl-push-7 { left: 29.16666667%; } .ant-col-xxl-pull-7 { right: 29.16666667%; } .ant-col-xxl-offset-7 { margin-left: 29.16666667%; } .ant-col-xxl-order-7 { order: 7; } .ant-col-xxl-6 { display: block; flex: 0 0 25%; max-width: 25%; } .ant-col-xxl-push-6 { left: 25%; } .ant-col-xxl-pull-6 { right: 25%; } .ant-col-xxl-offset-6 { margin-left: 25%; } .ant-col-xxl-order-6 { order: 6; } .ant-col-xxl-5 { display: block; flex: 0 0 20.83333333%; max-width: 20.83333333%; } .ant-col-xxl-push-5 { left: 20.83333333%; } .ant-col-xxl-pull-5 { right: 20.83333333%; } .ant-col-xxl-offset-5 { margin-left: 20.83333333%; } .ant-col-xxl-order-5 { order: 5; } .ant-col-xxl-4 { display: block; flex: 0 0 16.66666667%; max-width: 16.66666667%; } .ant-col-xxl-push-4 { left: 16.66666667%; } .ant-col-xxl-pull-4 { right: 16.66666667%; } .ant-col-xxl-offset-4 { margin-left: 16.66666667%; } .ant-col-xxl-order-4 { order: 4; } .ant-col-xxl-3 { display: block; flex: 0 0 12.5%; max-width: 12.5%; } .ant-col-xxl-push-3 { left: 12.5%; } .ant-col-xxl-pull-3 { right: 12.5%; } .ant-col-xxl-offset-3 { margin-left: 12.5%; } .ant-col-xxl-order-3 { order: 3; } .ant-col-xxl-2 { display: block; flex: 0 0 8.33333333%; max-width: 8.33333333%; } .ant-col-xxl-push-2 { left: 8.33333333%; } .ant-col-xxl-pull-2 { right: 8.33333333%; } .ant-col-xxl-offset-2 { margin-left: 8.33333333%; } .ant-col-xxl-order-2 { order: 2; } .ant-col-xxl-1 { display: block; flex: 0 0 4.16666667%; max-width: 4.16666667%; } .ant-col-xxl-push-1 { left: 4.16666667%; } .ant-col-xxl-pull-1 { right: 4.16666667%; } .ant-col-xxl-offset-1 { margin-left: 4.16666667%; } .ant-col-xxl-order-1 { order: 1; } .ant-col-xxl-0 { display: none; } .ant-col-push-0 { left: auto; } .ant-col-pull-0 { right: auto; } .ant-col-xxl-push-0 { left: auto; } .ant-col-xxl-pull-0 { right: auto; } .ant-col-xxl-offset-0 { margin-left: 0; } .ant-col-xxl-order-0 { order: 0; } .ant-col-push-0.ant-col-rtl { right: auto; } .ant-col-pull-0.ant-col-rtl { left: auto; } .ant-col-xxl-push-0.ant-col-rtl { right: auto; } .ant-col-xxl-pull-0.ant-col-rtl { left: auto; } .ant-col-xxl-offset-0.ant-col-rtl { margin-right: 0; } .ant-col-xxl-push-1.ant-col-rtl { right: 4.16666667%; left: auto; } .ant-col-xxl-pull-1.ant-col-rtl { right: auto; left: 4.16666667%; } .ant-col-xxl-offset-1.ant-col-rtl { margin-right: 4.16666667%; margin-left: 0; } .ant-col-xxl-push-2.ant-col-rtl { right: 8.33333333%; left: auto; } .ant-col-xxl-pull-2.ant-col-rtl { right: auto; left: 8.33333333%; } .ant-col-xxl-offset-2.ant-col-rtl { margin-right: 8.33333333%; margin-left: 0; } .ant-col-xxl-push-3.ant-col-rtl { right: 12.5%; left: auto; } .ant-col-xxl-pull-3.ant-col-rtl { right: auto; left: 12.5%; } .ant-col-xxl-offset-3.ant-col-rtl { margin-right: 12.5%; margin-left: 0; } .ant-col-xxl-push-4.ant-col-rtl { right: 16.66666667%; left: auto; } .ant-col-xxl-pull-4.ant-col-rtl { right: auto; left: 16.66666667%; } .ant-col-xxl-offset-4.ant-col-rtl { margin-right: 16.66666667%; margin-left: 0; } .ant-col-xxl-push-5.ant-col-rtl { right: 20.83333333%; left: auto; } .ant-col-xxl-pull-5.ant-col-rtl { right: auto; left: 20.83333333%; } .ant-col-xxl-offset-5.ant-col-rtl { margin-right: 20.83333333%; margin-left: 0; } .ant-col-xxl-push-6.ant-col-rtl { right: 25%; left: auto; } .ant-col-xxl-pull-6.ant-col-rtl { right: auto; left: 25%; } .ant-col-xxl-offset-6.ant-col-rtl { margin-right: 25%; margin-left: 0; } .ant-col-xxl-push-7.ant-col-rtl { right: 29.16666667%; left: auto; } .ant-col-xxl-pull-7.ant-col-rtl { right: auto; left: 29.16666667%; } .ant-col-xxl-offset-7.ant-col-rtl { margin-right: 29.16666667%; margin-left: 0; } .ant-col-xxl-push-8.ant-col-rtl { right: 33.33333333%; left: auto; } .ant-col-xxl-pull-8.ant-col-rtl { right: auto; left: 33.33333333%; } .ant-col-xxl-offset-8.ant-col-rtl { margin-right: 33.33333333%; margin-left: 0; } .ant-col-xxl-push-9.ant-col-rtl { right: 37.5%; left: auto; } .ant-col-xxl-pull-9.ant-col-rtl { right: auto; left: 37.5%; } .ant-col-xxl-offset-9.ant-col-rtl { margin-right: 37.5%; margin-left: 0; } .ant-col-xxl-push-10.ant-col-rtl { right: 41.66666667%; left: auto; } .ant-col-xxl-pull-10.ant-col-rtl { right: auto; left: 41.66666667%; } .ant-col-xxl-offset-10.ant-col-rtl { margin-right: 41.66666667%; margin-left: 0; } .ant-col-xxl-push-11.ant-col-rtl { right: 45.83333333%; left: auto; } .ant-col-xxl-pull-11.ant-col-rtl { right: auto; left: 45.83333333%; } .ant-col-xxl-offset-11.ant-col-rtl { margin-right: 45.83333333%; margin-left: 0; } .ant-col-xxl-push-12.ant-col-rtl { right: 50%; left: auto; } .ant-col-xxl-pull-12.ant-col-rtl { right: auto; left: 50%; } .ant-col-xxl-offset-12.ant-col-rtl { margin-right: 50%; margin-left: 0; } .ant-col-xxl-push-13.ant-col-rtl { right: 54.16666667%; left: auto; } .ant-col-xxl-pull-13.ant-col-rtl { right: auto; left: 54.16666667%; } .ant-col-xxl-offset-13.ant-col-rtl { margin-right: 54.16666667%; margin-left: 0; } .ant-col-xxl-push-14.ant-col-rtl { right: 58.33333333%; left: auto; } .ant-col-xxl-pull-14.ant-col-rtl { right: auto; left: 58.33333333%; } .ant-col-xxl-offset-14.ant-col-rtl { margin-right: 58.33333333%; margin-left: 0; } .ant-col-xxl-push-15.ant-col-rtl { right: 62.5%; left: auto; } .ant-col-xxl-pull-15.ant-col-rtl { right: auto; left: 62.5%; } .ant-col-xxl-offset-15.ant-col-rtl { margin-right: 62.5%; margin-left: 0; } .ant-col-xxl-push-16.ant-col-rtl { right: 66.66666667%; left: auto; } .ant-col-xxl-pull-16.ant-col-rtl { right: auto; left: 66.66666667%; } .ant-col-xxl-offset-16.ant-col-rtl { margin-right: 66.66666667%; margin-left: 0; } .ant-col-xxl-push-17.ant-col-rtl { right: 70.83333333%; left: auto; } .ant-col-xxl-pull-17.ant-col-rtl { right: auto; left: 70.83333333%; } .ant-col-xxl-offset-17.ant-col-rtl { margin-right: 70.83333333%; margin-left: 0; } .ant-col-xxl-push-18.ant-col-rtl { right: 75%; left: auto; } .ant-col-xxl-pull-18.ant-col-rtl { right: auto; left: 75%; } .ant-col-xxl-offset-18.ant-col-rtl { margin-right: 75%; margin-left: 0; } .ant-col-xxl-push-19.ant-col-rtl { right: 79.16666667%; left: auto; } .ant-col-xxl-pull-19.ant-col-rtl { right: auto; left: 79.16666667%; } .ant-col-xxl-offset-19.ant-col-rtl { margin-right: 79.16666667%; margin-left: 0; } .ant-col-xxl-push-20.ant-col-rtl { right: 83.33333333%; left: auto; } .ant-col-xxl-pull-20.ant-col-rtl { right: auto; left: 83.33333333%; } .ant-col-xxl-offset-20.ant-col-rtl { margin-right: 83.33333333%; margin-left: 0; } .ant-col-xxl-push-21.ant-col-rtl { right: 87.5%; left: auto; } .ant-col-xxl-pull-21.ant-col-rtl { right: auto; left: 87.5%; } .ant-col-xxl-offset-21.ant-col-rtl { margin-right: 87.5%; margin-left: 0; } .ant-col-xxl-push-22.ant-col-rtl { right: 91.66666667%; left: auto; } .ant-col-xxl-pull-22.ant-col-rtl { right: auto; left: 91.66666667%; } .ant-col-xxl-offset-22.ant-col-rtl { margin-right: 91.66666667%; margin-left: 0; } .ant-col-xxl-push-23.ant-col-rtl { right: 95.83333333%; left: auto; } .ant-col-xxl-pull-23.ant-col-rtl { right: auto; left: 95.83333333%; } .ant-col-xxl-offset-23.ant-col-rtl { margin-right: 95.83333333%; margin-left: 0; } .ant-col-xxl-push-24.ant-col-rtl { right: 100%; left: auto; } .ant-col-xxl-pull-24.ant-col-rtl { right: auto; left: 100%; } .ant-col-xxl-offset-24.ant-col-rtl { margin-right: 100%; margin-left: 0; } } .ant-row-rtl { direction: rtl; } .ant-image { position: relative; display: inline-block; } .ant-image-img { width: 100%; height: auto; vertical-align: middle; } .ant-image-img-placeholder { background-color: #f5f5f5; background-image: url('data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTYiIGhlaWdodD0iMTYiIHZpZXdCb3g9IjAgMCAxNiAxNiIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48cGF0aCBkPSJNMTQuNSAyLjVoLTEzQS41LjUgMCAwIDAgMSAzdjEwYS41LjUgMCAwIDAgLjUuNWgxM2EuNS41IDAgMCAwIC41LS41VjNhLjUuNSAwIDAgMC0uNS0uNXpNNS4yODEgNC43NWExIDEgMCAwIDEgMCAyIDEgMSAwIDAgMSAwLTJ6bTguMDMgNi44M2EuMTI3LjEyNyAwIDAgMS0uMDgxLjAzSDIuNzY5YS4xMjUuMTI1IDAgMCAxLS4wOTYtLjIwN2wyLjY2MS0zLjE1NmEuMTI2LjEyNiAwIDAgMSAuMTc3LS4wMTZsLjAxNi4wMTZMNy4wOCAxMC4wOWwyLjQ3LTIuOTNhLjEyNi4xMjYgMCAwIDEgLjE3Ny0uMDE2bC4wMTUuMDE2IDMuNTg4IDQuMjQ0YS4xMjcuMTI3IDAgMCAxLS4wMi4xNzV6IiBmaWxsPSIjOEM4QzhDIiBmaWxsLXJ1bGU9Im5vbnplcm8iLz48L3N2Zz4='); background-repeat: no-repeat; background-position: center center; background-size: 30%; } .ant-image-mask { position: absolute; top: 0; right: 0; bottom: 0; left: 0; display: flex; align-items: center; justify-content: center; color: #fff; background: rgba(0, 0, 0, 0.5); cursor: pointer; opacity: 0; transition: opacity 0.3s; } .ant-image-mask-info { padding: 0 4px; overflow: hidden; white-space: nowrap; text-overflow: ellipsis; } .ant-image-mask-info .anticon { margin-inline-end: 4px; } .ant-image-mask:hover { opacity: 1; } .ant-image-placeholder { position: absolute; top: 0; right: 0; bottom: 0; left: 0; } .ant-image-preview { pointer-events: none; height: 100%; text-align: center; } .ant-image-preview.ant-zoom-enter, .ant-image-preview.ant-zoom-appear { transform: none; opacity: 0; animation-duration: 0.3s; user-select: none; } .ant-image-preview-mask { position: fixed; top: 0; right: 0; bottom: 0; left: 0; z-index: 1000; height: 100%; background-color: rgba(0, 0, 0, 0.45); } .ant-image-preview-mask-hidden { display: none; } .ant-image-preview-wrap { position: fixed; top: 0; right: 0; bottom: 0; left: 0; overflow: auto; outline: 0; } .ant-image-preview-body { position: absolute; top: 0; right: 0; bottom: 0; left: 0; overflow: hidden; } .ant-image-preview-img { max-width: 100%; max-height: 100%; vertical-align: middle; transform: scale3d(1, 1, 1); cursor: grab; transition: transform 0.3s cubic-bezier(0.215, 0.61, 0.355, 1) 0s; user-select: none; pointer-events: auto; } .ant-image-preview-img-wrapper { position: absolute; top: 0; right: 0; bottom: 0; left: 0; transition: transform 0.3s cubic-bezier(0.215, 0.61, 0.355, 1) 0s; } .ant-image-preview-img-wrapper::before { display: inline-block; width: 1px; height: 50%; margin-right: -1px; content: ''; } .ant-image-preview-moving .ant-image-preview-img { cursor: grabbing; } .ant-image-preview-moving .ant-image-preview-img-wrapper { transition-duration: 0s; } .ant-image-preview-wrap { z-index: 1080; } .ant-image-preview-operations-wrapper { position: fixed; top: 0; right: 0; z-index: 1081; width: 100%; } .ant-image-preview-operations { box-sizing: border-box; margin: 0; padding: 0; font-size: 14px; font-variant: tabular-nums; line-height: 1.5715; font-feature-settings: 'tnum'; display: flex; flex-direction: row-reverse; align-items: center; color: rgba(255, 255, 255, 0.85); list-style: none; background: rgba(0, 0, 0, 0.1); pointer-events: auto; } .ant-image-preview-operations-operation { margin-left: 12px; padding: 12px; cursor: pointer; transition: all 0.3s; } .ant-image-preview-operations-operation:hover { background: rgba(0, 0, 0, 0.2); } .ant-image-preview-operations-operation-disabled { color: rgba(255, 255, 255, 0.25); pointer-events: none; } .ant-image-preview-operations-operation:last-of-type { margin-left: 0; } .ant-image-preview-operations-progress { position: absolute; left: 50%; transform: translateX(-50%); } .ant-image-preview-operations-icon { font-size: 18px; } .ant-image-preview-switch-left, .ant-image-preview-switch-right { position: fixed; top: 50%; right: 8px; z-index: 1081; display: flex; align-items: center; justify-content: center; width: 44px; height: 44px; color: rgba(255, 255, 255, 0.85); background: rgba(0, 0, 0, 0.1); border-radius: 50%; transform: translateY(-50%); cursor: pointer; transition: all 0.3s; pointer-events: auto; } .ant-image-preview-switch-left:hover, .ant-image-preview-switch-right:hover { background: rgba(0, 0, 0, 0.2); } .ant-image-preview-switch-left-disabled, .ant-image-preview-switch-right-disabled, .ant-image-preview-switch-left-disabled:hover, .ant-image-preview-switch-right-disabled:hover { color: rgba(255, 255, 255, 0.25); background: rgba(0, 0, 0, 0.1); cursor: not-allowed; } .ant-image-preview-switch-left-disabled > .anticon, .ant-image-preview-switch-right-disabled > .anticon, .ant-image-preview-switch-left-disabled:hover > .anticon, .ant-image-preview-switch-right-disabled:hover > .anticon { cursor: not-allowed; } .ant-image-preview-switch-left > .anticon, .ant-image-preview-switch-right > .anticon { font-size: 18px; } .ant-image-preview-switch-left { left: 8px; } .ant-image-preview-switch-right { right: 8px; } .ant-input-affix-wrapper { position: relative; display: inline-block; width: 100%; min-width: 0; padding: 4px 11px; color: rgba(255, 255, 255, 0.85); font-size: 14px; line-height: 1.5715; background-color: transparent; background-image: none; border: 1px solid #434343; border-radius: 2px; transition: all 0.3s; display: inline-flex; } .ant-input-affix-wrapper::placeholder { color: rgba(255, 255, 255, 0.3); user-select: none; } .ant-input-affix-wrapper:placeholder-shown { text-overflow: ellipsis; } .ant-input-affix-wrapper:hover { border-color: #165996; border-right-width: 1px; } .ant-input-rtl .ant-input-affix-wrapper:hover { border-right-width: 0; border-left-width: 1px !important; } .ant-input-affix-wrapper:focus, .ant-input-affix-wrapper-focused { border-color: #177ddc; box-shadow: 0 0 0 2px rgba(23, 125, 220, 0.2); border-right-width: 1px; outline: 0; } .ant-input-rtl .ant-input-affix-wrapper:focus, .ant-input-rtl .ant-input-affix-wrapper-focused { border-right-width: 0; border-left-width: 1px !important; } .ant-input-affix-wrapper-disabled { color: rgba(255, 255, 255, 0.3); background-color: rgba(255, 255, 255, 0.08); border-color: #434343; box-shadow: none; cursor: not-allowed; opacity: 1; } .ant-input-affix-wrapper-disabled:hover { border-color: #434343; border-right-width: 1px; } .ant-input-affix-wrapper[disabled] { color: rgba(255, 255, 255, 0.3); background-color: rgba(255, 255, 255, 0.08); border-color: #434343; box-shadow: none; cursor: not-allowed; opacity: 1; } .ant-input-affix-wrapper[disabled]:hover { border-color: #434343; border-right-width: 1px; } .ant-input-affix-wrapper-borderless, .ant-input-affix-wrapper-borderless:hover, .ant-input-affix-wrapper-borderless:focus, .ant-input-affix-wrapper-borderless-focused, .ant-input-affix-wrapper-borderless-disabled, .ant-input-affix-wrapper-borderless[disabled] { background-color: transparent; border: none; box-shadow: none; } textarea.ant-input-affix-wrapper { max-width: 100%; height: auto; min-height: 32px; line-height: 1.5715; vertical-align: bottom; transition: all 0.3s, height 0s; } .ant-input-affix-wrapper-lg { padding: 6.5px 11px; font-size: 16px; } .ant-input-affix-wrapper-sm { padding: 0px 7px; } .ant-input-affix-wrapper-rtl { direction: rtl; } .ant-input-affix-wrapper:not(.ant-input-affix-wrapper-disabled):hover { border-color: #165996; border-right-width: 1px; z-index: 1; } .ant-input-rtl .ant-input-affix-wrapper:not(.ant-input-affix-wrapper-disabled):hover { border-right-width: 0; border-left-width: 1px !important; } .ant-input-search-with-button .ant-input-affix-wrapper:not(.ant-input-affix-wrapper-disabled):hover { z-index: 0; } .ant-input-affix-wrapper-focused, .ant-input-affix-wrapper:focus { z-index: 1; } .ant-input-affix-wrapper-disabled .ant-input[disabled] { background: rgba(255, 255, 255, 0); } .ant-input-affix-wrapper > .ant-input { font-size: inherit; border: none; outline: none; } .ant-input-affix-wrapper > .ant-input:focus { box-shadow: none !important; } .ant-input-affix-wrapper > .ant-input:not(textarea) { padding: 0; } .ant-input-affix-wrapper::before { display: inline-block; width: 0; visibility: hidden; content: '\a0'; } .ant-input-prefix, .ant-input-suffix { display: flex; flex: none; align-items: center; } .ant-input-prefix > *:not(:last-child), .ant-input-suffix > *:not(:last-child) { margin-right: 8px; } .ant-input-show-count-suffix { color: rgba(255, 255, 255, 0.45); } .ant-input-show-count-has-suffix { margin-right: 2px; } .ant-input-prefix { margin-right: 4px; } .ant-input-suffix { margin-left: 4px; } .anticon.ant-input-clear-icon, .ant-input-clear-icon { margin: 0; color: rgba(255, 255, 255, 0.3); font-size: 12px; vertical-align: -1px; cursor: pointer; transition: color 0.3s; } .anticon.ant-input-clear-icon:hover, .ant-input-clear-icon:hover { color: rgba(255, 255, 255, 0.45); } .anticon.ant-input-clear-icon:active, .ant-input-clear-icon:active { color: rgba(255, 255, 255, 0.85); } .anticon.ant-input-clear-icon-hidden, .ant-input-clear-icon-hidden { visibility: hidden; } .anticon.ant-input-clear-icon-has-suffix, .ant-input-clear-icon-has-suffix { margin: 0 4px; } .ant-input-affix-wrapper.ant-input-affix-wrapper-textarea-with-clear-btn { padding: 0; } .ant-input-affix-wrapper.ant-input-affix-wrapper-textarea-with-clear-btn .ant-input-clear-icon { position: absolute; top: 8px; right: 8px; z-index: 1; } .ant-input-status-error:not(.ant-input-disabled):not(.ant-input-borderless).ant-input, .ant-input-status-error:not(.ant-input-disabled):not(.ant-input-borderless).ant-input:hover { background: transparent; border-color: #a61d24; } .ant-input-status-error:not(.ant-input-disabled):not(.ant-input-borderless).ant-input:focus, .ant-input-status-error:not(.ant-input-disabled):not(.ant-input-borderless).ant-input-focused { border-color: #a61d24; box-shadow: 0 0 0 2px rgba(166, 29, 36, 0.2); border-right-width: 1px; outline: 0; } .ant-input-status-error .ant-input-prefix { color: #a61d24; } .ant-input-status-warning:not(.ant-input-disabled):not(.ant-input-borderless).ant-input, .ant-input-status-warning:not(.ant-input-disabled):not(.ant-input-borderless).ant-input:hover { background: transparent; border-color: #d89614; } .ant-input-status-warning:not(.ant-input-disabled):not(.ant-input-borderless).ant-input:focus, .ant-input-status-warning:not(.ant-input-disabled):not(.ant-input-borderless).ant-input-focused { border-color: #d89614; box-shadow: 0 0 0 2px rgba(216, 150, 20, 0.2); border-right-width: 1px; outline: 0; } .ant-input-status-warning .ant-input-prefix { color: #d89614; } .ant-input-affix-wrapper-status-error:not(.ant-input-affix-wrapper-disabled):not(.ant-input-affix-wrapper-borderless).ant-input-affix-wrapper, .ant-input-affix-wrapper-status-error:not(.ant-input-affix-wrapper-disabled):not(.ant-input-affix-wrapper-borderless).ant-input-affix-wrapper:hover { background: transparent; border-color: #a61d24; } .ant-input-affix-wrapper-status-error:not(.ant-input-affix-wrapper-disabled):not(.ant-input-affix-wrapper-borderless).ant-input-affix-wrapper:focus, .ant-input-affix-wrapper-status-error:not(.ant-input-affix-wrapper-disabled):not(.ant-input-affix-wrapper-borderless).ant-input-affix-wrapper-focused { border-color: #a61d24; box-shadow: 0 0 0 2px rgba(166, 29, 36, 0.2); border-right-width: 1px; outline: 0; } .ant-input-affix-wrapper-status-error .ant-input-prefix { color: #a61d24; } .ant-input-affix-wrapper-status-warning:not(.ant-input-affix-wrapper-disabled):not(.ant-input-affix-wrapper-borderless).ant-input-affix-wrapper, .ant-input-affix-wrapper-status-warning:not(.ant-input-affix-wrapper-disabled):not(.ant-input-affix-wrapper-borderless).ant-input-affix-wrapper:hover { background: transparent; border-color: #d89614; } .ant-input-affix-wrapper-status-warning:not(.ant-input-affix-wrapper-disabled):not(.ant-input-affix-wrapper-borderless).ant-input-affix-wrapper:focus, .ant-input-affix-wrapper-status-warning:not(.ant-input-affix-wrapper-disabled):not(.ant-input-affix-wrapper-borderless).ant-input-affix-wrapper-focused { border-color: #d89614; box-shadow: 0 0 0 2px rgba(216, 150, 20, 0.2); border-right-width: 1px; outline: 0; } .ant-input-affix-wrapper-status-warning .ant-input-prefix { color: #d89614; } .ant-input-textarea-status-error.ant-input-textarea-has-feedback .ant-input, .ant-input-textarea-status-warning.ant-input-textarea-has-feedback .ant-input, .ant-input-textarea-status-success.ant-input-textarea-has-feedback .ant-input, .ant-input-textarea-status-validating.ant-input-textarea-has-feedback .ant-input { padding-right: 24px; } .ant-input-group-wrapper-status-error .ant-input-group-addon { color: #a61d24; border-color: #a61d24; } .ant-input-group-wrapper-status-warning .ant-input-group-addon { color: #d89614; border-color: #d89614; } .ant-input { box-sizing: border-box; margin: 0; padding: 0; font-variant: tabular-nums; list-style: none; font-feature-settings: 'tnum'; position: relative; display: inline-block; width: 100%; min-width: 0; padding: 4px 11px; color: rgba(255, 255, 255, 0.85); font-size: 14px; line-height: 1.5715; background-color: transparent; background-image: none; border: 1px solid #434343; border-radius: 2px; transition: all 0.3s; } .ant-input::placeholder { color: rgba(255, 255, 255, 0.3); user-select: none; } .ant-input:placeholder-shown { text-overflow: ellipsis; } .ant-input:hover { border-color: #165996; border-right-width: 1px; } .ant-input-rtl .ant-input:hover { border-right-width: 0; border-left-width: 1px !important; } .ant-input:focus, .ant-input-focused { border-color: #177ddc; box-shadow: 0 0 0 2px rgba(23, 125, 220, 0.2); border-right-width: 1px; outline: 0; } .ant-input-rtl .ant-input:focus, .ant-input-rtl .ant-input-focused { border-right-width: 0; border-left-width: 1px !important; } .ant-input-disabled { color: rgba(255, 255, 255, 0.3); background-color: rgba(255, 255, 255, 0.08); border-color: #434343; box-shadow: none; cursor: not-allowed; opacity: 1; } .ant-input-disabled:hover { border-color: #434343; border-right-width: 1px; } .ant-input[disabled] { color: rgba(255, 255, 255, 0.3); background-color: rgba(255, 255, 255, 0.08); border-color: #434343; box-shadow: none; cursor: not-allowed; opacity: 1; } .ant-input[disabled]:hover { border-color: #434343; border-right-width: 1px; } .ant-input-borderless, .ant-input-borderless:hover, .ant-input-borderless:focus, .ant-input-borderless-focused, .ant-input-borderless-disabled, .ant-input-borderless[disabled] { background-color: transparent; border: none; box-shadow: none; } textarea.ant-input { max-width: 100%; height: auto; min-height: 32px; line-height: 1.5715; vertical-align: bottom; transition: all 0.3s, height 0s; } .ant-input-lg { padding: 6.5px 11px; font-size: 16px; } .ant-input-sm { padding: 0px 7px; } .ant-input-rtl { direction: rtl; } .ant-input-group { box-sizing: border-box; margin: 0; padding: 0; color: rgba(255, 255, 255, 0.85); font-size: 14px; font-variant: tabular-nums; line-height: 1.5715; list-style: none; font-feature-settings: 'tnum'; position: relative; display: table; width: 100%; border-collapse: separate; border-spacing: 0; } .ant-input-group[class*='col-'] { float: none; padding-right: 0; padding-left: 0; } .ant-input-group > [class*='col-'] { padding-right: 8px; } .ant-input-group > [class*='col-']:last-child { padding-right: 0; } .ant-input-group-addon, .ant-input-group-wrap, .ant-input-group > .ant-input { display: table-cell; } .ant-input-group-addon:not(:first-child):not(:last-child), .ant-input-group-wrap:not(:first-child):not(:last-child), .ant-input-group > .ant-input:not(:first-child):not(:last-child) { border-radius: 0; } .ant-input-group-addon, .ant-input-group-wrap { width: 1px; white-space: nowrap; vertical-align: middle; } .ant-input-group-wrap > * { display: block !important; } .ant-input-group .ant-input { float: left; width: 100%; margin-bottom: 0; text-align: inherit; } .ant-input-group .ant-input:focus { z-index: 1; border-right-width: 1px; } .ant-input-group .ant-input:hover { z-index: 1; border-right-width: 1px; } .ant-input-search-with-button .ant-input-group .ant-input:hover { z-index: 0; } .ant-input-group-addon { position: relative; padding: 0 11px; color: rgba(255, 255, 255, 0.85); font-weight: normal; font-size: 14px; text-align: center; background-color: rgba(255, 255, 255, 0.04); border: 1px solid #434343; border-radius: 2px; transition: all 0.3s; } .ant-input-group-addon .ant-select { margin: -5px -11px; } .ant-input-group-addon .ant-select.ant-select-single:not(.ant-select-customize-input) .ant-select-selector { background-color: inherit; border: 1px solid transparent; box-shadow: none; } .ant-input-group-addon .ant-select-open .ant-select-selector, .ant-input-group-addon .ant-select-focused .ant-select-selector { color: #177ddc; } .ant-input-group-addon .ant-cascader-picker { margin: -9px -12px; background-color: transparent; } .ant-input-group-addon .ant-cascader-picker .ant-cascader-input { text-align: left; border: 0; box-shadow: none; } .ant-input-group > .ant-input:first-child, .ant-input-group-addon:first-child { border-top-right-radius: 0; border-bottom-right-radius: 0; } .ant-input-group > .ant-input:first-child .ant-select .ant-select-selector, .ant-input-group-addon:first-child .ant-select .ant-select-selector { border-top-right-radius: 0; border-bottom-right-radius: 0; } .ant-input-group > .ant-input-affix-wrapper:not(:first-child) .ant-input { border-top-left-radius: 0; border-bottom-left-radius: 0; } .ant-input-group > .ant-input-affix-wrapper:not(:last-child) .ant-input { border-top-right-radius: 0; border-bottom-right-radius: 0; } .ant-input-group-addon:first-child { border-right: 0; } .ant-input-group-addon:last-child { border-left: 0; } .ant-input-group > .ant-input:last-child, .ant-input-group-addon:last-child { border-top-left-radius: 0; border-bottom-left-radius: 0; } .ant-input-group > .ant-input:last-child .ant-select .ant-select-selector, .ant-input-group-addon:last-child .ant-select .ant-select-selector { border-top-left-radius: 0; border-bottom-left-radius: 0; } .ant-input-group-lg .ant-input, .ant-input-group-lg > .ant-input-group-addon { padding: 6.5px 11px; font-size: 16px; } .ant-input-group-sm .ant-input, .ant-input-group-sm > .ant-input-group-addon { padding: 0px 7px; } .ant-input-group-lg .ant-select-single .ant-select-selector { height: 40px; } .ant-input-group-sm .ant-select-single .ant-select-selector { height: 24px; } .ant-input-group .ant-input-affix-wrapper:not(:last-child) { border-top-right-radius: 0; border-bottom-right-radius: 0; } .ant-input-search .ant-input-group .ant-input-affix-wrapper:not(:last-child) { border-top-left-radius: 2px; border-bottom-left-radius: 2px; } .ant-input-group .ant-input-affix-wrapper:not(:first-child), .ant-input-search .ant-input-group .ant-input-affix-wrapper:not(:first-child) { border-top-left-radius: 0; border-bottom-left-radius: 0; } .ant-input-group.ant-input-group-compact { display: block; } .ant-input-group.ant-input-group-compact::before { display: table; content: ''; } .ant-input-group.ant-input-group-compact::after { display: table; clear: both; content: ''; } .ant-input-group.ant-input-group-compact::before { display: table; content: ''; } .ant-input-group.ant-input-group-compact::after { display: table; clear: both; content: ''; } .ant-input-group.ant-input-group-compact-addon:not(:first-child):not(:last-child), .ant-input-group.ant-input-group-compact-wrap:not(:first-child):not(:last-child), .ant-input-group.ant-input-group-compact > .ant-input:not(:first-child):not(:last-child) { border-right-width: 1px; } .ant-input-group.ant-input-group-compact-addon:not(:first-child):not(:last-child):hover, .ant-input-group.ant-input-group-compact-wrap:not(:first-child):not(:last-child):hover, .ant-input-group.ant-input-group-compact > .ant-input:not(:first-child):not(:last-child):hover { z-index: 1; } .ant-input-group.ant-input-group-compact-addon:not(:first-child):not(:last-child):focus, .ant-input-group.ant-input-group-compact-wrap:not(:first-child):not(:last-child):focus, .ant-input-group.ant-input-group-compact > .ant-input:not(:first-child):not(:last-child):focus { z-index: 1; } .ant-input-group.ant-input-group-compact > * { display: inline-block; float: none; vertical-align: top; border-radius: 0; } .ant-input-group.ant-input-group-compact > .ant-input-affix-wrapper, .ant-input-group.ant-input-group-compact > .ant-input-number-affix-wrapper, .ant-input-group.ant-input-group-compact > .ant-picker-range { display: inline-flex; } .ant-input-group.ant-input-group-compact > *:not(:last-child) { margin-right: -1px; border-right-width: 1px; } .ant-input-group.ant-input-group-compact .ant-input { float: none; } .ant-input-group.ant-input-group-compact > .ant-select > .ant-select-selector, .ant-input-group.ant-input-group-compact > .ant-select-auto-complete .ant-input, .ant-input-group.ant-input-group-compact > .ant-cascader-picker .ant-input, .ant-input-group.ant-input-group-compact > .ant-input-group-wrapper .ant-input { border-right-width: 1px; border-radius: 0; } .ant-input-group.ant-input-group-compact > .ant-select > .ant-select-selector:hover, .ant-input-group.ant-input-group-compact > .ant-select-auto-complete .ant-input:hover, .ant-input-group.ant-input-group-compact > .ant-cascader-picker .ant-input:hover, .ant-input-group.ant-input-group-compact > .ant-input-group-wrapper .ant-input:hover { z-index: 1; } .ant-input-group.ant-input-group-compact > .ant-select > .ant-select-selector:focus, .ant-input-group.ant-input-group-compact > .ant-select-auto-complete .ant-input:focus, .ant-input-group.ant-input-group-compact > .ant-cascader-picker .ant-input:focus, .ant-input-group.ant-input-group-compact > .ant-input-group-wrapper .ant-input:focus { z-index: 1; } .ant-input-group.ant-input-group-compact > .ant-select-focused { z-index: 1; } .ant-input-group.ant-input-group-compact > .ant-select > .ant-select-arrow { z-index: 1; } .ant-input-group.ant-input-group-compact > *:first-child, .ant-input-group.ant-input-group-compact > .ant-select:first-child > .ant-select-selector, .ant-input-group.ant-input-group-compact > .ant-select-auto-complete:first-child .ant-input, .ant-input-group.ant-input-group-compact > .ant-cascader-picker:first-child .ant-input { border-top-left-radius: 2px; border-bottom-left-radius: 2px; } .ant-input-group.ant-input-group-compact > *:last-child, .ant-input-group.ant-input-group-compact > .ant-select:last-child > .ant-select-selector, .ant-input-group.ant-input-group-compact > .ant-cascader-picker:last-child .ant-input, .ant-input-group.ant-input-group-compact > .ant-cascader-picker-focused:last-child .ant-input { border-right-width: 1px; border-top-right-radius: 2px; border-bottom-right-radius: 2px; } .ant-input-group.ant-input-group-compact > .ant-select-auto-complete .ant-input { vertical-align: top; } .ant-input-group.ant-input-group-compact .ant-input-group-wrapper + .ant-input-group-wrapper { margin-left: -1px; } .ant-input-group.ant-input-group-compact .ant-input-group-wrapper + .ant-input-group-wrapper .ant-input-affix-wrapper { border-radius: 0; } .ant-input-group.ant-input-group-compact .ant-input-group-wrapper:not(:last-child).ant-input-search > .ant-input-group > .ant-input-group-addon > .ant-input-search-button { border-radius: 0; } .ant-input-group.ant-input-group-compact .ant-input-group-wrapper:not(:last-child).ant-input-search > .ant-input-group > .ant-input { border-radius: 2px 0 0 2px; } .ant-input-group > .ant-input-rtl:first-child, .ant-input-group-rtl .ant-input-group-addon:first-child { border-radius: 0 2px 2px 0; } .ant-input-group-rtl .ant-input-group-addon:first-child { border-right: 1px solid #434343; border-left: 0; } .ant-input-group-rtl .ant-input-group-addon:last-child { border-right: 0; border-left: 1px solid #434343; border-radius: 2px 0 0 2px; } .ant-input-group-rtl.ant-input-group > .ant-input:last-child, .ant-input-group-rtl.ant-input-group-addon:last-child { border-radius: 2px 0 0 2px; } .ant-input-group-rtl.ant-input-group .ant-input-affix-wrapper:not(:first-child) { border-radius: 2px 0 0 2px; } .ant-input-group-rtl.ant-input-group .ant-input-affix-wrapper:not(:last-child) { border-radius: 0 2px 2px 0; } .ant-input-group-rtl.ant-input-group.ant-input-group-compact > *:not(:last-child) { margin-right: 0; margin-left: -1px; border-left-width: 1px; } .ant-input-group-rtl.ant-input-group.ant-input-group-compact > *:first-child, .ant-input-group-rtl.ant-input-group.ant-input-group-compact > .ant-select:first-child > .ant-select-selector, .ant-input-group-rtl.ant-input-group.ant-input-group-compact > .ant-select-auto-complete:first-child .ant-input, .ant-input-group-rtl.ant-input-group.ant-input-group-compact > .ant-cascader-picker:first-child .ant-input { border-radius: 0 2px 2px 0; } .ant-input-group-rtl.ant-input-group.ant-input-group-compact > *:last-child, .ant-input-group-rtl.ant-input-group.ant-input-group-compact > .ant-select:last-child > .ant-select-selector, .ant-input-group-rtl.ant-input-group.ant-input-group-compact > .ant-select-auto-complete:last-child .ant-input, .ant-input-group-rtl.ant-input-group.ant-input-group-compact > .ant-cascader-picker:last-child .ant-input, .ant-input-group-rtl.ant-input-group.ant-input-group-compact > .ant-cascader-picker-focused:last-child .ant-input { border-left-width: 1px; border-radius: 2px 0 0 2px; } .ant-input-group.ant-input-group-compact .ant-input-group-wrapper-rtl + .ant-input-group-wrapper-rtl { margin-right: -1px; margin-left: 0; } .ant-input-group.ant-input-group-compact .ant-input-group-wrapper-rtl:not(:last-child).ant-input-search > .ant-input-group > .ant-input { border-radius: 0 2px 2px 0; } .ant-input-group > .ant-input-rtl:first-child { border-radius: 0 2px 2px 0; } .ant-input-group > .ant-input-rtl:last-child { border-radius: 2px 0 0 2px; } .ant-input-group-rtl .ant-input-group-addon:first-child { border-right: 1px solid #434343; border-left: 0; border-radius: 0 2px 2px 0; } .ant-input-group-rtl .ant-input-group-addon:last-child { border-right: 0; border-left: 1px solid #434343; border-radius: 2px 0 0 2px; } .ant-input-group-wrapper { display: inline-block; width: 100%; text-align: start; vertical-align: top; } .ant-input-password-icon.anticon { color: rgba(255, 255, 255, 0.45); cursor: pointer; transition: all 0.3s; } .ant-input-password-icon.anticon:hover { color: rgba(255, 255, 255, 0.85); } .ant-input[type='color'] { height: 32px; } .ant-input[type='color'].ant-input-lg { height: 40px; } .ant-input[type='color'].ant-input-sm { height: 24px; padding-top: 3px; padding-bottom: 3px; } .ant-input-textarea-show-count > .ant-input { height: 100%; } .ant-input-textarea-show-count::after { float: right; color: rgba(255, 255, 255, 0.45); white-space: nowrap; content: attr(data-count); pointer-events: none; } .ant-input-textarea-show-count.ant-input-textarea-in-form-item::after { margin-bottom: -22px; } .ant-input-textarea-suffix { position: absolute; top: 0; right: 11px; bottom: 0; z-index: 1; display: inline-flex; align-items: center; margin: auto; } .ant-input-compact-item:not(.ant-input-compact-last-item):not(.ant-input-compact-item-rtl) { margin-right: -1px; } .ant-input-compact-item:not(.ant-input-compact-last-item).ant-input-compact-item-rtl { margin-left: -1px; } .ant-input-compact-item:hover, .ant-input-compact-item:focus, .ant-input-compact-item:active { z-index: 2; } .ant-input-compact-item[disabled] { z-index: 0; } .ant-input-compact-item:not(.ant-input-compact-first-item):not(.ant-input-compact-last-item).ant-input { border-radius: 0; } .ant-input-compact-item.ant-input.ant-input-compact-first-item:not(.ant-input-compact-last-item):not(.ant-input-compact-item-rtl) { border-top-right-radius: 0; border-bottom-right-radius: 0; } .ant-input-compact-item.ant-input.ant-input-compact-last-item:not(.ant-input-compact-first-item):not(.ant-input-compact-item-rtl) { border-top-left-radius: 0; border-bottom-left-radius: 0; } .ant-input-compact-item.ant-input.ant-input-compact-item-rtl.ant-input-compact-first-item:not(.ant-input-compact-last-item) { border-top-left-radius: 0; border-bottom-left-radius: 0; } .ant-input-compact-item.ant-input.ant-input-compact-item-rtl.ant-input-compact-last-item:not(.ant-input-compact-first-item) { border-top-right-radius: 0; border-bottom-right-radius: 0; } .ant-input-search .ant-input:hover, .ant-input-search .ant-input:focus { border-color: #165996; } .ant-input-search .ant-input:hover + .ant-input-group-addon .ant-input-search-button:not(.ant-btn-primary), .ant-input-search .ant-input:focus + .ant-input-group-addon .ant-input-search-button:not(.ant-btn-primary) { border-left-color: #165996; } .ant-input-search .ant-input-affix-wrapper { border-radius: 0; } .ant-input-search .ant-input-lg { line-height: 1.5713; } .ant-input-search > .ant-input-group > .ant-input-group-addon:last-child { left: -1px; padding: 0; border: 0; } .ant-input-search > .ant-input-group > .ant-input-group-addon:last-child .ant-input-search-button { padding-top: 0; padding-bottom: 0; border-radius: 0 2px 2px 0; } .ant-input-search > .ant-input-group > .ant-input-group-addon:last-child .ant-input-search-button:not(.ant-btn-primary) { color: rgba(255, 255, 255, 0.45); } .ant-input-search > .ant-input-group > .ant-input-group-addon:last-child .ant-input-search-button:not(.ant-btn-primary).ant-btn-loading::before { top: 0; right: 0; bottom: 0; left: 0; } .ant-input-search-button { height: 32px; } .ant-input-search-button:hover, .ant-input-search-button:focus { z-index: 1; } .ant-input-search-large .ant-input-search-button { height: 40px; } .ant-input-search-small .ant-input-search-button { height: 24px; } .ant-input-search.ant-input-compact-item:not(.ant-input-compact-item-rtl):not(.ant-input-compact-last-item) .ant-input-group-addon .ant-input-search-button { margin-right: -1px; border-radius: 0; } .ant-input-search.ant-input-compact-item:not(.ant-input-compact-first-item) .ant-input, .ant-input-search.ant-input-compact-item:not(.ant-input-compact-first-item) .ant-input-affix-wrapper { border-radius: 0; } .ant-input-search.ant-input-compact-item > .ant-input-group-addon .ant-input-search-button:hover, .ant-input-search.ant-input-compact-item > .ant-input:hover, .ant-input-search.ant-input-compact-item .ant-input-affix-wrapper:hover, .ant-input-search.ant-input-compact-item > .ant-input-group-addon .ant-input-search-button:focus, .ant-input-search.ant-input-compact-item > .ant-input:focus, .ant-input-search.ant-input-compact-item .ant-input-affix-wrapper:focus, .ant-input-search.ant-input-compact-item > .ant-input-group-addon .ant-input-search-button:active, .ant-input-search.ant-input-compact-item > .ant-input:active, .ant-input-search.ant-input-compact-item .ant-input-affix-wrapper:active { z-index: 2; } .ant-input-search.ant-input-compact-item > .ant-input-affix-wrapper-focused { z-index: 2; } .ant-input-search.ant-input-compact-item-rtl:not(.ant-input-compact-last-item) .ant-input-group-addon:last-child .ant-input-search-button { margin-left: -1px; border-radius: 0; } .ant-input-group-wrapper-rtl { direction: rtl; } .ant-input-group-rtl { direction: rtl; } .ant-input-affix-wrapper.ant-input-affix-wrapper-rtl > input.ant-input { border: none; outline: none; } .ant-input-affix-wrapper-rtl .ant-input-prefix { margin: 0 0 0 4px; } .ant-input-affix-wrapper-rtl .ant-input-suffix { margin: 0 4px 0 0; } .ant-input-textarea-rtl { direction: rtl; } .ant-input-textarea-rtl.ant-input-textarea-show-count::after { text-align: left; } .ant-input-affix-wrapper-rtl .ant-input-clear-icon-has-suffix { margin-right: 0; margin-left: 4px; } .ant-input-affix-wrapper-rtl .ant-input-clear-icon { right: auto; left: 8px; } .ant-input-search-rtl { direction: rtl; } .ant-input-search-rtl .ant-input:hover + .ant-input-group-addon .ant-input-search-button:not(.ant-btn-primary), .ant-input-search-rtl .ant-input:focus + .ant-input-group-addon .ant-input-search-button:not(.ant-btn-primary) { border-left-color: #434343; } .ant-input-search-rtl .ant-input:hover + .ant-input-group-addon .ant-input-search-button:not(.ant-btn-primary):hover, .ant-input-search-rtl .ant-input:focus + .ant-input-group-addon .ant-input-search-button:not(.ant-btn-primary):hover { border-left-color: #165996; } .ant-input-search-rtl > .ant-input-group > .ant-input-affix-wrapper:hover, .ant-input-search-rtl > .ant-input-group > .ant-input-affix-wrapper-focused { border-right-color: #165996; } .ant-input-search-rtl > .ant-input-group > .ant-input-group-addon:last-child { right: -1px; left: auto; } .ant-input-search-rtl > .ant-input-group > .ant-input-group-addon:last-child .ant-input-search-button { border-radius: 2px 0 0 2px; } @media screen and (-ms-high-contrast: active), (-ms-high-contrast: none) { .ant-input { height: 32px; } .ant-input-lg { height: 40px; } .ant-input-sm { height: 24px; } .ant-input-affix-wrapper > input.ant-input { height: auto; } } .ant-input-number-affix-wrapper { display: inline-block; width: 100%; min-width: 0; padding: 4px 11px; color: rgba(255, 255, 255, 0.85); font-size: 14px; line-height: 1.5715; background-color: transparent; background-image: none; border: 1px solid #434343; border-radius: 2px; transition: all 0.3s; position: relative; display: inline-flex; width: 90px; padding: 0; padding-inline-start: 11px; } .ant-input-number-affix-wrapper::placeholder { color: rgba(255, 255, 255, 0.3); user-select: none; } .ant-input-number-affix-wrapper:placeholder-shown { text-overflow: ellipsis; } .ant-input-number-affix-wrapper:hover { border-color: #165996; border-right-width: 1px; } .ant-input-rtl .ant-input-number-affix-wrapper:hover { border-right-width: 0; border-left-width: 1px !important; } .ant-input-number-affix-wrapper:focus, .ant-input-number-affix-wrapper-focused { border-color: #177ddc; box-shadow: 0 0 0 2px rgba(23, 125, 220, 0.2); border-right-width: 1px; outline: 0; } .ant-input-rtl .ant-input-number-affix-wrapper:focus, .ant-input-rtl .ant-input-number-affix-wrapper-focused { border-right-width: 0; border-left-width: 1px !important; } .ant-input-number-affix-wrapper-disabled { color: rgba(255, 255, 255, 0.3); background-color: rgba(255, 255, 255, 0.08); border-color: #434343; box-shadow: none; cursor: not-allowed; opacity: 1; } .ant-input-number-affix-wrapper-disabled:hover { border-color: #434343; border-right-width: 1px; } .ant-input-number-affix-wrapper[disabled] { color: rgba(255, 255, 255, 0.3); background-color: rgba(255, 255, 255, 0.08); border-color: #434343; box-shadow: none; cursor: not-allowed; opacity: 1; } .ant-input-number-affix-wrapper[disabled]:hover { border-color: #434343; border-right-width: 1px; } .ant-input-number-affix-wrapper-borderless, .ant-input-number-affix-wrapper-borderless:hover, .ant-input-number-affix-wrapper-borderless:focus, .ant-input-number-affix-wrapper-borderless-focused, .ant-input-number-affix-wrapper-borderless-disabled, .ant-input-number-affix-wrapper-borderless[disabled] { background-color: transparent; border: none; box-shadow: none; } textarea.ant-input-number-affix-wrapper { max-width: 100%; height: auto; min-height: 32px; line-height: 1.5715; vertical-align: bottom; transition: all 0.3s, height 0s; } .ant-input-number-affix-wrapper-lg { padding: 6.5px 11px; font-size: 16px; } .ant-input-number-affix-wrapper-sm { padding: 0px 7px; } .ant-input-number-affix-wrapper-rtl { direction: rtl; } .ant-input-number-affix-wrapper:not(.ant-input-number-affix-wrapper-disabled):hover { border-color: #165996; border-right-width: 1px; z-index: 1; } .ant-input-rtl .ant-input-number-affix-wrapper:not(.ant-input-number-affix-wrapper-disabled):hover { border-right-width: 0; border-left-width: 1px !important; } .ant-input-number-affix-wrapper-focused, .ant-input-number-affix-wrapper:focus { z-index: 1; } .ant-input-number-affix-wrapper-disabled .ant-input-number[disabled] { background: transparent; } .ant-input-number-affix-wrapper > div.ant-input-number { width: 100%; border: none; outline: none; } .ant-input-number-affix-wrapper > div.ant-input-number.ant-input-number-focused { box-shadow: none !important; } .ant-input-number-affix-wrapper input.ant-input-number-input { padding: 0; } .ant-input-number-affix-wrapper::before { display: inline-block; width: 0; visibility: hidden; content: '\a0'; } .ant-input-number-affix-wrapper .ant-input-number-handler-wrap { z-index: 2; } .ant-input-number-prefix, .ant-input-number-suffix { display: flex; flex: none; align-items: center; pointer-events: none; } .ant-input-number-prefix { margin-inline-end: 4px; } .ant-input-number-suffix { position: absolute; top: 0; right: 0; z-index: 1; height: 100%; margin-right: 11px; margin-left: 4px; } .ant-input-number-group-wrapper .ant-input-number-affix-wrapper { width: 100%; } .ant-input-number-status-error:not(.ant-input-number-disabled):not(.ant-input-number-borderless).ant-input-number, .ant-input-number-status-error:not(.ant-input-number-disabled):not(.ant-input-number-borderless).ant-input-number:hover { background: transparent; border-color: #a61d24; } .ant-input-number-status-error:not(.ant-input-number-disabled):not(.ant-input-number-borderless).ant-input-number:focus, .ant-input-number-status-error:not(.ant-input-number-disabled):not(.ant-input-number-borderless).ant-input-number-focused { border-color: #a61d24; box-shadow: 0 0 0 2px rgba(166, 29, 36, 0.2); border-right-width: 1px; outline: 0; } .ant-input-number-status-error .ant-input-number-prefix { color: #a61d24; } .ant-input-number-status-warning:not(.ant-input-number-disabled):not(.ant-input-number-borderless).ant-input-number, .ant-input-number-status-warning:not(.ant-input-number-disabled):not(.ant-input-number-borderless).ant-input-number:hover { background: transparent; border-color: #d89614; } .ant-input-number-status-warning:not(.ant-input-number-disabled):not(.ant-input-number-borderless).ant-input-number:focus, .ant-input-number-status-warning:not(.ant-input-number-disabled):not(.ant-input-number-borderless).ant-input-number-focused { border-color: #d89614; box-shadow: 0 0 0 2px rgba(216, 150, 20, 0.2); border-right-width: 1px; outline: 0; } .ant-input-number-status-warning .ant-input-number-prefix { color: #d89614; } .ant-input-number-affix-wrapper-status-error:not(.ant-input-number-affix-wrapper-disabled):not(.ant-input-number-affix-wrapper-borderless).ant-input-number-affix-wrapper, .ant-input-number-affix-wrapper-status-error:not(.ant-input-number-affix-wrapper-disabled):not(.ant-input-number-affix-wrapper-borderless).ant-input-number-affix-wrapper:hover { background: transparent; border-color: #a61d24; } .ant-input-number-affix-wrapper-status-error:not(.ant-input-number-affix-wrapper-disabled):not(.ant-input-number-affix-wrapper-borderless).ant-input-number-affix-wrapper:focus, .ant-input-number-affix-wrapper-status-error:not(.ant-input-number-affix-wrapper-disabled):not(.ant-input-number-affix-wrapper-borderless).ant-input-number-affix-wrapper-focused { border-color: #a61d24; box-shadow: 0 0 0 2px rgba(166, 29, 36, 0.2); border-right-width: 1px; outline: 0; } .ant-input-number-affix-wrapper-status-error .ant-input-number-prefix { color: #a61d24; } .ant-input-number-affix-wrapper-status-warning:not(.ant-input-number-affix-wrapper-disabled):not(.ant-input-number-affix-wrapper-borderless).ant-input-number-affix-wrapper, .ant-input-number-affix-wrapper-status-warning:not(.ant-input-number-affix-wrapper-disabled):not(.ant-input-number-affix-wrapper-borderless).ant-input-number-affix-wrapper:hover { background: transparent; border-color: #d89614; } .ant-input-number-affix-wrapper-status-warning:not(.ant-input-number-affix-wrapper-disabled):not(.ant-input-number-affix-wrapper-borderless).ant-input-number-affix-wrapper:focus, .ant-input-number-affix-wrapper-status-warning:not(.ant-input-number-affix-wrapper-disabled):not(.ant-input-number-affix-wrapper-borderless).ant-input-number-affix-wrapper-focused { border-color: #d89614; box-shadow: 0 0 0 2px rgba(216, 150, 20, 0.2); border-right-width: 1px; outline: 0; } .ant-input-number-affix-wrapper-status-warning .ant-input-number-prefix { color: #d89614; } .ant-input-number-group-wrapper-status-error .ant-input-number-group-addon { color: #a61d24; border-color: #a61d24; } .ant-input-number-group-wrapper-status-warning .ant-input-number-group-addon { color: #d89614; border-color: #d89614; } .ant-input-number { box-sizing: border-box; font-variant: tabular-nums; list-style: none; font-feature-settings: 'tnum'; position: relative; width: 100%; min-width: 0; padding: 4px 11px; color: rgba(255, 255, 255, 0.85); font-size: 14px; line-height: 1.5715; background-color: transparent; background-image: none; transition: all 0.3s; display: inline-block; width: 90px; margin: 0; padding: 0; border: 1px solid #434343; border-radius: 2px; } .ant-input-number::placeholder { color: rgba(255, 255, 255, 0.3); user-select: none; } .ant-input-number:placeholder-shown { text-overflow: ellipsis; } .ant-input-number:hover { border-color: #165996; border-right-width: 1px; } .ant-input-rtl .ant-input-number:hover { border-right-width: 0; border-left-width: 1px !important; } .ant-input-number:focus, .ant-input-number-focused { border-color: #177ddc; box-shadow: 0 0 0 2px rgba(23, 125, 220, 0.2); border-right-width: 1px; outline: 0; } .ant-input-rtl .ant-input-number:focus, .ant-input-rtl .ant-input-number-focused { border-right-width: 0; border-left-width: 1px !important; } .ant-input-number-disabled { color: rgba(255, 255, 255, 0.3); background-color: rgba(255, 255, 255, 0.08); border-color: #434343; box-shadow: none; cursor: not-allowed; opacity: 1; } .ant-input-number-disabled:hover { border-color: #434343; border-right-width: 1px; } .ant-input-number[disabled] { color: rgba(255, 255, 255, 0.3); background-color: rgba(255, 255, 255, 0.08); border-color: #434343; box-shadow: none; cursor: not-allowed; opacity: 1; } .ant-input-number[disabled]:hover { border-color: #434343; border-right-width: 1px; } .ant-input-number-borderless, .ant-input-number-borderless:hover, .ant-input-number-borderless:focus, .ant-input-number-borderless-focused, .ant-input-number-borderless-disabled, .ant-input-number-borderless[disabled] { background-color: transparent; border: none; box-shadow: none; } textarea.ant-input-number { max-width: 100%; height: auto; min-height: 32px; line-height: 1.5715; vertical-align: bottom; transition: all 0.3s, height 0s; } .ant-input-number-lg { padding: 6.5px 11px; font-size: 16px; } .ant-input-number-sm { padding: 0px 7px; } .ant-input-number-rtl { direction: rtl; } .ant-input-number-group { box-sizing: border-box; margin: 0; padding: 0; color: rgba(255, 255, 255, 0.85); font-size: 14px; font-variant: tabular-nums; line-height: 1.5715; list-style: none; font-feature-settings: 'tnum'; position: relative; display: table; width: 100%; border-collapse: separate; border-spacing: 0; } .ant-input-number-group[class*='col-'] { float: none; padding-right: 0; padding-left: 0; } .ant-input-number-group > [class*='col-'] { padding-right: 8px; } .ant-input-number-group > [class*='col-']:last-child { padding-right: 0; } .ant-input-number-group-addon, .ant-input-number-group-wrap, .ant-input-number-group > .ant-input-number { display: table-cell; } .ant-input-number-group-addon:not(:first-child):not(:last-child), .ant-input-number-group-wrap:not(:first-child):not(:last-child), .ant-input-number-group > .ant-input-number:not(:first-child):not(:last-child) { border-radius: 0; } .ant-input-number-group-addon, .ant-input-number-group-wrap { width: 1px; white-space: nowrap; vertical-align: middle; } .ant-input-number-group-wrap > * { display: block !important; } .ant-input-number-group .ant-input-number { float: left; width: 100%; margin-bottom: 0; text-align: inherit; } .ant-input-number-group .ant-input-number:focus { z-index: 1; border-right-width: 1px; } .ant-input-number-group .ant-input-number:hover { z-index: 1; border-right-width: 1px; } .ant-input-search-with-button .ant-input-number-group .ant-input-number:hover { z-index: 0; } .ant-input-number-group-addon { position: relative; padding: 0 11px; color: rgba(255, 255, 255, 0.85); font-weight: normal; font-size: 14px; text-align: center; background-color: rgba(255, 255, 255, 0.04); border: 1px solid #434343; border-radius: 2px; transition: all 0.3s; } .ant-input-number-group-addon .ant-select { margin: -5px -11px; } .ant-input-number-group-addon .ant-select.ant-select-single:not(.ant-select-customize-input) .ant-select-selector { background-color: inherit; border: 1px solid transparent; box-shadow: none; } .ant-input-number-group-addon .ant-select-open .ant-select-selector, .ant-input-number-group-addon .ant-select-focused .ant-select-selector { color: #177ddc; } .ant-input-number-group-addon .ant-cascader-picker { margin: -9px -12px; background-color: transparent; } .ant-input-number-group-addon .ant-cascader-picker .ant-cascader-input { text-align: left; border: 0; box-shadow: none; } .ant-input-number-group > .ant-input-number:first-child, .ant-input-number-group-addon:first-child { border-top-right-radius: 0; border-bottom-right-radius: 0; } .ant-input-number-group > .ant-input-number:first-child .ant-select .ant-select-selector, .ant-input-number-group-addon:first-child .ant-select .ant-select-selector { border-top-right-radius: 0; border-bottom-right-radius: 0; } .ant-input-number-group > .ant-input-number-affix-wrapper:not(:first-child) .ant-input-number { border-top-left-radius: 0; border-bottom-left-radius: 0; } .ant-input-number-group > .ant-input-number-affix-wrapper:not(:last-child) .ant-input-number { border-top-right-radius: 0; border-bottom-right-radius: 0; } .ant-input-number-group-addon:first-child { border-right: 0; } .ant-input-number-group-addon:last-child { border-left: 0; } .ant-input-number-group > .ant-input-number:last-child, .ant-input-number-group-addon:last-child { border-top-left-radius: 0; border-bottom-left-radius: 0; } .ant-input-number-group > .ant-input-number:last-child .ant-select .ant-select-selector, .ant-input-number-group-addon:last-child .ant-select .ant-select-selector { border-top-left-radius: 0; border-bottom-left-radius: 0; } .ant-input-number-group-lg .ant-input-number, .ant-input-number-group-lg > .ant-input-number-group-addon { padding: 6.5px 11px; font-size: 16px; } .ant-input-number-group-sm .ant-input-number, .ant-input-number-group-sm > .ant-input-number-group-addon { padding: 0px 7px; } .ant-input-number-group-lg .ant-select-single .ant-select-selector { height: 40px; } .ant-input-number-group-sm .ant-select-single .ant-select-selector { height: 24px; } .ant-input-number-group .ant-input-number-affix-wrapper:not(:last-child) { border-top-right-radius: 0; border-bottom-right-radius: 0; } .ant-input-search .ant-input-number-group .ant-input-number-affix-wrapper:not(:last-child) { border-top-left-radius: 2px; border-bottom-left-radius: 2px; } .ant-input-number-group .ant-input-number-affix-wrapper:not(:first-child), .ant-input-search .ant-input-number-group .ant-input-number-affix-wrapper:not(:first-child) { border-top-left-radius: 0; border-bottom-left-radius: 0; } .ant-input-number-group.ant-input-number-group-compact { display: block; } .ant-input-number-group.ant-input-number-group-compact::before { display: table; content: ''; } .ant-input-number-group.ant-input-number-group-compact::after { display: table; clear: both; content: ''; } .ant-input-number-group.ant-input-number-group-compact::before { display: table; content: ''; } .ant-input-number-group.ant-input-number-group-compact::after { display: table; clear: both; content: ''; } .ant-input-number-group.ant-input-number-group-compact-addon:not(:first-child):not(:last-child), .ant-input-number-group.ant-input-number-group-compact-wrap:not(:first-child):not(:last-child), .ant-input-number-group.ant-input-number-group-compact > .ant-input-number:not(:first-child):not(:last-child) { border-right-width: 1px; } .ant-input-number-group.ant-input-number-group-compact-addon:not(:first-child):not(:last-child):hover, .ant-input-number-group.ant-input-number-group-compact-wrap:not(:first-child):not(:last-child):hover, .ant-input-number-group.ant-input-number-group-compact > .ant-input-number:not(:first-child):not(:last-child):hover { z-index: 1; } .ant-input-number-group.ant-input-number-group-compact-addon:not(:first-child):not(:last-child):focus, .ant-input-number-group.ant-input-number-group-compact-wrap:not(:first-child):not(:last-child):focus, .ant-input-number-group.ant-input-number-group-compact > .ant-input-number:not(:first-child):not(:last-child):focus { z-index: 1; } .ant-input-number-group.ant-input-number-group-compact > * { display: inline-block; float: none; vertical-align: top; border-radius: 0; } .ant-input-number-group.ant-input-number-group-compact > .ant-input-number-affix-wrapper, .ant-input-number-group.ant-input-number-group-compact > .ant-input-number-number-affix-wrapper, .ant-input-number-group.ant-input-number-group-compact > .ant-picker-range { display: inline-flex; } .ant-input-number-group.ant-input-number-group-compact > *:not(:last-child) { margin-right: -1px; border-right-width: 1px; } .ant-input-number-group.ant-input-number-group-compact .ant-input-number { float: none; } .ant-input-number-group.ant-input-number-group-compact > .ant-select > .ant-select-selector, .ant-input-number-group.ant-input-number-group-compact > .ant-select-auto-complete .ant-input, .ant-input-number-group.ant-input-number-group-compact > .ant-cascader-picker .ant-input, .ant-input-number-group.ant-input-number-group-compact > .ant-input-group-wrapper .ant-input { border-right-width: 1px; border-radius: 0; } .ant-input-number-group.ant-input-number-group-compact > .ant-select > .ant-select-selector:hover, .ant-input-number-group.ant-input-number-group-compact > .ant-select-auto-complete .ant-input:hover, .ant-input-number-group.ant-input-number-group-compact > .ant-cascader-picker .ant-input:hover, .ant-input-number-group.ant-input-number-group-compact > .ant-input-group-wrapper .ant-input:hover { z-index: 1; } .ant-input-number-group.ant-input-number-group-compact > .ant-select > .ant-select-selector:focus, .ant-input-number-group.ant-input-number-group-compact > .ant-select-auto-complete .ant-input:focus, .ant-input-number-group.ant-input-number-group-compact > .ant-cascader-picker .ant-input:focus, .ant-input-number-group.ant-input-number-group-compact > .ant-input-group-wrapper .ant-input:focus { z-index: 1; } .ant-input-number-group.ant-input-number-group-compact > .ant-select-focused { z-index: 1; } .ant-input-number-group.ant-input-number-group-compact > .ant-select > .ant-select-arrow { z-index: 1; } .ant-input-number-group.ant-input-number-group-compact > *:first-child, .ant-input-number-group.ant-input-number-group-compact > .ant-select:first-child > .ant-select-selector, .ant-input-number-group.ant-input-number-group-compact > .ant-select-auto-complete:first-child .ant-input, .ant-input-number-group.ant-input-number-group-compact > .ant-cascader-picker:first-child .ant-input { border-top-left-radius: 2px; border-bottom-left-radius: 2px; } .ant-input-number-group.ant-input-number-group-compact > *:last-child, .ant-input-number-group.ant-input-number-group-compact > .ant-select:last-child > .ant-select-selector, .ant-input-number-group.ant-input-number-group-compact > .ant-cascader-picker:last-child .ant-input, .ant-input-number-group.ant-input-number-group-compact > .ant-cascader-picker-focused:last-child .ant-input { border-right-width: 1px; border-top-right-radius: 2px; border-bottom-right-radius: 2px; } .ant-input-number-group.ant-input-number-group-compact > .ant-select-auto-complete .ant-input { vertical-align: top; } .ant-input-number-group.ant-input-number-group-compact .ant-input-group-wrapper + .ant-input-group-wrapper { margin-left: -1px; } .ant-input-number-group.ant-input-number-group-compact .ant-input-group-wrapper + .ant-input-group-wrapper .ant-input-affix-wrapper { border-radius: 0; } .ant-input-number-group.ant-input-number-group-compact .ant-input-group-wrapper:not(:last-child).ant-input-search > .ant-input-group > .ant-input-group-addon > .ant-input-search-button { border-radius: 0; } .ant-input-number-group.ant-input-number-group-compact .ant-input-group-wrapper:not(:last-child).ant-input-search > .ant-input-group > .ant-input { border-radius: 2px 0 0 2px; } .ant-input-number-group > .ant-input-number-rtl:first-child, .ant-input-number-group-rtl .ant-input-number-group-addon:first-child { border-radius: 0 2px 2px 0; } .ant-input-number-group-rtl .ant-input-number-group-addon:first-child { border-right: 1px solid #434343; border-left: 0; } .ant-input-number-group-rtl .ant-input-number-group-addon:last-child { border-right: 0; border-left: 1px solid #434343; border-radius: 2px 0 0 2px; } .ant-input-number-group-rtl.ant-input-number-group > .ant-input-number:last-child, .ant-input-number-group-rtl.ant-input-number-group-addon:last-child { border-radius: 2px 0 0 2px; } .ant-input-number-group-rtl.ant-input-number-group .ant-input-number-affix-wrapper:not(:first-child) { border-radius: 2px 0 0 2px; } .ant-input-number-group-rtl.ant-input-number-group .ant-input-number-affix-wrapper:not(:last-child) { border-radius: 0 2px 2px 0; } .ant-input-number-group-rtl.ant-input-number-group.ant-input-number-group-compact > *:not(:last-child) { margin-right: 0; margin-left: -1px; border-left-width: 1px; } .ant-input-number-group-rtl.ant-input-number-group.ant-input-number-group-compact > *:first-child, .ant-input-number-group-rtl.ant-input-number-group.ant-input-number-group-compact > .ant-select:first-child > .ant-select-selector, .ant-input-number-group-rtl.ant-input-number-group.ant-input-number-group-compact > .ant-select-auto-complete:first-child .ant-input, .ant-input-number-group-rtl.ant-input-number-group.ant-input-number-group-compact > .ant-cascader-picker:first-child .ant-input { border-radius: 0 2px 2px 0; } .ant-input-number-group-rtl.ant-input-number-group.ant-input-number-group-compact > *:last-child, .ant-input-number-group-rtl.ant-input-number-group.ant-input-number-group-compact > .ant-select:last-child > .ant-select-selector, .ant-input-number-group-rtl.ant-input-number-group.ant-input-number-group-compact > .ant-select-auto-complete:last-child .ant-input, .ant-input-number-group-rtl.ant-input-number-group.ant-input-number-group-compact > .ant-cascader-picker:last-child .ant-input, .ant-input-number-group-rtl.ant-input-number-group.ant-input-number-group-compact > .ant-cascader-picker-focused:last-child .ant-input { border-left-width: 1px; border-radius: 2px 0 0 2px; } .ant-input-number-group.ant-input-number-group-compact .ant-input-group-wrapper-rtl + .ant-input-group-wrapper-rtl { margin-right: -1px; margin-left: 0; } .ant-input-number-group.ant-input-number-group-compact .ant-input-group-wrapper-rtl:not(:last-child).ant-input-search > .ant-input-group > .ant-input { border-radius: 0 2px 2px 0; } .ant-input-number-group > .ant-input-number-rtl:first-child { border-radius: 0 2px 2px 0; } .ant-input-number-group > .ant-input-number-rtl:last-child { border-radius: 2px 0 0 2px; } .ant-input-number-group-rtl .ant-input-number-group-addon:first-child { border-right: 1px solid #434343; border-left: 0; border-radius: 0 2px 2px 0; } .ant-input-number-group-rtl .ant-input-number-group-addon:last-child { border-right: 0; border-left: 1px solid #434343; border-radius: 2px 0 0 2px; } .ant-input-number-group-wrapper { display: inline-block; text-align: start; vertical-align: top; } .ant-input-number-handler { position: relative; display: block; width: 100%; height: 50%; overflow: hidden; color: rgba(255, 255, 255, 0.45); font-weight: bold; line-height: 0; text-align: center; border-left: 1px solid #434343; transition: all 0.1s linear; } .ant-input-number-handler:active { background: rgba(255, 255, 255, 0.08); } .ant-input-number-handler:hover .ant-input-number-handler-up-inner, .ant-input-number-handler:hover .ant-input-number-handler-down-inner { color: #165996; } .ant-input-number-handler-up-inner, .ant-input-number-handler-down-inner { display: inline-flex; align-items: center; color: inherit; font-style: normal; line-height: 0; text-align: center; text-transform: none; vertical-align: -0.125em; text-rendering: optimizelegibility; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; position: absolute; right: 4px; width: 12px; height: 12px; color: rgba(255, 255, 255, 0.45); line-height: 12px; transition: all 0.1s linear; user-select: none; } .ant-input-number-handler-up-inner > *, .ant-input-number-handler-down-inner > * { line-height: 1; } .ant-input-number-handler-up-inner svg, .ant-input-number-handler-down-inner svg { display: inline-block; } .ant-input-number-handler-up-inner::before, .ant-input-number-handler-down-inner::before { display: none; } .ant-input-number-handler-up-inner .ant-input-number-handler-up-inner-icon, .ant-input-number-handler-up-inner .ant-input-number-handler-down-inner-icon, .ant-input-number-handler-down-inner .ant-input-number-handler-up-inner-icon, .ant-input-number-handler-down-inner .ant-input-number-handler-down-inner-icon { display: block; } .ant-input-number:hover { border-color: #165996; border-right-width: 1px; } .ant-input-number:hover + .ant-form-item-children-icon { opacity: 0; transition: opacity 0.24s linear 0.24s; } .ant-input-number-focused { border-color: #177ddc; box-shadow: 0 0 0 2px rgba(23, 125, 220, 0.2); border-right-width: 1px; outline: 0; } .ant-input-rtl .ant-input-number-focused { border-right-width: 0; border-left-width: 1px !important; } .ant-input-number-disabled { color: rgba(255, 255, 255, 0.3); background-color: rgba(255, 255, 255, 0.08); border-color: #434343; box-shadow: none; cursor: not-allowed; opacity: 1; } .ant-input-number-disabled:hover { border-color: #434343; border-right-width: 1px; } .ant-input-number-disabled .ant-input-number-input { cursor: not-allowed; } .ant-input-number-disabled .ant-input-number-handler-wrap { display: none; } .ant-input-number-readonly .ant-input-number-handler-wrap { display: none; } .ant-input-number-input { width: 100%; height: 30px; padding: 0 11px; text-align: left; background-color: transparent; border: 0; border-radius: 2px; outline: 0; transition: all 0.3s linear; appearance: textfield !important; } .ant-input-number-input::placeholder { color: rgba(255, 255, 255, 0.3); user-select: none; } .ant-input-number-input:placeholder-shown { text-overflow: ellipsis; } .ant-input-number-input[type='number']::-webkit-inner-spin-button, .ant-input-number-input[type='number']::-webkit-outer-spin-button { margin: 0; /* stylelint-disable-next-line property-no-vendor-prefix */ -webkit-appearance: none; appearance: none; } .ant-input-number-lg { padding: 0; font-size: 16px; } .ant-input-number-lg input { height: 38px; } .ant-input-number-sm { padding: 0; } .ant-input-number-sm input { height: 22px; padding: 0 7px; } .ant-input-number-handler-wrap { position: absolute; top: 0; right: 0; width: 22px; height: 100%; background: #141414; border-radius: 0 2px 2px 0; opacity: 0; transition: opacity 0.24s linear 0.1s; } .ant-input-number-handler-wrap .ant-input-number-handler .ant-input-number-handler-up-inner, .ant-input-number-handler-wrap .ant-input-number-handler .ant-input-number-handler-down-inner { display: flex; align-items: center; justify-content: center; min-width: auto; margin-right: 0; font-size: 7px; } .ant-input-number-borderless .ant-input-number-handler-wrap { border-left-width: 0; } .ant-input-number-handler-wrap:hover .ant-input-number-handler { height: 40%; } .ant-input-number:hover .ant-input-number-handler-wrap, .ant-input-number-focused .ant-input-number-handler-wrap { opacity: 1; } .ant-input-number-handler-up { border-top-right-radius: 2px; cursor: pointer; } .ant-input-number-handler-up-inner { top: 50%; margin-top: -5px; text-align: center; } .ant-input-number-handler-up:hover { height: 60% !important; } .ant-input-number-handler-down { top: 0; border-top: 1px solid #434343; border-bottom-right-radius: 2px; cursor: pointer; } .ant-input-number-handler-down-inner { top: 50%; text-align: center; transform: translateY(-50%); } .ant-input-number-handler-down:hover { height: 60% !important; } .ant-input-number-borderless .ant-input-number-handler-down { border-top-width: 0; } .ant-input-number:hover:not(.ant-input-number-borderless) .ant-input-number-handler-down, .ant-input-number-focused:not(.ant-input-number-borderless) .ant-input-number-handler-down { border-top: 1px solid #434343; } .ant-input-number-handler-up-disabled, .ant-input-number-handler-down-disabled { cursor: not-allowed; } .ant-input-number-handler-up-disabled:hover .ant-input-number-handler-up-inner, .ant-input-number-handler-down-disabled:hover .ant-input-number-handler-down-inner { color: rgba(255, 255, 255, 0.3); } .ant-input-number-borderless { box-shadow: none; } .ant-input-number-out-of-range input { color: #a61d24; } .ant-input-number-compact-item:not(.ant-input-number-compact-last-item):not(.ant-input-number-compact-item-rtl) { margin-right: -1px; } .ant-input-number-compact-item:not(.ant-input-number-compact-last-item).ant-input-number-compact-item-rtl { margin-left: -1px; } .ant-input-number-compact-item:hover, .ant-input-number-compact-item:focus, .ant-input-number-compact-item:active { z-index: 2; } .ant-input-number-compact-item.ant-input-number-focused { z-index: 2; } .ant-input-number-compact-item[disabled] { z-index: 0; } .ant-input-number-compact-item:not(.ant-input-number-compact-first-item):not(.ant-input-number-compact-last-item).ant-input-number { border-radius: 0; } .ant-input-number-compact-item.ant-input-number.ant-input-number-compact-first-item:not(.ant-input-number-compact-last-item):not(.ant-input-number-compact-item-rtl) { border-top-right-radius: 0; border-bottom-right-radius: 0; } .ant-input-number-compact-item.ant-input-number.ant-input-number-compact-last-item:not(.ant-input-number-compact-first-item):not(.ant-input-number-compact-item-rtl) { border-top-left-radius: 0; border-bottom-left-radius: 0; } .ant-input-number-compact-item.ant-input-number.ant-input-number-compact-item-rtl.ant-input-number-compact-first-item:not(.ant-input-number-compact-last-item) { border-top-left-radius: 0; border-bottom-left-radius: 0; } .ant-input-number-compact-item.ant-input-number.ant-input-number-compact-item-rtl.ant-input-number-compact-last-item:not(.ant-input-number-compact-first-item) { border-top-right-radius: 0; border-bottom-right-radius: 0; } .ant-input-number-rtl { direction: rtl; } .ant-input-number-rtl .ant-input-number-handler { border-right: 1px solid #434343; border-left: 0; } .ant-input-number-rtl .ant-input-number-handler-wrap { right: auto; left: 0; } .ant-input-number-rtl.ant-input-number-borderless .ant-input-number-handler-wrap { border-right-width: 0; } .ant-input-number-rtl .ant-input-number-handler-up { border-top-right-radius: 0; } .ant-input-number-rtl .ant-input-number-handler-down { border-bottom-right-radius: 0; } .ant-input-number-rtl .ant-input-number-input { direction: ltr; text-align: right; } .ant-layout { display: flex; flex: auto; flex-direction: column; /* fix firefox can't set height smaller than content on flex item */ min-height: 0; background: var(--app-background-color); } .ant-layout, .ant-layout * { box-sizing: border-box; } .ant-layout.ant-layout-has-sider { flex-direction: row; } .ant-layout.ant-layout-has-sider > .ant-layout, .ant-layout.ant-layout-has-sider > .ant-layout-content { width: 0; } .ant-layout-header, .ant-layout-footer { flex: 0 0 auto; } .ant-layout-header { height: 64px; padding: 0 50px; color: rgba(255, 255, 255, 0.85); line-height: 64px; background: #1f1f1f; } .ant-layout-footer { padding: 24px 50px; color: rgba(255, 255, 255, 0.85); font-size: 14px; background: var(--app-background-color); } .ant-layout-content { flex: auto; /* fix firefox can't set height smaller than content on flex item */ min-height: 0; } .ant-layout-sider { position: relative; /* fix firefox can't set width smaller than content on flex item */ min-width: 0; background: #1f1f1f; transition: all 0.2s; } .ant-layout-sider-children { height: 100%; margin-top: -0.1px; padding-top: 0.1px; } .ant-layout-sider-children .ant-menu.ant-menu-inline-collapsed { width: auto; } .ant-layout-sider-has-trigger { padding-bottom: 48px; } .ant-layout-sider-right { order: 1; } .ant-layout-sider-trigger { position: fixed; bottom: 0; z-index: 1; height: 48px; color: #fff; line-height: 48px; text-align: center; background: #262626; cursor: pointer; transition: all 0.2s; } .ant-layout-sider-zero-width > * { overflow: hidden; } .ant-layout-sider-zero-width-trigger { position: absolute; top: 64px; right: -36px; z-index: 1; width: 36px; height: 42px; color: #fff; font-size: 18px; line-height: 42px; text-align: center; background: #1f1f1f; border-radius: 0 2px 2px 0; cursor: pointer; transition: background 0.3s ease; } .ant-layout-sider-zero-width-trigger::after { position: absolute; top: 0; right: 0; bottom: 0; left: 0; background: transparent; transition: all 0.3s; content: ''; } .ant-layout-sider-zero-width-trigger:hover::after { background: rgba(255, 255, 255, 0.1); } .ant-layout-sider-zero-width-trigger-right { left: -36px; border-radius: 2px 0 0 2px; } .ant-layout-sider-light { background: #fff; } .ant-layout-sider-light .ant-layout-sider-trigger { color: rgba(255, 255, 255, 0.85); background: #fff; } .ant-layout-sider-light .ant-layout-sider-zero-width-trigger { color: rgba(255, 255, 255, 0.85); background: #fff; } .ant-layout-rtl { direction: rtl; } .ant-list .ant-card { background: transparent; } .ant-list { box-sizing: border-box; margin: 0; padding: 0; color: rgba(255, 255, 255, 0.85); font-size: 14px; font-variant: tabular-nums; line-height: 1.5715; list-style: none; font-feature-settings: 'tnum'; position: relative; } .ant-list * { outline: none; } .ant-list-pagination { margin-top: 24px; text-align: right; } .ant-list-pagination .ant-pagination-options { text-align: left; } .ant-list-more { margin-top: 12px; text-align: center; } .ant-list-more button { padding-right: 32px; padding-left: 32px; } .ant-list-spin { min-height: 40px; text-align: center; } .ant-list-empty-text { padding: 16px; color: rgba(255, 255, 255, 0.3); font-size: 14px; text-align: center; } .ant-list-items { margin: 0; padding: 0; list-style: none; } .ant-list-item { display: flex; align-items: center; justify-content: space-between; padding: 12px 0; color: rgba(255, 255, 255, 0.85); } .ant-list-item-meta { display: flex; flex: 1; align-items: flex-start; max-width: 100%; } .ant-list-item-meta-avatar { margin-right: 16px; } .ant-list-item-meta-content { flex: 1 0; width: 0; color: rgba(255, 255, 255, 0.85); } .ant-list-item-meta-title { margin-bottom: 4px; color: rgba(255, 255, 255, 0.85); font-size: 14px; line-height: 1.5715; } .ant-list-item-meta-title > a { color: rgba(255, 255, 255, 0.85); transition: all 0.3s; } .ant-list-item-meta-title > a:hover { color: #177ddc; } .ant-list-item-meta-description { color: rgba(255, 255, 255, 0.45); font-size: 14px; line-height: 1.5715; } .ant-list-item-action { flex: 0 0 auto; margin-left: 48px; padding: 0; font-size: 0; list-style: none; } .ant-list-item-action > li { position: relative; display: inline-block; padding: 0 8px; color: rgba(255, 255, 255, 0.45); font-size: 14px; line-height: 1.5715; text-align: center; } .ant-list-item-action > li:first-child { padding-left: 0; } .ant-list-item-action-split { position: absolute; top: 50%; right: 0; width: 1px; height: 14px; margin-top: -7px; background-color: #303030; } .ant-list-header { background: transparent; } .ant-list-footer { background: transparent; } .ant-list-header, .ant-list-footer { padding-top: 12px; padding-bottom: 12px; } .ant-list-empty { padding: 16px 0; color: rgba(255, 255, 255, 0.45); font-size: 12px; text-align: center; } .ant-list-split .ant-list-item { border-bottom: 1px solid #303030; } .ant-list-split .ant-list-item:last-child { border-bottom: none; } .ant-list-split .ant-list-header { border-bottom: 1px solid #303030; } .ant-list-split.ant-list-empty .ant-list-footer { border-top: 1px solid #303030; } .ant-list-loading .ant-list-spin-nested-loading { min-height: 32px; } .ant-list-split.ant-list-something-after-last-item .ant-spin-container > .ant-list-items > .ant-list-item:last-child { border-bottom: 1px solid #303030; } .ant-list-lg .ant-list-item { padding: 16px 24px; } .ant-list-sm .ant-list-item { padding: 8px 16px; } .ant-list-vertical .ant-list-item { align-items: initial; } .ant-list-vertical .ant-list-item-main { display: block; flex: 1; } .ant-list-vertical .ant-list-item-extra { margin-left: 40px; } .ant-list-vertical .ant-list-item-meta { margin-bottom: 16px; } .ant-list-vertical .ant-list-item-meta-title { margin-bottom: 12px; color: rgba(255, 255, 255, 0.85); font-size: 16px; line-height: 24px; } .ant-list-vertical .ant-list-item-action { margin-top: 16px; margin-left: auto; } .ant-list-vertical .ant-list-item-action > li { padding: 0 16px; } .ant-list-vertical .ant-list-item-action > li:first-child { padding-left: 0; } .ant-list-grid .ant-col > .ant-list-item { display: block; max-width: 100%; margin-bottom: 16px; padding-top: 0; padding-bottom: 0; border-bottom: none; } .ant-list-item-no-flex { display: block; } .ant-list:not(.ant-list-vertical) .ant-list-item-no-flex .ant-list-item-action { float: right; } .ant-list-bordered { border: 1px solid #434343; border-radius: 2px; } .ant-list-bordered .ant-list-header { padding-right: 24px; padding-left: 24px; } .ant-list-bordered .ant-list-footer { padding-right: 24px; padding-left: 24px; } .ant-list-bordered .ant-list-item { padding-right: 24px; padding-left: 24px; } .ant-list-bordered .ant-list-pagination { margin: 16px 24px; } .ant-list-bordered.ant-list-sm .ant-list-item { padding: 8px 16px; } .ant-list-bordered.ant-list-sm .ant-list-header, .ant-list-bordered.ant-list-sm .ant-list-footer { padding: 8px 16px; } .ant-list-bordered.ant-list-lg .ant-list-item { padding: 16px 24px; } .ant-list-bordered.ant-list-lg .ant-list-header, .ant-list-bordered.ant-list-lg .ant-list-footer { padding: 16px 24px; } @media screen and (max-width: 768px) { .ant-list-item-action { margin-left: 24px; } .ant-list-vertical .ant-list-item-extra { margin-left: 24px; } } @media screen and (max-width: 576px) { .ant-list-item { flex-wrap: wrap; } .ant-list-item-action { margin-left: 12px; } .ant-list-vertical .ant-list-item { flex-wrap: wrap-reverse; } .ant-list-vertical .ant-list-item-main { min-width: 220px; } .ant-list-vertical .ant-list-item-extra { margin: auto auto 16px; } } .ant-list-rtl { direction: rtl; text-align: right; } .ant-list-rtl .ReactVirtualized__List .ant-list-item { direction: rtl; } .ant-list-rtl .ant-list-pagination { text-align: left; } .ant-list-rtl .ant-list-item-meta-avatar { margin-right: 0; margin-left: 16px; } .ant-list-rtl .ant-list-item-action { margin-right: 48px; margin-left: 0; } .ant-list.ant-list-rtl .ant-list-item-action > li:first-child { padding-right: 0; padding-left: 16px; } .ant-list-rtl .ant-list-item-action-split { right: auto; left: 0; } .ant-list-rtl.ant-list-vertical .ant-list-item-extra { margin-right: 40px; margin-left: 0; } .ant-list-rtl.ant-list-vertical .ant-list-item-action { margin-right: auto; } .ant-list-rtl .ant-list-vertical .ant-list-item-action > li:first-child { padding-right: 0; padding-left: 16px; } .ant-list-rtl .ant-list:not(.ant-list-vertical) .ant-list-item-no-flex .ant-list-item-action { float: left; } @media screen and (max-width: 768px) { .ant-list-rtl .ant-list-item-action { margin-right: 24px; margin-left: 0; } .ant-list-rtl .ant-list-vertical .ant-list-item-extra { margin-right: 24px; margin-left: 0; } } @media screen and (max-width: 576px) { .ant-list-rtl .ant-list-item-action { margin-right: 22px; margin-left: 0; } .ant-list-rtl.ant-list-vertical .ant-list-item-extra { margin: auto auto 16px; } } .ant-mentions-status-error:not(.ant-mentions-disabled):not(.ant-mentions-borderless).ant-mentions, .ant-mentions-status-error:not(.ant-mentions-disabled):not(.ant-mentions-borderless).ant-mentions:hover { background: transparent; border-color: #a61d24; } .ant-mentions-status-error:not(.ant-mentions-disabled):not(.ant-mentions-borderless).ant-mentions:focus, .ant-mentions-status-error:not(.ant-mentions-disabled):not(.ant-mentions-borderless).ant-mentions-focused { border-color: #a61d24; box-shadow: 0 0 0 2px rgba(166, 29, 36, 0.2); border-right-width: 1px; outline: 0; } .ant-mentions-status-error .ant-input-prefix { color: #a61d24; } .ant-mentions-status-warning:not(.ant-mentions-disabled):not(.ant-mentions-borderless).ant-mentions, .ant-mentions-status-warning:not(.ant-mentions-disabled):not(.ant-mentions-borderless).ant-mentions:hover { background: transparent; border-color: #d89614; } .ant-mentions-status-warning:not(.ant-mentions-disabled):not(.ant-mentions-borderless).ant-mentions:focus, .ant-mentions-status-warning:not(.ant-mentions-disabled):not(.ant-mentions-borderless).ant-mentions-focused { border-color: #d89614; box-shadow: 0 0 0 2px rgba(216, 150, 20, 0.2); border-right-width: 1px; outline: 0; } .ant-mentions-status-warning .ant-input-prefix { color: #d89614; } .ant-mentions { box-sizing: border-box; margin: 0; font-variant: tabular-nums; list-style: none; font-feature-settings: 'tnum'; width: 100%; min-width: 0; padding: 4px 11px; color: rgba(255, 255, 255, 0.85); font-size: 14px; background-color: transparent; background-image: none; border: 1px solid #434343; border-radius: 2px; transition: all 0.3s; position: relative; display: inline-block; height: auto; padding: 0; overflow: hidden; line-height: 1.5715; white-space: pre-wrap; vertical-align: bottom; } .ant-mentions::placeholder { color: rgba(255, 255, 255, 0.3); user-select: none; } .ant-mentions:placeholder-shown { text-overflow: ellipsis; } .ant-mentions:hover { border-color: #165996; border-right-width: 1px; } .ant-input-rtl .ant-mentions:hover { border-right-width: 0; border-left-width: 1px !important; } .ant-mentions:focus, .ant-mentions-focused { border-color: #177ddc; box-shadow: 0 0 0 2px rgba(23, 125, 220, 0.2); border-right-width: 1px; outline: 0; } .ant-input-rtl .ant-mentions:focus, .ant-input-rtl .ant-mentions-focused { border-right-width: 0; border-left-width: 1px !important; } .ant-mentions-disabled { color: rgba(255, 255, 255, 0.3); background-color: rgba(255, 255, 255, 0.08); border-color: #434343; box-shadow: none; cursor: not-allowed; opacity: 1; } .ant-mentions-disabled:hover { border-color: #434343; border-right-width: 1px; } .ant-mentions[disabled] { color: rgba(255, 255, 255, 0.3); background-color: rgba(255, 255, 255, 0.08); border-color: #434343; box-shadow: none; cursor: not-allowed; opacity: 1; } .ant-mentions[disabled]:hover { border-color: #434343; border-right-width: 1px; } .ant-mentions-borderless, .ant-mentions-borderless:hover, .ant-mentions-borderless:focus, .ant-mentions-borderless-focused, .ant-mentions-borderless-disabled, .ant-mentions-borderless[disabled] { background-color: transparent; border: none; box-shadow: none; } textarea.ant-mentions { max-width: 100%; height: auto; min-height: 32px; line-height: 1.5715; vertical-align: bottom; transition: all 0.3s, height 0s; } .ant-mentions-lg { padding: 6.5px 11px; font-size: 16px; } .ant-mentions-sm { padding: 0px 7px; } .ant-mentions-rtl { direction: rtl; } .ant-mentions-disabled > textarea { color: rgba(255, 255, 255, 0.3); background-color: rgba(255, 255, 255, 0.08); border-color: #434343; box-shadow: none; cursor: not-allowed; opacity: 1; } .ant-mentions-disabled > textarea:hover { border-color: #434343; border-right-width: 1px; } .ant-mentions-focused { border-color: #177ddc; box-shadow: 0 0 0 2px rgba(23, 125, 220, 0.2); border-right-width: 1px; outline: 0; } .ant-input-rtl .ant-mentions-focused { border-right-width: 0; border-left-width: 1px !important; } .ant-mentions > textarea, .ant-mentions-measure { min-height: 30px; margin: 0; padding: 4px 11px; overflow: inherit; overflow-x: hidden; overflow-y: auto; /* stylelint-disable declaration-block-no-redundant-longhand-properties */ font-weight: inherit; font-size: inherit; font-family: inherit; font-style: inherit; font-variant: inherit; font-size-adjust: inherit; font-stretch: inherit; line-height: inherit; /* stylelint-enable declaration-block-no-redundant-longhand-properties */ direction: inherit; letter-spacing: inherit; white-space: inherit; text-align: inherit; vertical-align: top; word-wrap: break-word; word-break: inherit; tab-size: inherit; } .ant-mentions > textarea { width: 100%; border: none; outline: none; resize: none; background-color: transparent; } .ant-mentions > textarea::placeholder { color: rgba(255, 255, 255, 0.3); user-select: none; } .ant-mentions > textarea:placeholder-shown { text-overflow: ellipsis; } .ant-mentions-measure { position: absolute; top: 0; right: 0; bottom: 0; left: 0; z-index: -1; color: transparent; pointer-events: none; } .ant-mentions-measure > span { display: inline-block; min-height: 1em; } .ant-mentions-dropdown { margin: 0; padding: 0; color: rgba(255, 255, 255, 0.85); font-variant: tabular-nums; line-height: 1.5715; list-style: none; font-feature-settings: 'tnum'; position: absolute; top: -9999px; left: -9999px; z-index: 1050; box-sizing: border-box; font-size: 14px; font-variant: initial; background-color: #1f1f1f; border-radius: 2px; outline: none; box-shadow: 0 3px 6px -4px rgba(0, 0, 0, 0.48), 0 6px 16px 0 rgba(0, 0, 0, 0.32), 0 9px 28px 8px rgba(0, 0, 0, 0.2); } .ant-mentions-dropdown-hidden { display: none; } .ant-mentions-dropdown-menu { max-height: 250px; margin-bottom: 0; padding-left: 0; overflow: auto; list-style: none; outline: none; } .ant-mentions-dropdown-menu-item { position: relative; display: block; min-width: 100px; padding: 5px 12px; overflow: hidden; color: rgba(255, 255, 255, 0.85); font-weight: normal; line-height: 1.5715; white-space: nowrap; text-overflow: ellipsis; cursor: pointer; transition: background 0.3s ease; } .ant-mentions-dropdown-menu-item:hover { background-color: rgba(255, 255, 255, 0.08); } .ant-mentions-dropdown-menu-item:first-child { border-radius: 2px 2px 0 0; } .ant-mentions-dropdown-menu-item:last-child { border-radius: 0 0 2px 2px; } .ant-mentions-dropdown-menu-item-disabled { color: rgba(255, 255, 255, 0.3); cursor: not-allowed; } .ant-mentions-dropdown-menu-item-disabled:hover { color: rgba(255, 255, 255, 0.3); background-color: #1f1f1f; cursor: not-allowed; } .ant-mentions-dropdown-menu-item-selected { color: rgba(255, 255, 255, 0.85); font-weight: 600; background-color: rgba(255, 255, 255, 0.04); } .ant-mentions-dropdown-menu-item-active { background-color: rgba(255, 255, 255, 0.08); } .ant-mentions-suffix { position: absolute; top: 0; right: 11px; bottom: 0; z-index: 1; display: inline-flex; align-items: center; margin: auto; } .ant-mentions-rtl { direction: rtl; } .ant-menu-item-danger.ant-menu-item { color: #a61d24; } .ant-menu-item-danger.ant-menu-item:hover, .ant-menu-item-danger.ant-menu-item-active { color: #a61d24; } .ant-menu-item-danger.ant-menu-item:active { background: #2a1215; } .ant-menu-item-danger.ant-menu-item-selected { color: #a61d24; } .ant-menu-item-danger.ant-menu-item-selected > a, .ant-menu-item-danger.ant-menu-item-selected > a:hover { color: #a61d24; } .ant-menu:not(.ant-menu-horizontal) .ant-menu-item-danger.ant-menu-item-selected { background-color: #2a1215; } .ant-menu-inline .ant-menu-item-danger.ant-menu-item::after { border-right-color: #a61d24; } .ant-menu-dark .ant-menu-item-danger.ant-menu-item, .ant-menu-dark .ant-menu-item-danger.ant-menu-item:hover, .ant-menu-dark .ant-menu-item-danger.ant-menu-item > a { color: #a61d24; } .ant-menu-dark.ant-menu-dark:not(.ant-menu-horizontal) .ant-menu-item-danger.ant-menu-item-selected { color: #fff; background-color: #a61d24; } .ant-menu { box-sizing: border-box; margin: 0; padding: 0; font-variant: tabular-nums; line-height: 1.5715; font-feature-settings: 'tnum'; margin-bottom: 0; padding-left: 0; color: rgba(255, 255, 255, 0.85); font-size: 14px; line-height: 0; text-align: left; list-style: none; background: #141414; outline: none; box-shadow: 0 3px 6px -4px rgba(0, 0, 0, 0.48), 0 6px 16px 0 rgba(0, 0, 0, 0.32), 0 9px 28px 8px rgba(0, 0, 0, 0.2); transition: background 0.3s, width 0.3s cubic-bezier(0.2, 0, 0, 1) 0s; } .ant-menu::before { display: table; content: ''; } .ant-menu::after { display: table; clear: both; content: ''; } .ant-menu::before { display: table; content: ''; } .ant-menu::after { display: table; clear: both; content: ''; } .ant-menu.ant-menu-root:focus-visible { box-shadow: 0 0 0 2px #11263c; } .ant-menu ul, .ant-menu ol { margin: 0; padding: 0; list-style: none; } .ant-menu-overflow { display: flex; } .ant-menu-overflow-item { flex: none; } .ant-menu-hidden, .ant-menu-submenu-hidden { display: none; } .ant-menu-item-group-title { height: 1.5715; padding: 8px 16px; color: rgba(255, 255, 255, 0.45); font-size: 14px; line-height: 1.5715; transition: all 0.3s; } .ant-menu-horizontal .ant-menu-submenu { transition: border-color 0.3s cubic-bezier(0.645, 0.045, 0.355, 1), background 0.3s cubic-bezier(0.645, 0.045, 0.355, 1); } .ant-menu-submenu, .ant-menu-submenu-inline { transition: border-color 0.3s cubic-bezier(0.645, 0.045, 0.355, 1), background 0.3s cubic-bezier(0.645, 0.045, 0.355, 1), padding 0.15s cubic-bezier(0.645, 0.045, 0.355, 1); } .ant-menu-submenu-selected { color: #177ddc; } .ant-menu-item:active, .ant-menu-submenu-title:active { background: #111b26; } .ant-menu-submenu .ant-menu-sub { cursor: initial; transition: background 0.3s cubic-bezier(0.645, 0.045, 0.355, 1), padding 0.3s cubic-bezier(0.645, 0.045, 0.355, 1); } .ant-menu-title-content { transition: color 0.3s; } .ant-menu-item a { color: rgba(255, 255, 255, 0.85); } .ant-menu-item a:hover { color: #177ddc; } .ant-menu-item a::before { position: absolute; top: 0; right: 0; bottom: 0; left: 0; background-color: transparent; content: ''; } .ant-menu-item > .ant-badge a { color: rgba(255, 255, 255, 0.85); } .ant-menu-item > .ant-badge a:hover { color: #177ddc; } .ant-menu-item-divider { overflow: hidden; line-height: 0; border-color: #303030; border-style: solid; border-width: 1px 0 0; } .ant-menu-item-divider-dashed { border-style: dashed; } .ant-menu-horizontal .ant-menu-item, .ant-menu-horizontal .ant-menu-submenu { margin-top: -1px; } .ant-menu-horizontal > .ant-menu-item:hover, .ant-menu-horizontal > .ant-menu-item-active, .ant-menu-horizontal > .ant-menu-submenu .ant-menu-submenu-title:hover { background-color: transparent; } .ant-menu-item-selected { color: #177ddc; } .ant-menu-item-selected a, .ant-menu-item-selected a:hover { color: #177ddc; } .ant-menu:not(.ant-menu-horizontal) .ant-menu-item-selected { background-color: #111b26; } .ant-menu-inline, .ant-menu-vertical, .ant-menu-vertical-left { border-right: 1px solid #303030; } .ant-menu-vertical-right { border-left: 1px solid #303030; } .ant-menu-vertical.ant-menu-sub, .ant-menu-vertical-left.ant-menu-sub, .ant-menu-vertical-right.ant-menu-sub { min-width: 160px; max-height: calc(100vh - 100px); padding: 0; overflow: hidden; border-right: 0; } .ant-menu-vertical.ant-menu-sub:not([class*='-active']), .ant-menu-vertical-left.ant-menu-sub:not([class*='-active']), .ant-menu-vertical-right.ant-menu-sub:not([class*='-active']) { overflow-x: hidden; overflow-y: auto; } .ant-menu-vertical.ant-menu-sub .ant-menu-item, .ant-menu-vertical-left.ant-menu-sub .ant-menu-item, .ant-menu-vertical-right.ant-menu-sub .ant-menu-item { left: 0; margin-left: 0; border-right: 0; } .ant-menu-vertical.ant-menu-sub .ant-menu-item::after, .ant-menu-vertical-left.ant-menu-sub .ant-menu-item::after, .ant-menu-vertical-right.ant-menu-sub .ant-menu-item::after { border-right: 0; } .ant-menu-vertical.ant-menu-sub > .ant-menu-item, .ant-menu-vertical-left.ant-menu-sub > .ant-menu-item, .ant-menu-vertical-right.ant-menu-sub > .ant-menu-item, .ant-menu-vertical.ant-menu-sub > .ant-menu-submenu, .ant-menu-vertical-left.ant-menu-sub > .ant-menu-submenu, .ant-menu-vertical-right.ant-menu-sub > .ant-menu-submenu { transform-origin: 0 0; } .ant-menu-horizontal.ant-menu-sub { min-width: 114px; } .ant-menu-horizontal .ant-menu-item, .ant-menu-horizontal .ant-menu-submenu-title { transition: border-color 0.3s, background 0.3s; } .ant-menu-item, .ant-menu-submenu-title { position: relative; display: block; margin: 0; padding: 0 20px; white-space: nowrap; cursor: pointer; transition: border-color 0.3s, background 0.3s, padding 0.3s cubic-bezier(0.645, 0.045, 0.355, 1); } .ant-menu-item .ant-menu-item-icon, .ant-menu-submenu-title .ant-menu-item-icon, .ant-menu-item .anticon, .ant-menu-submenu-title .anticon { min-width: 14px; font-size: 14px; transition: font-size 0.15s cubic-bezier(0.215, 0.61, 0.355, 1), margin 0.3s cubic-bezier(0.645, 0.045, 0.355, 1), color 0.3s; } .ant-menu-item .ant-menu-item-icon + span, .ant-menu-submenu-title .ant-menu-item-icon + span, .ant-menu-item .anticon + span, .ant-menu-submenu-title .anticon + span { margin-left: 10px; opacity: 1; transition: opacity 0.3s cubic-bezier(0.645, 0.045, 0.355, 1), margin 0.3s, color 0.3s; } .ant-menu-item .ant-menu-item-icon.svg, .ant-menu-submenu-title .ant-menu-item-icon.svg { vertical-align: -0.125em; } .ant-menu-item.ant-menu-item-only-child > .anticon, .ant-menu-submenu-title.ant-menu-item-only-child > .anticon, .ant-menu-item.ant-menu-item-only-child > .ant-menu-item-icon, .ant-menu-submenu-title.ant-menu-item-only-child > .ant-menu-item-icon { margin-right: 0; } .ant-menu-item:not(.ant-menu-item-disabled):focus-visible, .ant-menu-submenu-title:not(.ant-menu-item-disabled):focus-visible { box-shadow: 0 0 0 2px #11263c; } .ant-menu > .ant-menu-item-divider { margin: 1px 0; padding: 0; } .ant-menu-submenu-popup { position: absolute; z-index: 1050; background: transparent; border-radius: 2px; box-shadow: none; transform-origin: 0 0; } .ant-menu-submenu-popup::before { position: absolute; top: -7px; right: 0; bottom: 0; left: 0; z-index: -1; width: 100%; height: 100%; opacity: 0.0001; content: ' '; } .ant-menu-submenu-placement-rightTop::before { top: 0; left: -7px; } .ant-menu-submenu > .ant-menu { background-color: #141414; border-radius: 2px; } .ant-menu-submenu > .ant-menu-submenu-title::after { transition: transform 0.3s cubic-bezier(0.645, 0.045, 0.355, 1); } .ant-menu-submenu-popup > .ant-menu { background-color: #1f1f1f; } .ant-menu-submenu-expand-icon, .ant-menu-submenu-arrow { position: absolute; top: 50%; right: 16px; width: 10px; color: rgba(255, 255, 255, 0.85); transform: translateY(-50%); transition: transform 0.3s cubic-bezier(0.645, 0.045, 0.355, 1); } .ant-menu-submenu-arrow::before, .ant-menu-submenu-arrow::after { position: absolute; width: 6px; height: 1.5px; background-color: currentcolor; border-radius: 2px; transition: background 0.3s cubic-bezier(0.645, 0.045, 0.355, 1), transform 0.3s cubic-bezier(0.645, 0.045, 0.355, 1), top 0.3s cubic-bezier(0.645, 0.045, 0.355, 1), color 0.3s cubic-bezier(0.645, 0.045, 0.355, 1); content: ''; } .ant-menu-submenu-arrow::before { transform: rotate(45deg) translateY(-2.5px); } .ant-menu-submenu-arrow::after { transform: rotate(-45deg) translateY(2.5px); } .ant-menu-submenu:hover > .ant-menu-submenu-title > .ant-menu-submenu-expand-icon, .ant-menu-submenu:hover > .ant-menu-submenu-title > .ant-menu-submenu-arrow { color: #177ddc; } .ant-menu-inline-collapsed .ant-menu-submenu-arrow::before, .ant-menu-submenu-inline .ant-menu-submenu-arrow::before { transform: rotate(-45deg) translateX(2.5px); } .ant-menu-inline-collapsed .ant-menu-submenu-arrow::after, .ant-menu-submenu-inline .ant-menu-submenu-arrow::after { transform: rotate(45deg) translateX(-2.5px); } .ant-menu-submenu-horizontal .ant-menu-submenu-arrow { display: none; } .ant-menu-submenu-open.ant-menu-submenu-inline > .ant-menu-submenu-title > .ant-menu-submenu-arrow { transform: translateY(-2px); } .ant-menu-submenu-open.ant-menu-submenu-inline > .ant-menu-submenu-title > .ant-menu-submenu-arrow::after { transform: rotate(-45deg) translateX(-2.5px); } .ant-menu-submenu-open.ant-menu-submenu-inline > .ant-menu-submenu-title > .ant-menu-submenu-arrow::before { transform: rotate(45deg) translateX(2.5px); } .ant-menu-vertical .ant-menu-submenu-selected, .ant-menu-vertical-left .ant-menu-submenu-selected, .ant-menu-vertical-right .ant-menu-submenu-selected { color: #177ddc; } .ant-menu-horizontal { line-height: 46px; border: 0; border-bottom: 1px solid #303030; box-shadow: none; } .ant-menu-horizontal:not(.ant-menu-dark) > .ant-menu-item, .ant-menu-horizontal:not(.ant-menu-dark) > .ant-menu-submenu { margin-top: -1px; margin-bottom: 0; padding: 0 20px; } .ant-menu-horizontal:not(.ant-menu-dark) > .ant-menu-item:hover, .ant-menu-horizontal:not(.ant-menu-dark) > .ant-menu-submenu:hover, .ant-menu-horizontal:not(.ant-menu-dark) > .ant-menu-item-active, .ant-menu-horizontal:not(.ant-menu-dark) > .ant-menu-submenu-active, .ant-menu-horizontal:not(.ant-menu-dark) > .ant-menu-item-open, .ant-menu-horizontal:not(.ant-menu-dark) > .ant-menu-submenu-open, .ant-menu-horizontal:not(.ant-menu-dark) > .ant-menu-item-selected, .ant-menu-horizontal:not(.ant-menu-dark) > .ant-menu-submenu-selected { color: #177ddc; } .ant-menu-horizontal:not(.ant-menu-dark) > .ant-menu-item:hover::after, .ant-menu-horizontal:not(.ant-menu-dark) > .ant-menu-submenu:hover::after, .ant-menu-horizontal:not(.ant-menu-dark) > .ant-menu-item-active::after, .ant-menu-horizontal:not(.ant-menu-dark) > .ant-menu-submenu-active::after, .ant-menu-horizontal:not(.ant-menu-dark) > .ant-menu-item-open::after, .ant-menu-horizontal:not(.ant-menu-dark) > .ant-menu-submenu-open::after, .ant-menu-horizontal:not(.ant-menu-dark) > .ant-menu-item-selected::after, .ant-menu-horizontal:not(.ant-menu-dark) > .ant-menu-submenu-selected::after { border-bottom: 2px solid #177ddc; } .ant-menu-horizontal > .ant-menu-item, .ant-menu-horizontal > .ant-menu-submenu { position: relative; top: 1px; display: inline-block; vertical-align: bottom; } .ant-menu-horizontal > .ant-menu-item::after, .ant-menu-horizontal > .ant-menu-submenu::after { position: absolute; right: 20px; bottom: 0; left: 20px; border-bottom: 2px solid transparent; transition: border-color 0.3s cubic-bezier(0.645, 0.045, 0.355, 1); content: ''; } .ant-menu-horizontal > .ant-menu-submenu > .ant-menu-submenu-title { padding: 0; } .ant-menu-horizontal > .ant-menu-item a { color: rgba(255, 255, 255, 0.85); } .ant-menu-horizontal > .ant-menu-item a:hover { color: #177ddc; } .ant-menu-horizontal > .ant-menu-item a::before { bottom: -2px; } .ant-menu-horizontal > .ant-menu-item-selected a { color: #177ddc; } .ant-menu-horizontal::after { display: block; clear: both; height: 0; content: '\20'; } .ant-menu-vertical .ant-menu-item, .ant-menu-vertical-left .ant-menu-item, .ant-menu-vertical-right .ant-menu-item, .ant-menu-inline .ant-menu-item { position: relative; } .ant-menu-vertical .ant-menu-item::after, .ant-menu-vertical-left .ant-menu-item::after, .ant-menu-vertical-right .ant-menu-item::after, .ant-menu-inline .ant-menu-item::after { position: absolute; top: 0; right: 0; bottom: 0; border-right: 3px solid #177ddc; transform: scaleY(0.0001); opacity: 0; transition: transform 0.15s cubic-bezier(0.215, 0.61, 0.355, 1), opacity 0.15s cubic-bezier(0.215, 0.61, 0.355, 1); content: ''; } .ant-menu-vertical .ant-menu-item, .ant-menu-vertical-left .ant-menu-item, .ant-menu-vertical-right .ant-menu-item, .ant-menu-inline .ant-menu-item, .ant-menu-vertical .ant-menu-submenu-title, .ant-menu-vertical-left .ant-menu-submenu-title, .ant-menu-vertical-right .ant-menu-submenu-title, .ant-menu-inline .ant-menu-submenu-title { height: 40px; margin-top: 4px; margin-bottom: 4px; padding: 0 16px; overflow: hidden; line-height: 40px; text-overflow: ellipsis; } .ant-menu-vertical .ant-menu-submenu, .ant-menu-vertical-left .ant-menu-submenu, .ant-menu-vertical-right .ant-menu-submenu, .ant-menu-inline .ant-menu-submenu { padding-bottom: 0.02px; } .ant-menu-vertical .ant-menu-item:not(:last-child), .ant-menu-vertical-left .ant-menu-item:not(:last-child), .ant-menu-vertical-right .ant-menu-item:not(:last-child), .ant-menu-inline .ant-menu-item:not(:last-child) { margin-bottom: 8px; } .ant-menu-vertical > .ant-menu-item, .ant-menu-vertical-left > .ant-menu-item, .ant-menu-vertical-right > .ant-menu-item, .ant-menu-inline > .ant-menu-item, .ant-menu-vertical > .ant-menu-submenu > .ant-menu-submenu-title, .ant-menu-vertical-left > .ant-menu-submenu > .ant-menu-submenu-title, .ant-menu-vertical-right > .ant-menu-submenu > .ant-menu-submenu-title, .ant-menu-inline > .ant-menu-submenu > .ant-menu-submenu-title { height: 40px; line-height: 40px; } .ant-menu-vertical .ant-menu-item-group-list .ant-menu-submenu-title, .ant-menu-vertical .ant-menu-submenu-title { padding-right: 34px; } .ant-menu-inline { width: 100%; } .ant-menu-inline .ant-menu-selected::after, .ant-menu-inline .ant-menu-item-selected::after { transform: scaleY(1); opacity: 1; transition: transform 0.15s cubic-bezier(0.645, 0.045, 0.355, 1), opacity 0.15s cubic-bezier(0.645, 0.045, 0.355, 1); } .ant-menu-inline .ant-menu-item, .ant-menu-inline .ant-menu-submenu-title { width: calc(100% + 1px); } .ant-menu-inline .ant-menu-item-group-list .ant-menu-submenu-title, .ant-menu-inline .ant-menu-submenu-title { padding-right: 34px; } .ant-menu-inline.ant-menu-root .ant-menu-item, .ant-menu-inline.ant-menu-root .ant-menu-submenu-title { display: flex; align-items: center; transition: border-color 0.3s, background 0.3s, padding 0.1s cubic-bezier(0.215, 0.61, 0.355, 1); } .ant-menu-inline.ant-menu-root .ant-menu-item > .ant-menu-title-content, .ant-menu-inline.ant-menu-root .ant-menu-submenu-title > .ant-menu-title-content { flex: auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; } .ant-menu-inline.ant-menu-root .ant-menu-item > *, .ant-menu-inline.ant-menu-root .ant-menu-submenu-title > * { flex: none; } .ant-menu.ant-menu-inline-collapsed { width: 80px; } .ant-menu.ant-menu-inline-collapsed > .ant-menu-item, .ant-menu.ant-menu-inline-collapsed > .ant-menu-item-group > .ant-menu-item-group-list > .ant-menu-item, .ant-menu.ant-menu-inline-collapsed > .ant-menu-item-group > .ant-menu-item-group-list > .ant-menu-submenu > .ant-menu-submenu-title, .ant-menu.ant-menu-inline-collapsed > .ant-menu-submenu > .ant-menu-submenu-title { left: 0; padding: 0 calc(50% - 16px / 2); text-overflow: clip; } .ant-menu.ant-menu-inline-collapsed > .ant-menu-item .ant-menu-submenu-arrow, .ant-menu.ant-menu-inline-collapsed > .ant-menu-item-group > .ant-menu-item-group-list > .ant-menu-item .ant-menu-submenu-arrow, .ant-menu.ant-menu-inline-collapsed > .ant-menu-item-group > .ant-menu-item-group-list > .ant-menu-submenu > .ant-menu-submenu-title .ant-menu-submenu-arrow, .ant-menu.ant-menu-inline-collapsed > .ant-menu-submenu > .ant-menu-submenu-title .ant-menu-submenu-arrow { opacity: 0; } .ant-menu.ant-menu-inline-collapsed > .ant-menu-item .ant-menu-item-icon, .ant-menu.ant-menu-inline-collapsed > .ant-menu-item-group > .ant-menu-item-group-list > .ant-menu-item .ant-menu-item-icon, .ant-menu.ant-menu-inline-collapsed > .ant-menu-item-group > .ant-menu-item-group-list > .ant-menu-submenu > .ant-menu-submenu-title .ant-menu-item-icon, .ant-menu.ant-menu-inline-collapsed > .ant-menu-submenu > .ant-menu-submenu-title .ant-menu-item-icon, .ant-menu.ant-menu-inline-collapsed > .ant-menu-item .anticon, .ant-menu.ant-menu-inline-collapsed > .ant-menu-item-group > .ant-menu-item-group-list > .ant-menu-item .anticon, .ant-menu.ant-menu-inline-collapsed > .ant-menu-item-group > .ant-menu-item-group-list > .ant-menu-submenu > .ant-menu-submenu-title .anticon, .ant-menu.ant-menu-inline-collapsed > .ant-menu-submenu > .ant-menu-submenu-title .anticon { margin: 0; font-size: 16px; line-height: 40px; } .ant-menu.ant-menu-inline-collapsed > .ant-menu-item .ant-menu-item-icon + span, .ant-menu.ant-menu-inline-collapsed > .ant-menu-item-group > .ant-menu-item-group-list > .ant-menu-item .ant-menu-item-icon + span, .ant-menu.ant-menu-inline-collapsed > .ant-menu-item-group > .ant-menu-item-group-list > .ant-menu-submenu > .ant-menu-submenu-title .ant-menu-item-icon + span, .ant-menu.ant-menu-inline-collapsed > .ant-menu-submenu > .ant-menu-submenu-title .ant-menu-item-icon + span, .ant-menu.ant-menu-inline-collapsed > .ant-menu-item .anticon + span, .ant-menu.ant-menu-inline-collapsed > .ant-menu-item-group > .ant-menu-item-group-list > .ant-menu-item .anticon + span, .ant-menu.ant-menu-inline-collapsed > .ant-menu-item-group > .ant-menu-item-group-list > .ant-menu-submenu > .ant-menu-submenu-title .anticon + span, .ant-menu.ant-menu-inline-collapsed > .ant-menu-submenu > .ant-menu-submenu-title .anticon + span { display: inline-block; opacity: 0; } .ant-menu.ant-menu-inline-collapsed .ant-menu-item-icon, .ant-menu.ant-menu-inline-collapsed .anticon { display: inline-block; } .ant-menu.ant-menu-inline-collapsed-tooltip { pointer-events: none; } .ant-menu.ant-menu-inline-collapsed-tooltip .ant-menu-item-icon, .ant-menu.ant-menu-inline-collapsed-tooltip .anticon { display: none; } .ant-menu.ant-menu-inline-collapsed-tooltip a { color: rgba(255, 255, 255, 0.85); } .ant-menu.ant-menu-inline-collapsed .ant-menu-item-group-title { padding-right: 4px; padding-left: 4px; overflow: hidden; white-space: nowrap; text-overflow: ellipsis; } .ant-menu-item-group-list { margin: 0; padding: 0; } .ant-menu-item-group-list .ant-menu-item, .ant-menu-item-group-list .ant-menu-submenu-title { padding: 0 16px 0 28px; } .ant-menu-root.ant-menu-vertical, .ant-menu-root.ant-menu-vertical-left, .ant-menu-root.ant-menu-vertical-right, .ant-menu-root.ant-menu-inline { box-shadow: none; } .ant-menu-root.ant-menu-inline-collapsed .ant-menu-item > .ant-menu-inline-collapsed-noicon, .ant-menu-root.ant-menu-inline-collapsed .ant-menu-submenu .ant-menu-submenu-title > .ant-menu-inline-collapsed-noicon { font-size: 16px; text-align: center; } .ant-menu-sub.ant-menu-inline { padding: 0; background: rgba(255, 255, 255, 0.04); border: 0; border-radius: 0; box-shadow: none; } .ant-menu-sub.ant-menu-inline > .ant-menu-item, .ant-menu-sub.ant-menu-inline > .ant-menu-submenu > .ant-menu-submenu-title { height: 40px; line-height: 40px; list-style-position: inside; list-style-type: disc; } .ant-menu-sub.ant-menu-inline .ant-menu-item-group-title { padding-left: 32px; } .ant-menu-item-disabled, .ant-menu-submenu-disabled { color: rgba(255, 255, 255, 0.3) !important; background: none; cursor: not-allowed; } .ant-menu-item-disabled::after, .ant-menu-submenu-disabled::after { border-color: transparent !important; } .ant-menu-item-disabled a, .ant-menu-submenu-disabled a { color: rgba(255, 255, 255, 0.3) !important; pointer-events: none; } .ant-menu-item-disabled > .ant-menu-submenu-title, .ant-menu-submenu-disabled > .ant-menu-submenu-title { color: rgba(255, 255, 255, 0.3) !important; cursor: not-allowed; } .ant-menu-item-disabled > .ant-menu-submenu-title > .ant-menu-submenu-arrow::before, .ant-menu-submenu-disabled > .ant-menu-submenu-title > .ant-menu-submenu-arrow::before, .ant-menu-item-disabled > .ant-menu-submenu-title > .ant-menu-submenu-arrow::after, .ant-menu-submenu-disabled > .ant-menu-submenu-title > .ant-menu-submenu-arrow::after { background: rgba(255, 255, 255, 0.3) !important; } .ant-layout-header .ant-menu { line-height: inherit; } .ant-menu-inline-collapsed-tooltip a, .ant-menu-inline-collapsed-tooltip a:hover { color: #fff; } .ant-menu-light .ant-menu-item:hover, .ant-menu-light .ant-menu-item-active, .ant-menu-light .ant-menu:not(.ant-menu-inline) .ant-menu-submenu-open, .ant-menu-light .ant-menu-submenu-active, .ant-menu-light .ant-menu-submenu-title:hover { color: #177ddc; } .ant-menu.ant-menu-root:focus-visible { box-shadow: 0 0 0 2px #388ed3; } .ant-menu-dark .ant-menu-item:focus-visible, .ant-menu-dark .ant-menu-submenu-title:focus-visible { box-shadow: 0 0 0 2px #388ed3; } .ant-menu.ant-menu-dark, .ant-menu-dark .ant-menu-sub, .ant-menu.ant-menu-dark .ant-menu-sub { color: rgba(255, 255, 255, 0.65); background: #1f1f1f; } .ant-menu.ant-menu-dark .ant-menu-submenu-title .ant-menu-submenu-arrow, .ant-menu-dark .ant-menu-sub .ant-menu-submenu-title .ant-menu-submenu-arrow, .ant-menu.ant-menu-dark .ant-menu-sub .ant-menu-submenu-title .ant-menu-submenu-arrow { opacity: 0.45; transition: all 0.3s; } .ant-menu.ant-menu-dark .ant-menu-submenu-title .ant-menu-submenu-arrow::after, .ant-menu-dark .ant-menu-sub .ant-menu-submenu-title .ant-menu-submenu-arrow::after, .ant-menu.ant-menu-dark .ant-menu-sub .ant-menu-submenu-title .ant-menu-submenu-arrow::after, .ant-menu.ant-menu-dark .ant-menu-submenu-title .ant-menu-submenu-arrow::before, .ant-menu-dark .ant-menu-sub .ant-menu-submenu-title .ant-menu-submenu-arrow::before, .ant-menu.ant-menu-dark .ant-menu-sub .ant-menu-submenu-title .ant-menu-submenu-arrow::before { background: #fff; } .ant-menu-dark.ant-menu-submenu-popup { background: transparent; } .ant-menu-dark .ant-menu-inline.ant-menu-sub { background: #141414; } .ant-menu-dark.ant-menu-horizontal { border-bottom: 0; } .ant-menu-dark.ant-menu-horizontal > .ant-menu-item, .ant-menu-dark.ant-menu-horizontal > .ant-menu-submenu { top: 0; margin-top: 0; padding: 0 20px; border-color: #1f1f1f; border-bottom: 0; } .ant-menu-dark.ant-menu-horizontal > .ant-menu-item:hover { background-color: #177ddc; } .ant-menu-dark.ant-menu-horizontal > .ant-menu-item > a::before { bottom: 0; } .ant-menu-dark .ant-menu-item, .ant-menu-dark .ant-menu-item-group-title, .ant-menu-dark .ant-menu-item > a, .ant-menu-dark .ant-menu-item > span > a { color: rgba(255, 255, 255, 0.65); } .ant-menu-dark.ant-menu-inline, .ant-menu-dark.ant-menu-vertical, .ant-menu-dark.ant-menu-vertical-left, .ant-menu-dark.ant-menu-vertical-right { border-right: 0; } .ant-menu-dark.ant-menu-inline .ant-menu-item, .ant-menu-dark.ant-menu-vertical .ant-menu-item, .ant-menu-dark.ant-menu-vertical-left .ant-menu-item, .ant-menu-dark.ant-menu-vertical-right .ant-menu-item { left: 0; margin-left: 0; border-right: 0; } .ant-menu-dark.ant-menu-inline .ant-menu-item::after, .ant-menu-dark.ant-menu-vertical .ant-menu-item::after, .ant-menu-dark.ant-menu-vertical-left .ant-menu-item::after, .ant-menu-dark.ant-menu-vertical-right .ant-menu-item::after { border-right: 0; } .ant-menu-dark.ant-menu-inline .ant-menu-item, .ant-menu-dark.ant-menu-inline .ant-menu-submenu-title { width: 100%; } .ant-menu-dark .ant-menu-item:hover, .ant-menu-dark .ant-menu-item-active, .ant-menu-dark .ant-menu-submenu-active, .ant-menu-dark .ant-menu-submenu-open, .ant-menu-dark .ant-menu-submenu-selected, .ant-menu-dark .ant-menu-submenu-title:hover { color: #fff; background-color: transparent; } .ant-menu-dark .ant-menu-item:hover > a, .ant-menu-dark .ant-menu-item-active > a, .ant-menu-dark .ant-menu-submenu-active > a, .ant-menu-dark .ant-menu-submenu-open > a, .ant-menu-dark .ant-menu-submenu-selected > a, .ant-menu-dark .ant-menu-submenu-title:hover > a, .ant-menu-dark .ant-menu-item:hover > span > a, .ant-menu-dark .ant-menu-item-active > span > a, .ant-menu-dark .ant-menu-submenu-active > span > a, .ant-menu-dark .ant-menu-submenu-open > span > a, .ant-menu-dark .ant-menu-submenu-selected > span > a, .ant-menu-dark .ant-menu-submenu-title:hover > span > a { color: #fff; } .ant-menu-dark .ant-menu-item:hover > .ant-menu-submenu-title > .ant-menu-submenu-arrow, .ant-menu-dark .ant-menu-item-active > .ant-menu-submenu-title > .ant-menu-submenu-arrow, .ant-menu-dark .ant-menu-submenu-active > .ant-menu-submenu-title > .ant-menu-submenu-arrow, .ant-menu-dark .ant-menu-submenu-open > .ant-menu-submenu-title > .ant-menu-submenu-arrow, .ant-menu-dark .ant-menu-submenu-selected > .ant-menu-submenu-title > .ant-menu-submenu-arrow, .ant-menu-dark .ant-menu-submenu-title:hover > .ant-menu-submenu-title > .ant-menu-submenu-arrow { opacity: 1; } .ant-menu-dark .ant-menu-item:hover > .ant-menu-submenu-title > .ant-menu-submenu-arrow::after, .ant-menu-dark .ant-menu-item-active > .ant-menu-submenu-title > .ant-menu-submenu-arrow::after, .ant-menu-dark .ant-menu-submenu-active > .ant-menu-submenu-title > .ant-menu-submenu-arrow::after, .ant-menu-dark .ant-menu-submenu-open > .ant-menu-submenu-title > .ant-menu-submenu-arrow::after, .ant-menu-dark .ant-menu-submenu-selected > .ant-menu-submenu-title > .ant-menu-submenu-arrow::after, .ant-menu-dark .ant-menu-submenu-title:hover > .ant-menu-submenu-title > .ant-menu-submenu-arrow::after, .ant-menu-dark .ant-menu-item:hover > .ant-menu-submenu-title > .ant-menu-submenu-arrow::before, .ant-menu-dark .ant-menu-item-active > .ant-menu-submenu-title > .ant-menu-submenu-arrow::before, .ant-menu-dark .ant-menu-submenu-active > .ant-menu-submenu-title > .ant-menu-submenu-arrow::before, .ant-menu-dark .ant-menu-submenu-open > .ant-menu-submenu-title > .ant-menu-submenu-arrow::before, .ant-menu-dark .ant-menu-submenu-selected > .ant-menu-submenu-title > .ant-menu-submenu-arrow::before, .ant-menu-dark .ant-menu-submenu-title:hover > .ant-menu-submenu-title > .ant-menu-submenu-arrow::before { background: #fff; } .ant-menu-dark .ant-menu-item:hover { background-color: transparent; } .ant-menu-dark.ant-menu-dark:not(.ant-menu-horizontal) .ant-menu-item-selected { background-color: #177ddc; } .ant-menu-dark .ant-menu-item-selected { color: #fff; border-right: 0; } .ant-menu-dark .ant-menu-item-selected::after { border-right: 0; } .ant-menu-dark .ant-menu-item-selected > a, .ant-menu-dark .ant-menu-item-selected > span > a, .ant-menu-dark .ant-menu-item-selected > a:hover, .ant-menu-dark .ant-menu-item-selected > span > a:hover { color: #fff; } .ant-menu-dark .ant-menu-item-selected .ant-menu-item-icon, .ant-menu-dark .ant-menu-item-selected .anticon { color: #fff; } .ant-menu-dark .ant-menu-item-selected .ant-menu-item-icon + span, .ant-menu-dark .ant-menu-item-selected .anticon + span { color: #fff; } .ant-menu.ant-menu-dark .ant-menu-item-selected, .ant-menu-submenu-popup.ant-menu-dark .ant-menu-item-selected { background-color: #177ddc; } .ant-menu-dark .ant-menu-item-disabled, .ant-menu-dark .ant-menu-submenu-disabled, .ant-menu-dark .ant-menu-item-disabled > a, .ant-menu-dark .ant-menu-submenu-disabled > a, .ant-menu-dark .ant-menu-item-disabled > span > a, .ant-menu-dark .ant-menu-submenu-disabled > span > a { color: rgba(255, 255, 255, 0.3) !important; opacity: 0.8; } .ant-menu-dark .ant-menu-item-disabled > .ant-menu-submenu-title, .ant-menu-dark .ant-menu-submenu-disabled > .ant-menu-submenu-title { color: rgba(255, 255, 255, 0.3) !important; } .ant-menu-dark .ant-menu-item-disabled > .ant-menu-submenu-title > .ant-menu-submenu-arrow::before, .ant-menu-dark .ant-menu-submenu-disabled > .ant-menu-submenu-title > .ant-menu-submenu-arrow::before, .ant-menu-dark .ant-menu-item-disabled > .ant-menu-submenu-title > .ant-menu-submenu-arrow::after, .ant-menu-dark .ant-menu-submenu-disabled > .ant-menu-submenu-title > .ant-menu-submenu-arrow::after { background: rgba(255, 255, 255, 0.3) !important; } .ant-menu.ant-menu-rtl { direction: rtl; text-align: right; } .ant-menu-rtl .ant-menu-item-group-title { text-align: right; } .ant-menu-rtl.ant-menu-inline, .ant-menu-rtl.ant-menu-vertical { border-right: none; border-left: 1px solid #303030; } .ant-menu-rtl.ant-menu-dark.ant-menu-inline, .ant-menu-rtl.ant-menu-dark.ant-menu-vertical { border-left: none; } .ant-menu-rtl.ant-menu-vertical.ant-menu-sub > .ant-menu-item, .ant-menu-rtl.ant-menu-vertical-left.ant-menu-sub > .ant-menu-item, .ant-menu-rtl.ant-menu-vertical-right.ant-menu-sub > .ant-menu-item, .ant-menu-rtl.ant-menu-vertical.ant-menu-sub > .ant-menu-submenu, .ant-menu-rtl.ant-menu-vertical-left.ant-menu-sub > .ant-menu-submenu, .ant-menu-rtl.ant-menu-vertical-right.ant-menu-sub > .ant-menu-submenu { transform-origin: top right; } .ant-menu-rtl .ant-menu-item .ant-menu-item-icon, .ant-menu-rtl .ant-menu-submenu-title .ant-menu-item-icon, .ant-menu-rtl .ant-menu-item .anticon, .ant-menu-rtl .ant-menu-submenu-title .anticon { margin-right: auto; margin-left: 10px; } .ant-menu-rtl .ant-menu-item.ant-menu-item-only-child > .ant-menu-item-icon, .ant-menu-rtl .ant-menu-submenu-title.ant-menu-item-only-child > .ant-menu-item-icon, .ant-menu-rtl .ant-menu-item.ant-menu-item-only-child > .anticon, .ant-menu-rtl .ant-menu-submenu-title.ant-menu-item-only-child > .anticon { margin-left: 0; } .ant-menu-submenu-rtl.ant-menu-submenu-popup { transform-origin: 100% 0; } .ant-menu-rtl .ant-menu-submenu-vertical > .ant-menu-submenu-title .ant-menu-submenu-arrow, .ant-menu-rtl .ant-menu-submenu-vertical-left > .ant-menu-submenu-title .ant-menu-submenu-arrow, .ant-menu-rtl .ant-menu-submenu-vertical-right > .ant-menu-submenu-title .ant-menu-submenu-arrow, .ant-menu-rtl .ant-menu-submenu-inline > .ant-menu-submenu-title .ant-menu-submenu-arrow { right: auto; left: 16px; } .ant-menu-rtl .ant-menu-submenu-vertical > .ant-menu-submenu-title .ant-menu-submenu-arrow::before, .ant-menu-rtl .ant-menu-submenu-vertical-left > .ant-menu-submenu-title .ant-menu-submenu-arrow::before, .ant-menu-rtl .ant-menu-submenu-vertical-right > .ant-menu-submenu-title .ant-menu-submenu-arrow::before { transform: rotate(-45deg) translateY(-2px); } .ant-menu-rtl .ant-menu-submenu-vertical > .ant-menu-submenu-title .ant-menu-submenu-arrow::after, .ant-menu-rtl .ant-menu-submenu-vertical-left > .ant-menu-submenu-title .ant-menu-submenu-arrow::after, .ant-menu-rtl .ant-menu-submenu-vertical-right > .ant-menu-submenu-title .ant-menu-submenu-arrow::after { transform: rotate(45deg) translateY(2px); } .ant-menu-rtl.ant-menu-vertical .ant-menu-item::after, .ant-menu-rtl.ant-menu-vertical-left .ant-menu-item::after, .ant-menu-rtl.ant-menu-vertical-right .ant-menu-item::after, .ant-menu-rtl.ant-menu-inline .ant-menu-item::after { right: auto; left: 0; } .ant-menu-rtl.ant-menu-vertical .ant-menu-item, .ant-menu-rtl.ant-menu-vertical-left .ant-menu-item, .ant-menu-rtl.ant-menu-vertical-right .ant-menu-item, .ant-menu-rtl.ant-menu-inline .ant-menu-item, .ant-menu-rtl.ant-menu-vertical .ant-menu-submenu-title, .ant-menu-rtl.ant-menu-vertical-left .ant-menu-submenu-title, .ant-menu-rtl.ant-menu-vertical-right .ant-menu-submenu-title, .ant-menu-rtl.ant-menu-inline .ant-menu-submenu-title { text-align: right; } .ant-menu-rtl.ant-menu-inline .ant-menu-submenu-title { padding-right: 0; padding-left: 34px; } .ant-menu-rtl.ant-menu-vertical .ant-menu-submenu-title { padding-right: 16px; padding-left: 34px; } .ant-menu-rtl.ant-menu-inline-collapsed.ant-menu-vertical .ant-menu-submenu-title { padding: 0 calc(50% - 16px / 2); } .ant-menu-rtl .ant-menu-item-group-list .ant-menu-item, .ant-menu-rtl .ant-menu-item-group-list .ant-menu-submenu-title { padding: 0 28px 0 16px; } .ant-menu-sub.ant-menu-inline { border: 0; } .ant-menu-rtl.ant-menu-sub.ant-menu-inline .ant-menu-item-group-title { padding-right: 32px; padding-left: 0; } .ant-message { box-sizing: border-box; margin: 0; padding: 0; color: rgba(255, 255, 255, 0.85); font-size: 14px; font-variant: tabular-nums; line-height: 1.5715; list-style: none; font-feature-settings: 'tnum'; position: fixed; top: 8px; left: 0; z-index: 1010; width: 100%; pointer-events: none; } .ant-message-notice { padding: 8px; text-align: center; } .ant-message-notice-content { display: inline-block; padding: 10px 16px; background: #1f1f1f; border-radius: 2px; box-shadow: 0 3px 6px -4px rgba(0, 0, 0, 0.48), 0 6px 16px 0 rgba(0, 0, 0, 0.32), 0 9px 28px 8px rgba(0, 0, 0, 0.2); pointer-events: all; } .ant-message-success .anticon { color: #49aa19; } .ant-message-error .anticon { color: #a61d24; } .ant-message-warning .anticon { color: #d89614; } .ant-message-info .anticon, .ant-message-loading .anticon { color: #177ddc; } .ant-message .anticon { position: relative; top: 1px; margin-right: 8px; font-size: 16px; } .ant-message-notice.ant-move-up-leave.ant-move-up-leave-active { animation-name: MessageMoveOut; animation-duration: 0.3s; } @keyframes MessageMoveOut { 0% { max-height: 150px; padding: 8px; opacity: 1; } 100% { max-height: 0; padding: 0; opacity: 0; } } .ant-message-rtl { direction: rtl; } .ant-message-rtl span { direction: rtl; } .ant-message-rtl .anticon { margin-right: 0; margin-left: 8px; } .ant-modal { box-sizing: border-box; margin: 0; padding: 0; color: rgba(255, 255, 255, 0.85); font-size: 14px; font-variant: tabular-nums; line-height: 1.5715; list-style: none; font-feature-settings: 'tnum'; pointer-events: none; position: relative; top: 100px; width: auto; max-width: calc(100vw - 32px); margin: 0 auto; padding-bottom: 24px; } .ant-modal.ant-zoom-enter, .ant-modal.ant-zoom-appear { transform: none; opacity: 0; animation-duration: 0.3s; user-select: none; } .ant-modal-mask { position: fixed; top: 0; right: 0; bottom: 0; left: 0; z-index: 1000; height: 100%; background-color: rgba(0, 0, 0, 0.45); } .ant-modal-mask-hidden { display: none; } .ant-modal-wrap { position: fixed; top: 0; right: 0; bottom: 0; left: 0; overflow: auto; outline: 0; } .ant-modal-wrap { z-index: 1000; } .ant-modal-title { margin: 0; color: rgba(255, 255, 255, 0.85); font-weight: 500; font-size: 16px; line-height: 22px; word-wrap: break-word; } .ant-modal-content { position: relative; background-color: #1f1f1f; background-clip: padding-box; border: 0; border-radius: 2px; box-shadow: 0 3px 6px -4px rgba(0, 0, 0, 0.48), 0 6px 16px 0 rgba(0, 0, 0, 0.32), 0 9px 28px 8px rgba(0, 0, 0, 0.2); pointer-events: auto; } .ant-modal-close { position: absolute; top: 0; right: 0; z-index: 10; padding: 0; color: rgba(255, 255, 255, 0.45); font-weight: 700; line-height: 1; text-decoration: none; background: transparent; border: 0; outline: 0; cursor: pointer; transition: color 0.3s; } .ant-modal-close-x { display: block; width: 54px; height: 54px; font-size: 16px; font-style: normal; line-height: 54px; text-align: center; text-transform: none; text-rendering: auto; } .ant-modal-close:focus, .ant-modal-close:hover { color: rgba(255, 255, 255, 0.75); text-decoration: none; } .ant-modal-header { padding: 16px 24px; color: rgba(255, 255, 255, 0.85); background: #1f1f1f; border-bottom: 1px solid #303030; border-radius: 2px 2px 0 0; } .ant-modal-body { padding: 24px; font-size: 14px; line-height: 1.5715; word-wrap: break-word; } .ant-modal-footer { padding: 10px 16px; text-align: right; background: transparent; border-top: 1px solid #303030; border-radius: 0 0 2px 2px; } .ant-modal-footer .ant-btn + .ant-btn:not(.ant-dropdown-trigger) { margin-bottom: 0; margin-left: 8px; } .ant-modal-open { overflow: hidden; } .ant-modal-centered { text-align: center; } .ant-modal-centered::before { display: inline-block; width: 0; height: 100%; vertical-align: middle; content: ''; } .ant-modal-centered .ant-modal { top: 0; display: inline-block; padding-bottom: 0; text-align: left; vertical-align: middle; } @media (max-width: 767px) { .ant-modal { max-width: calc(100vw - 16px); margin: 8px auto; } .ant-modal-centered .ant-modal { flex: 1; } } .ant-modal-confirm .ant-modal-header { display: none; } .ant-modal-confirm .ant-modal-body { padding: 32px 32px 24px; } .ant-modal-confirm-body-wrapper::before { display: table; content: ''; } .ant-modal-confirm-body-wrapper::after { display: table; clear: both; content: ''; } .ant-modal-confirm-body-wrapper::before { display: table; content: ''; } .ant-modal-confirm-body-wrapper::after { display: table; clear: both; content: ''; } .ant-modal-confirm-body .ant-modal-confirm-title { display: block; overflow: hidden; color: rgba(255, 255, 255, 0.85); font-weight: 500; font-size: 16px; line-height: 1.4; } .ant-modal-confirm-body .ant-modal-confirm-content { margin-top: 8px; color: rgba(255, 255, 255, 0.85); font-size: 14px; } .ant-modal-confirm-body > .anticon { float: left; margin-right: 16px; font-size: 22px; } .ant-modal-confirm-body > .anticon + .ant-modal-confirm-title + .ant-modal-confirm-content { margin-left: 38px; } .ant-modal-confirm .ant-modal-confirm-btns { margin-top: 24px; text-align: right; } .ant-modal-confirm .ant-modal-confirm-btns .ant-btn + .ant-btn { margin-bottom: 0; margin-left: 8px; } .ant-modal-confirm-error .ant-modal-confirm-body > .anticon { color: #a61d24; } .ant-modal-confirm-warning .ant-modal-confirm-body > .anticon, .ant-modal-confirm-confirm .ant-modal-confirm-body > .anticon { color: #d89614; } .ant-modal-confirm-info .ant-modal-confirm-body > .anticon { color: #177ddc; } .ant-modal-confirm-success .ant-modal-confirm-body > .anticon { color: #49aa19; } .ant-modal-confirm .ant-zoom-leave .ant-modal-confirm-btns { pointer-events: none; } .ant-modal-wrap-rtl { direction: rtl; } .ant-modal-wrap-rtl .ant-modal-close { right: initial; left: 0; } .ant-modal-wrap-rtl .ant-modal-footer { text-align: left; } .ant-modal-wrap-rtl .ant-modal-footer .ant-btn + .ant-btn { margin-right: 8px; margin-left: 0; } .ant-modal-wrap-rtl .ant-modal-confirm-body { direction: rtl; } .ant-modal-wrap-rtl .ant-modal-confirm-body > .anticon { float: right; margin-right: 0; margin-left: 16px; } .ant-modal-wrap-rtl .ant-modal-confirm-body > .anticon + .ant-modal-confirm-title + .ant-modal-confirm-content { margin-right: 38px; margin-left: 0; } .ant-modal-wrap-rtl .ant-modal-confirm-btns { text-align: left; } .ant-modal-wrap-rtl .ant-modal-confirm-btns .ant-btn + .ant-btn { margin-right: 8px; margin-left: 0; } .ant-modal-wrap-rtl.ant-modal-centered .ant-modal { text-align: right; } .ant-modal .ant-picker-clear, .ant-modal .ant-slider-handle, .ant-modal .ant-anchor-wrapper, .ant-modal .ant-collapse-content, .ant-modal .ant-timeline-item-head, .ant-modal .ant-card { background-color: #1f1f1f; } .ant-modal .ant-transfer-list-header { background: #1f1f1f; border-bottom: 1px solid #3a3a3a; } .ant-modal .ant-transfer-list-content-item:not(.ant-transfer-list-content-item-disabled):hover { background-color: rgba(255, 255, 255, 0.08); } .ant-modal tr.ant-table-expanded-row > td, .ant-modal tr.ant-table-expanded-row:hover > td { background: #272727; } .ant-modal .ant-table.ant-table-small thead > tr > th { background-color: #1f1f1f; border-bottom: 1px solid #3a3a3a; } .ant-modal .ant-table { background-color: #1f1f1f; } .ant-modal .ant-table .ant-table-row-expand-icon { border: 1px solid #3a3a3a; } .ant-modal .ant-table tfoot > tr > th, .ant-modal .ant-table tfoot > tr > td { border-bottom: 1px solid #3a3a3a; } .ant-modal .ant-table thead > tr > th { background-color: #272727; border-bottom: 1px solid #3a3a3a; } .ant-modal .ant-table tbody > tr > td { border-bottom: 1px solid #3a3a3a; } .ant-modal .ant-table tbody > tr > td.ant-table-cell-fix-left, .ant-modal .ant-table tbody > tr > td.ant-table-cell-fix-right { background-color: #1f1f1f; } .ant-modal .ant-table tbody > tr.ant-table-row:hover > td { background: #303030; } .ant-modal .ant-table.ant-table-bordered .ant-table-title { border: 1px solid #3a3a3a; } .ant-modal .ant-table.ant-table-bordered thead > tr > th, .ant-modal .ant-table.ant-table-bordered tbody > tr > td, .ant-modal .ant-table.ant-table-bordered tfoot > tr > th, .ant-modal .ant-table.ant-table-bordered tfoot > tr > td { border-right: 1px solid #3a3a3a; } .ant-modal .ant-table.ant-table-bordered .ant-table-cell-fix-right-first::after { border-right: 1px solid #3a3a3a; } .ant-modal .ant-table.ant-table-bordered table thead > tr:not(:last-child) > th { border-bottom: 1px solid #303030; } .ant-modal .ant-table.ant-table-bordered .ant-table-container { border: 1px solid #3a3a3a; } .ant-modal .ant-table.ant-table-bordered .ant-table-expanded-row-fixed::after { border-right: 1px solid #3a3a3a; } .ant-modal .ant-table.ant-table-bordered .ant-table-footer { border: 1px solid #3a3a3a; } .ant-modal .ant-table .ant-table-filter-trigger-container-open { background-color: #525252; } .ant-modal .ant-picker-calendar-full { background-color: #1f1f1f; } .ant-modal .ant-picker-calendar-full .ant-picker-panel { background-color: #1f1f1f; } .ant-modal .ant-picker-calendar-full .ant-picker-panel .ant-picker-calendar-date { border-top: 2px solid #3a3a3a; } .ant-modal .ant-tabs.ant-tabs-card .ant-tabs-card-bar .ant-tabs-tab-active { background-color: #1f1f1f; border-bottom: 1px solid #1f1f1f; } .ant-modal .ant-badge-count { box-shadow: 0 0 0 1px #1f1f1f; } .ant-modal .ant-tree-show-line .ant-tree-switcher { background: #1f1f1f; } .ant-notification .ant-picker-clear, .ant-notification .ant-slider-handle, .ant-notification .ant-anchor-wrapper, .ant-notification .ant-collapse-content, .ant-notification .ant-timeline-item-head, .ant-notification .ant-card { background-color: #1f1f1f; } .ant-notification .ant-transfer-list-header { background: #1f1f1f; border-bottom: 1px solid #3a3a3a; } .ant-notification .ant-transfer-list-content-item:not(.ant-transfer-list-content-item-disabled):hover { background-color: rgba(255, 255, 255, 0.08); } .ant-notification tr.ant-table-expanded-row > td, .ant-notification tr.ant-table-expanded-row:hover > td { background: #272727; } .ant-notification .ant-table.ant-table-small thead > tr > th { background-color: #1f1f1f; border-bottom: 1px solid #3a3a3a; } .ant-notification .ant-table { background-color: #1f1f1f; } .ant-notification .ant-table .ant-table-row-expand-icon { border: 1px solid #3a3a3a; } .ant-notification .ant-table tfoot > tr > th, .ant-notification .ant-table tfoot > tr > td { border-bottom: 1px solid #3a3a3a; } .ant-notification .ant-table thead > tr > th { background-color: #272727; border-bottom: 1px solid #3a3a3a; } .ant-notification .ant-table tbody > tr > td { border-bottom: 1px solid #3a3a3a; } .ant-notification .ant-table tbody > tr > td.ant-table-cell-fix-left, .ant-notification .ant-table tbody > tr > td.ant-table-cell-fix-right { background-color: #1f1f1f; } .ant-notification .ant-table tbody > tr.ant-table-row:hover > td { background: #303030; } .ant-notification .ant-table.ant-table-bordered .ant-table-title { border: 1px solid #3a3a3a; } .ant-notification .ant-table.ant-table-bordered thead > tr > th, .ant-notification .ant-table.ant-table-bordered tbody > tr > td, .ant-notification .ant-table.ant-table-bordered tfoot > tr > th, .ant-notification .ant-table.ant-table-bordered tfoot > tr > td { border-right: 1px solid #3a3a3a; } .ant-notification .ant-table.ant-table-bordered .ant-table-cell-fix-right-first::after { border-right: 1px solid #3a3a3a; } .ant-notification .ant-table.ant-table-bordered table thead > tr:not(:last-child) > th { border-bottom: 1px solid #303030; } .ant-notification .ant-table.ant-table-bordered .ant-table-container { border: 1px solid #3a3a3a; } .ant-notification .ant-table.ant-table-bordered .ant-table-expanded-row-fixed::after { border-right: 1px solid #3a3a3a; } .ant-notification .ant-table.ant-table-bordered .ant-table-footer { border: 1px solid #3a3a3a; } .ant-notification .ant-table .ant-table-filter-trigger-container-open { background-color: #525252; } .ant-notification .ant-picker-calendar-full { background-color: #1f1f1f; } .ant-notification .ant-picker-calendar-full .ant-picker-panel { background-color: #1f1f1f; } .ant-notification .ant-picker-calendar-full .ant-picker-panel .ant-picker-calendar-date { border-top: 2px solid #3a3a3a; } .ant-notification .ant-tabs.ant-tabs-card .ant-tabs-card-bar .ant-tabs-tab-active { background-color: #1f1f1f; border-bottom: 1px solid #1f1f1f; } .ant-notification .ant-badge-count { box-shadow: 0 0 0 1px #1f1f1f; } .ant-notification .ant-tree-show-line .ant-tree-switcher { background: #1f1f1f; } .ant-notification { box-sizing: border-box; margin: 0; padding: 0; color: rgba(255, 255, 255, 0.85); font-size: 14px; font-variant: tabular-nums; line-height: 1.5715; list-style: none; font-feature-settings: 'tnum'; position: fixed; z-index: 1010; margin-right: 24px; } .ant-notification-close-icon { font-size: 14px; cursor: pointer; } .ant-notification-hook-holder { position: relative; } .ant-notification-notice { position: relative; width: 384px; max-width: calc(100vw - 24px * 2); margin-bottom: 16px; margin-left: auto; padding: 16px 24px; overflow: hidden; line-height: 1.5715; word-wrap: break-word; background: #1f1f1f; border-radius: 2px; box-shadow: 0 3px 6px -4px rgba(0, 0, 0, 0.48), 0 6px 16px 0 rgba(0, 0, 0, 0.32), 0 9px 28px 8px rgba(0, 0, 0, 0.2); } .ant-notification-top .ant-notification-notice, .ant-notification-bottom .ant-notification-notice { margin-right: auto; margin-left: auto; } .ant-notification-topLeft .ant-notification-notice, .ant-notification-bottomLeft .ant-notification-notice { margin-right: auto; margin-left: 0; } .ant-notification-notice-message { margin-bottom: 8px; color: rgba(255, 255, 255, 0.85); font-size: 16px; line-height: 24px; } .ant-notification-notice-message-single-line-auto-margin { display: block; width: calc(384px - 24px * 2 - 24px - 48px - 100%); max-width: 4px; background-color: transparent; pointer-events: none; } .ant-notification-notice-message-single-line-auto-margin::before { display: block; content: ''; } .ant-notification-notice-description { font-size: 14px; } .ant-notification-notice-closable .ant-notification-notice-message { padding-right: 24px; } .ant-notification-notice-with-icon .ant-notification-notice-message { margin-bottom: 4px; margin-left: 48px; font-size: 16px; } .ant-notification-notice-with-icon .ant-notification-notice-description { margin-left: 48px; font-size: 14px; } .ant-notification-notice-icon { position: absolute; margin-left: 4px; font-size: 24px; line-height: 24px; } .anticon.ant-notification-notice-icon-success { color: #49aa19; } .anticon.ant-notification-notice-icon-info { color: #177ddc; } .anticon.ant-notification-notice-icon-warning { color: #d89614; } .anticon.ant-notification-notice-icon-error { color: #a61d24; } .ant-notification-notice-close { position: absolute; top: 16px; right: 22px; color: rgba(255, 255, 255, 0.45); outline: none; } .ant-notification-notice-close:hover { color: rgba(255, 255, 255, 0.85); } .ant-notification-notice-btn { float: right; margin-top: 16px; } .ant-notification .notification-fade-effect { animation-duration: 0.24s; animation-timing-function: cubic-bezier(0.645, 0.045, 0.355, 1); animation-fill-mode: both; } .ant-notification-fade-enter, .ant-notification-fade-appear { animation-duration: 0.24s; animation-timing-function: cubic-bezier(0.645, 0.045, 0.355, 1); animation-fill-mode: both; opacity: 0; animation-play-state: paused; } .ant-notification-fade-leave { animation-duration: 0.24s; animation-timing-function: cubic-bezier(0.645, 0.045, 0.355, 1); animation-fill-mode: both; animation-duration: 0.2s; animation-play-state: paused; } .ant-notification-fade-enter.ant-notification-fade-enter-active, .ant-notification-fade-appear.ant-notification-fade-appear-active { animation-name: NotificationFadeIn; animation-play-state: running; } .ant-notification-fade-leave.ant-notification-fade-leave-active { animation-name: NotificationFadeOut; animation-play-state: running; } @keyframes NotificationFadeIn { 0% { left: 384px; opacity: 0; } 100% { left: 0; opacity: 1; } } @keyframes NotificationFadeOut { 0% { max-height: 150px; margin-bottom: 16px; opacity: 1; } 100% { max-height: 0; margin-bottom: 0; padding-top: 0; padding-bottom: 0; opacity: 0; } } .ant-notification-rtl { direction: rtl; } .ant-notification-rtl .ant-notification-notice-closable .ant-notification-notice-message { padding-right: 0; padding-left: 24px; } .ant-notification-rtl .ant-notification-notice-with-icon .ant-notification-notice-message { margin-right: 48px; margin-left: 0; } .ant-notification-rtl .ant-notification-notice-with-icon .ant-notification-notice-description { margin-right: 48px; margin-left: 0; } .ant-notification-rtl .ant-notification-notice-icon { margin-right: 4px; margin-left: 0; } .ant-notification-rtl .ant-notification-notice-close { right: auto; left: 22px; } .ant-notification-rtl .ant-notification-notice-btn { float: left; } .ant-notification-top, .ant-notification-bottom { margin-right: 0; margin-left: 0; } .ant-notification-top .ant-notification-fade-enter.ant-notification-fade-enter-active, .ant-notification-top .ant-notification-fade-appear.ant-notification-fade-appear-active { animation-name: NotificationTopFadeIn; } .ant-notification-bottom .ant-notification-fade-enter.ant-notification-fade-enter-active, .ant-notification-bottom .ant-notification-fade-appear.ant-notification-fade-appear-active { animation-name: NotificationBottomFadeIn; } .ant-notification-topLeft, .ant-notification-bottomLeft { margin-right: 0; margin-left: 24px; } .ant-notification-topLeft .ant-notification-fade-enter.ant-notification-fade-enter-active, .ant-notification-bottomLeft .ant-notification-fade-enter.ant-notification-fade-enter-active, .ant-notification-topLeft .ant-notification-fade-appear.ant-notification-fade-appear-active, .ant-notification-bottomLeft .ant-notification-fade-appear.ant-notification-fade-appear-active { animation-name: NotificationLeftFadeIn; } @keyframes NotificationTopFadeIn { 0% { margin-top: -100%; opacity: 0; } 100% { margin-top: 0; opacity: 1; } } @keyframes NotificationBottomFadeIn { 0% { margin-bottom: -100%; opacity: 0; } 100% { margin-bottom: 0; opacity: 1; } } @keyframes NotificationLeftFadeIn { 0% { right: 384px; opacity: 0; } 100% { right: 0; opacity: 1; } } .ant-page-header { box-sizing: border-box; margin: 0; padding: 0; color: rgba(255, 255, 255, 0.85); font-size: 14px; font-variant: tabular-nums; line-height: 1.5715; list-style: none; font-feature-settings: 'tnum'; position: relative; padding: 16px 24px; background-color: #141414; } .ant-page-header-ghost { background-color: transparent; } .ant-page-header.has-breadcrumb { padding-top: 12px; } .ant-page-header.has-footer { padding-bottom: 0; } .ant-page-header-back { margin-right: 16px; font-size: 16px; line-height: 1; } .ant-page-header-back-button { color: #177ddc; outline: none; cursor: pointer; transition: color 0.3s; color: inherit; } .ant-page-header-back-button:focus-visible, .ant-page-header-back-button:hover { color: #165996; } .ant-page-header-back-button:active { color: #388ed3; } .ant-page-header .ant-divider-vertical { height: 14px; margin: 0 12px; vertical-align: middle; } .ant-breadcrumb + .ant-page-header-heading { margin-top: 8px; } .ant-page-header-heading { display: flex; justify-content: space-between; } .ant-page-header-heading-left { display: flex; align-items: center; margin: 4px 0; overflow: hidden; } .ant-page-header-heading-title { margin-right: 12px; margin-bottom: 0; color: rgba(255, 255, 255, 0.85); font-weight: 600; font-size: 20px; line-height: 32px; overflow: hidden; white-space: nowrap; text-overflow: ellipsis; } .ant-page-header-heading .ant-avatar { margin-right: 12px; } .ant-page-header-heading-sub-title { margin-right: 12px; color: rgba(255, 255, 255, 0.45); font-size: 14px; line-height: 1.5715; overflow: hidden; white-space: nowrap; text-overflow: ellipsis; } .ant-page-header-heading-extra { margin: 4px 0; white-space: nowrap; } .ant-page-header-heading-extra > * { white-space: unset; } .ant-page-header-content { padding-top: 12px; } .ant-page-header-footer { margin-top: 16px; } .ant-page-header-footer .ant-tabs > .ant-tabs-nav { margin: 0; } .ant-page-header-footer .ant-tabs > .ant-tabs-nav::before { border: none; } .ant-page-header-footer .ant-tabs .ant-tabs-tab { padding-top: 8px; padding-bottom: 8px; font-size: 16px; } .ant-page-header-compact .ant-page-header-heading { flex-wrap: wrap; } .ant-page-header-rtl { direction: rtl; } .ant-page-header-rtl .ant-page-header-back { float: right; margin-right: 0; margin-left: 16px; } .ant-page-header-rtl .ant-page-header-heading-title { margin-right: 0; margin-left: 12px; } .ant-page-header-rtl .ant-page-header-heading .ant-avatar { margin-right: 0; margin-left: 12px; } .ant-page-header-rtl .ant-page-header-heading-sub-title { float: right; margin-right: 0; margin-left: 12px; } .ant-page-header-rtl .ant-page-header-heading-tags { float: right; } .ant-page-header-rtl .ant-page-header-heading-extra { float: left; } .ant-page-header-rtl .ant-page-header-heading-extra > * { margin-right: 12px; margin-left: 0; } .ant-page-header-rtl .ant-page-header-heading-extra > *:first-child { margin-right: 0; } .ant-page-header-rtl .ant-page-header-footer .ant-tabs-bar .ant-tabs-nav { float: right; } .ant-pagination { box-sizing: border-box; margin: 0; padding: 0; color: rgba(255, 255, 255, 0.85); font-size: 14px; font-variant: tabular-nums; line-height: 1.5715; list-style: none; font-feature-settings: 'tnum'; } .ant-pagination ul, .ant-pagination ol { margin: 0; padding: 0; list-style: none; } .ant-pagination::after { display: block; clear: both; height: 0; overflow: hidden; visibility: hidden; content: ' '; } .ant-pagination-total-text { display: inline-block; height: 32px; margin-right: 8px; line-height: 30px; vertical-align: middle; } .ant-pagination-item { display: inline-block; min-width: 32px; height: 32px; margin-right: 8px; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, 'Noto Sans', sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol', 'Noto Color Emoji'; line-height: 30px; text-align: center; vertical-align: middle; list-style: none; background-color: transparent; border: 1px solid #434343; border-radius: 2px; outline: 0; cursor: pointer; user-select: none; } .ant-pagination-item a { display: block; padding: 0 6px; color: rgba(255, 255, 255, 0.85); transition: none; } .ant-pagination-item a:hover { text-decoration: none; } .ant-pagination-item:hover { border-color: #177ddc; transition: all 0.3s; } .ant-pagination-item:hover a { color: #177ddc; } .ant-pagination-item:focus-visible { border-color: #177ddc; transition: all 0.3s; } .ant-pagination-item:focus-visible a { color: #177ddc; } .ant-pagination-item-active { font-weight: 500; background: transparent; border-color: #177ddc; } .ant-pagination-item-active a { color: #177ddc; } .ant-pagination-item-active:hover { border-color: #165996; } .ant-pagination-item-active:focus-visible { border-color: #165996; } .ant-pagination-item-active:hover a { color: #165996; } .ant-pagination-item-active:focus-visible a { color: #165996; } .ant-pagination-jump-prev, .ant-pagination-jump-next { outline: 0; } .ant-pagination-jump-prev .ant-pagination-item-container, .ant-pagination-jump-next .ant-pagination-item-container { position: relative; } .ant-pagination-jump-prev .ant-pagination-item-container .ant-pagination-item-link-icon, .ant-pagination-jump-next .ant-pagination-item-container .ant-pagination-item-link-icon { color: #177ddc; font-size: 12px; letter-spacing: -1px; opacity: 0; transition: all 0.2s; } .ant-pagination-jump-prev .ant-pagination-item-container .ant-pagination-item-link-icon-svg, .ant-pagination-jump-next .ant-pagination-item-container .ant-pagination-item-link-icon-svg { top: 0; right: 0; bottom: 0; left: 0; margin: auto; } .ant-pagination-jump-prev .ant-pagination-item-container .ant-pagination-item-ellipsis, .ant-pagination-jump-next .ant-pagination-item-container .ant-pagination-item-ellipsis { position: absolute; top: 0; right: 0; bottom: 0; left: 0; display: block; margin: auto; color: rgba(255, 255, 255, 0.3); font-family: Arial, Helvetica, sans-serif; letter-spacing: 2px; text-align: center; text-indent: 0.13em; opacity: 1; transition: all 0.2s; } .ant-pagination-jump-prev:hover .ant-pagination-item-link-icon, .ant-pagination-jump-next:hover .ant-pagination-item-link-icon { opacity: 1; } .ant-pagination-jump-prev:hover .ant-pagination-item-ellipsis, .ant-pagination-jump-next:hover .ant-pagination-item-ellipsis { opacity: 0; } .ant-pagination-jump-prev:focus-visible .ant-pagination-item-link-icon, .ant-pagination-jump-next:focus-visible .ant-pagination-item-link-icon { opacity: 1; } .ant-pagination-jump-prev:focus-visible .ant-pagination-item-ellipsis, .ant-pagination-jump-next:focus-visible .ant-pagination-item-ellipsis { opacity: 0; } .ant-pagination-prev, .ant-pagination-jump-prev, .ant-pagination-jump-next { margin-right: 8px; } .ant-pagination-prev, .ant-pagination-next, .ant-pagination-jump-prev, .ant-pagination-jump-next { display: inline-block; min-width: 32px; height: 32px; color: rgba(255, 255, 255, 0.85); font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, 'Noto Sans', sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol', 'Noto Color Emoji'; line-height: 32px; text-align: center; vertical-align: middle; list-style: none; border-radius: 2px; cursor: pointer; transition: all 0.3s; } .ant-pagination-prev, .ant-pagination-next { font-family: Arial, Helvetica, sans-serif; outline: 0; } .ant-pagination-prev button, .ant-pagination-next button { color: rgba(255, 255, 255, 0.85); cursor: pointer; user-select: none; } .ant-pagination-prev:hover button, .ant-pagination-next:hover button { border-color: #165996; } .ant-pagination-prev .ant-pagination-item-link, .ant-pagination-next .ant-pagination-item-link { display: block; width: 100%; height: 100%; padding: 0; font-size: 12px; text-align: center; background-color: transparent; border: 1px solid #434343; border-radius: 2px; outline: none; transition: all 0.3s; } .ant-pagination-prev:focus-visible .ant-pagination-item-link, .ant-pagination-next:focus-visible .ant-pagination-item-link { color: #177ddc; border-color: #177ddc; } .ant-pagination-prev:hover .ant-pagination-item-link, .ant-pagination-next:hover .ant-pagination-item-link { color: #177ddc; border-color: #177ddc; } .ant-pagination-disabled, .ant-pagination-disabled:hover { cursor: not-allowed; } .ant-pagination-disabled .ant-pagination-item-link, .ant-pagination-disabled:hover .ant-pagination-item-link { color: rgba(255, 255, 255, 0.3); border-color: #434343; cursor: not-allowed; } .ant-pagination-disabled:focus-visible { cursor: not-allowed; } .ant-pagination-disabled:focus-visible .ant-pagination-item-link { color: rgba(255, 255, 255, 0.3); border-color: #434343; cursor: not-allowed; } .ant-pagination-slash { margin: 0 10px 0 5px; } .ant-pagination-options { display: inline-block; margin-left: 16px; vertical-align: middle; } @media all and (-ms-high-contrast: none) { .ant-pagination-options *::-ms-backdrop, .ant-pagination-options { vertical-align: top; } } .ant-pagination-options-size-changer.ant-select { display: inline-block; width: auto; } .ant-pagination-options-quick-jumper { display: inline-block; height: 32px; margin-left: 8px; line-height: 32px; vertical-align: top; } .ant-pagination-options-quick-jumper input { position: relative; display: inline-block; width: 100%; min-width: 0; padding: 4px 11px; color: rgba(255, 255, 255, 0.85); font-size: 14px; line-height: 1.5715; background-color: transparent; background-image: none; border: 1px solid #434343; border-radius: 2px; transition: all 0.3s; width: 50px; height: 32px; margin: 0 8px; } .ant-pagination-options-quick-jumper input::placeholder { color: rgba(255, 255, 255, 0.3); user-select: none; } .ant-pagination-options-quick-jumper input:placeholder-shown { text-overflow: ellipsis; } .ant-pagination-options-quick-jumper input:hover { border-color: #165996; border-right-width: 1px; } .ant-input-rtl .ant-pagination-options-quick-jumper input:hover { border-right-width: 0; border-left-width: 1px !important; } .ant-pagination-options-quick-jumper input:focus, .ant-pagination-options-quick-jumper input-focused { border-color: #177ddc; box-shadow: 0 0 0 2px rgba(23, 125, 220, 0.2); border-right-width: 1px; outline: 0; } .ant-input-rtl .ant-pagination-options-quick-jumper input:focus, .ant-input-rtl .ant-pagination-options-quick-jumper input-focused { border-right-width: 0; border-left-width: 1px !important; } .ant-pagination-options-quick-jumper input-disabled { color: rgba(255, 255, 255, 0.3); background-color: rgba(255, 255, 255, 0.08); border-color: #434343; box-shadow: none; cursor: not-allowed; opacity: 1; } .ant-pagination-options-quick-jumper input-disabled:hover { border-color: #434343; border-right-width: 1px; } .ant-pagination-options-quick-jumper input[disabled] { color: rgba(255, 255, 255, 0.3); background-color: rgba(255, 255, 255, 0.08); border-color: #434343; box-shadow: none; cursor: not-allowed; opacity: 1; } .ant-pagination-options-quick-jumper input[disabled]:hover { border-color: #434343; border-right-width: 1px; } .ant-pagination-options-quick-jumper input-borderless, .ant-pagination-options-quick-jumper input-borderless:hover, .ant-pagination-options-quick-jumper input-borderless:focus, .ant-pagination-options-quick-jumper input-borderless-focused, .ant-pagination-options-quick-jumper input-borderless-disabled, .ant-pagination-options-quick-jumper input-borderless[disabled] { background-color: transparent; border: none; box-shadow: none; } textarea.ant-pagination-options-quick-jumper input { max-width: 100%; height: auto; min-height: 32px; line-height: 1.5715; vertical-align: bottom; transition: all 0.3s, height 0s; } .ant-pagination-options-quick-jumper input-lg { padding: 6.5px 11px; font-size: 16px; } .ant-pagination-options-quick-jumper input-sm { padding: 0px 7px; } .ant-pagination-options-quick-jumper input-rtl { direction: rtl; } .ant-pagination-simple .ant-pagination-prev, .ant-pagination-simple .ant-pagination-next { height: 24px; line-height: 24px; vertical-align: top; } .ant-pagination-simple .ant-pagination-prev .ant-pagination-item-link, .ant-pagination-simple .ant-pagination-next .ant-pagination-item-link { height: 24px; background-color: transparent; border: 0; } .ant-pagination-simple .ant-pagination-prev .ant-pagination-item-link::after, .ant-pagination-simple .ant-pagination-next .ant-pagination-item-link::after { height: 24px; line-height: 24px; } .ant-pagination-simple .ant-pagination-simple-pager { display: inline-block; height: 24px; margin-right: 8px; } .ant-pagination-simple .ant-pagination-simple-pager input { box-sizing: border-box; height: 100%; margin-right: 8px; padding: 0 6px; text-align: center; background-color: transparent; border: 1px solid #434343; border-radius: 2px; outline: none; transition: border-color 0.3s; } .ant-pagination-simple .ant-pagination-simple-pager input:hover { border-color: #177ddc; } .ant-pagination-simple .ant-pagination-simple-pager input:focus { border-color: #3c9be8; box-shadow: 0 0 0 2px rgba(23, 125, 220, 0.2); } .ant-pagination-simple .ant-pagination-simple-pager input[disabled] { color: rgba(255, 255, 255, 0.3); background: rgba(255, 255, 255, 0.08); border-color: #434343; cursor: not-allowed; } .ant-pagination.ant-pagination-mini .ant-pagination-total-text, .ant-pagination.ant-pagination-mini .ant-pagination-simple-pager { height: 24px; line-height: 24px; } .ant-pagination.ant-pagination-mini .ant-pagination-item { min-width: 24px; height: 24px; margin: 0; line-height: 22px; } .ant-pagination.ant-pagination-mini .ant-pagination-item:not(.ant-pagination-item-active) { background: transparent; border-color: transparent; } .ant-pagination.ant-pagination-mini .ant-pagination-prev, .ant-pagination.ant-pagination-mini .ant-pagination-next { min-width: 24px; height: 24px; margin: 0; line-height: 24px; } .ant-pagination.ant-pagination-mini .ant-pagination-prev .ant-pagination-item-link, .ant-pagination.ant-pagination-mini .ant-pagination-next .ant-pagination-item-link { background: transparent; border-color: transparent; } .ant-pagination.ant-pagination-mini .ant-pagination-prev .ant-pagination-item-link::after, .ant-pagination.ant-pagination-mini .ant-pagination-next .ant-pagination-item-link::after { height: 24px; line-height: 24px; } .ant-pagination.ant-pagination-mini .ant-pagination-jump-prev, .ant-pagination.ant-pagination-mini .ant-pagination-jump-next { height: 24px; margin-right: 0; line-height: 24px; } .ant-pagination.ant-pagination-mini .ant-pagination-options { margin-left: 2px; } .ant-pagination.ant-pagination-mini .ant-pagination-options-size-changer { top: 0px; } .ant-pagination.ant-pagination-mini .ant-pagination-options-quick-jumper { height: 24px; line-height: 24px; } .ant-pagination.ant-pagination-mini .ant-pagination-options-quick-jumper input { padding: 0px 7px; width: 44px; height: 24px; } .ant-pagination.ant-pagination-disabled { cursor: not-allowed; } .ant-pagination.ant-pagination-disabled .ant-pagination-item { background: rgba(255, 255, 255, 0.08); border-color: #434343; cursor: not-allowed; } .ant-pagination.ant-pagination-disabled .ant-pagination-item a { color: rgba(255, 255, 255, 0.3); background: transparent; border: none; cursor: not-allowed; } .ant-pagination.ant-pagination-disabled .ant-pagination-item-active { background: rgba(255, 255, 255, 0.25); } .ant-pagination.ant-pagination-disabled .ant-pagination-item-active a { color: #000; } .ant-pagination.ant-pagination-disabled .ant-pagination-item-link { color: rgba(255, 255, 255, 0.3); background: rgba(255, 255, 255, 0.08); border-color: #434343; cursor: not-allowed; } .ant-pagination-simple.ant-pagination.ant-pagination-disabled .ant-pagination-item-link { background: transparent; } .ant-pagination.ant-pagination-disabled .ant-pagination-item-link-icon { opacity: 0; } .ant-pagination.ant-pagination-disabled .ant-pagination-item-ellipsis { opacity: 1; } .ant-pagination.ant-pagination-disabled .ant-pagination-simple-pager { color: rgba(255, 255, 255, 0.3); } @media only screen and (max-width: 992px) { .ant-pagination-item-after-jump-prev, .ant-pagination-item-before-jump-next { display: none; } } @media only screen and (max-width: 576px) { .ant-pagination-options { display: none; } } .ant-pagination-rtl .ant-pagination-total-text { margin-right: 0; margin-left: 8px; } .ant-pagination-rtl .ant-pagination-item, .ant-pagination-rtl .ant-pagination-prev, .ant-pagination-rtl .ant-pagination-jump-prev, .ant-pagination-rtl .ant-pagination-jump-next { margin-right: 0; margin-left: 8px; } .ant-pagination-rtl .ant-pagination-slash { margin: 0 5px 0 10px; } .ant-pagination-rtl .ant-pagination-options { margin-right: 16px; margin-left: 0; } .ant-pagination-rtl .ant-pagination-options .ant-pagination-options-size-changer.ant-select { margin-right: 0; margin-left: 8px; } .ant-pagination-rtl .ant-pagination-options .ant-pagination-options-quick-jumper { margin-left: 0; } .ant-pagination-rtl.ant-pagination-simple .ant-pagination-simple-pager { margin-right: 0; margin-left: 8px; } .ant-pagination-rtl.ant-pagination-simple .ant-pagination-simple-pager input { margin-right: 0; margin-left: 8px; } .ant-pagination-rtl.ant-pagination.mini .ant-pagination-options { margin-right: 2px; margin-left: 0; } .ant-popconfirm { z-index: 1060; } .ant-popover { box-sizing: border-box; margin: 0; padding: 0; color: rgba(255, 255, 255, 0.85); font-size: 14px; font-variant: tabular-nums; line-height: 1.5715; list-style: none; font-feature-settings: 'tnum'; position: absolute; top: 0; left: 0; z-index: 1030; max-width: 100vw; font-weight: normal; white-space: normal; text-align: left; cursor: auto; user-select: text; } .ant-popover-content { position: relative; } .ant-popover::after { position: absolute; background: rgba(255, 255, 255, 0.01); content: ''; } .ant-popover-hidden { display: none; } .ant-popover-placement-top, .ant-popover-placement-topLeft, .ant-popover-placement-topRight { padding-bottom: 15.3137085px; } .ant-popover-placement-right, .ant-popover-placement-rightTop, .ant-popover-placement-rightBottom { padding-left: 15.3137085px; } .ant-popover-placement-bottom, .ant-popover-placement-bottomLeft, .ant-popover-placement-bottomRight { padding-top: 15.3137085px; } .ant-popover-placement-left, .ant-popover-placement-leftTop, .ant-popover-placement-leftBottom { padding-right: 15.3137085px; } .ant-popover-inner { background-color: #1f1f1f; background-clip: padding-box; border-radius: 2px; box-shadow: 0 3px 6px -4px rgba(0, 0, 0, 0.48), 0 6px 16px 0 rgba(0, 0, 0, 0.32), 0 9px 28px 8px rgba(0, 0, 0, 0.2); } @media screen and (-ms-high-contrast: active), (-ms-high-contrast: none) { .ant-popover { /* IE10+ */ } .ant-popover-inner { box-shadow: 0 3px 6px -4px rgba(0, 0, 0, 0.48), 0 6px 16px 0 rgba(0, 0, 0, 0.32), 0 9px 28px 8px rgba(0, 0, 0, 0.2); } } .ant-popover-title { min-width: 177px; min-height: 32px; margin: 0; padding: 5px 16px 4px; color: rgba(255, 255, 255, 0.85); font-weight: 500; border-bottom: 1px solid #303030; } .ant-popover-inner-content { width: max-content; max-width: 100%; padding: 12px 16px; color: rgba(255, 255, 255, 0.85); } .ant-popover-message { display: flex; padding: 4px 0 12px; color: rgba(255, 255, 255, 0.85); font-size: 14px; } .ant-popover-message-icon { display: inline-block; margin-right: 8px; color: #d89614; font-size: 14px; } .ant-popover-buttons { margin-bottom: 4px; text-align: right; } .ant-popover-buttons button:not(:first-child) { margin-left: 8px; } .ant-popover-arrow { position: absolute; display: block; width: 22px; height: 22px; overflow: hidden; background: transparent; pointer-events: none; } .ant-popover-arrow-content { --antd-arrow-background-color: #1f1f1f; position: absolute; top: 0; right: 0; bottom: 0; left: 0; display: block; width: 11.3137085px; height: 11.3137085px; margin: auto; content: ''; pointer-events: auto; border-radius: 0 0 2px; pointer-events: none; } .ant-popover-arrow-content::before { position: absolute; top: -11.3137085px; left: -11.3137085px; width: 33.9411255px; height: 33.9411255px; background: var(--antd-arrow-background-color); background-repeat: no-repeat; background-position: -10px -10px; content: ''; clip-path: inset(33% 33%); clip-path: path('M 9.849242404917499 24.091883092036785 A 5 5 0 0 1 13.384776310850237 22.627416997969522 L 20.627416997969522 22.627416997969522 A 2 2 0 0 0 22.627416997969522 20.627416997969522 L 22.627416997969522 13.384776310850237 A 5 5 0 0 1 24.091883092036785 9.849242404917499 L 23.091883092036785 9.849242404917499 L 9.849242404917499 23.091883092036785 Z'); } .ant-popover-placement-top .ant-popover-arrow, .ant-popover-placement-topLeft .ant-popover-arrow, .ant-popover-placement-topRight .ant-popover-arrow { bottom: 0; transform: translateY(100%); } .ant-popover-placement-top .ant-popover-arrow-content, .ant-popover-placement-topLeft .ant-popover-arrow-content, .ant-popover-placement-topRight .ant-popover-arrow-content { box-shadow: 3px 3px 7px rgba(0, 0, 0, 0.07); transform: translateY(-11px) rotate(45deg); } .ant-popover-placement-top .ant-popover-arrow { left: 50%; transform: translateY(100%) translateX(-50%); } .ant-popover-placement-topLeft .ant-popover-arrow { left: 16px; } .ant-popover-placement-topRight .ant-popover-arrow { right: 16px; } .ant-popover-placement-right .ant-popover-arrow, .ant-popover-placement-rightTop .ant-popover-arrow, .ant-popover-placement-rightBottom .ant-popover-arrow { left: 0; transform: translateX(-100%); } .ant-popover-placement-right .ant-popover-arrow-content, .ant-popover-placement-rightTop .ant-popover-arrow-content, .ant-popover-placement-rightBottom .ant-popover-arrow-content { box-shadow: 3px 3px 7px rgba(0, 0, 0, 0.07); transform: translateX(11px) rotate(135deg); } .ant-popover-placement-right .ant-popover-arrow { top: 50%; transform: translateX(-100%) translateY(-50%); } .ant-popover-placement-rightTop .ant-popover-arrow { top: 12px; } .ant-popover-placement-rightBottom .ant-popover-arrow { bottom: 12px; } .ant-popover-placement-bottom .ant-popover-arrow, .ant-popover-placement-bottomLeft .ant-popover-arrow, .ant-popover-placement-bottomRight .ant-popover-arrow { top: 0; transform: translateY(-100%); } .ant-popover-placement-bottom .ant-popover-arrow-content, .ant-popover-placement-bottomLeft .ant-popover-arrow-content, .ant-popover-placement-bottomRight .ant-popover-arrow-content { box-shadow: 2px 2px 5px rgba(0, 0, 0, 0.06); transform: translateY(11px) rotate(-135deg); } .ant-popover-placement-bottom .ant-popover-arrow { left: 50%; transform: translateY(-100%) translateX(-50%); } .ant-popover-placement-bottomLeft .ant-popover-arrow { left: 16px; } .ant-popover-placement-bottomRight .ant-popover-arrow { right: 16px; } .ant-popover-placement-left .ant-popover-arrow, .ant-popover-placement-leftTop .ant-popover-arrow, .ant-popover-placement-leftBottom .ant-popover-arrow { right: 0; transform: translateX(100%); } .ant-popover-placement-left .ant-popover-arrow-content, .ant-popover-placement-leftTop .ant-popover-arrow-content, .ant-popover-placement-leftBottom .ant-popover-arrow-content { box-shadow: 3px 3px 7px rgba(0, 0, 0, 0.07); transform: translateX(-11px) rotate(-45deg); } .ant-popover-placement-left .ant-popover-arrow { top: 50%; transform: translateX(100%) translateY(-50%); } .ant-popover-placement-leftTop .ant-popover-arrow { top: 12px; } .ant-popover-placement-leftBottom .ant-popover-arrow { bottom: 12px; } .ant-popover-pink .ant-popover-inner { background-color: #cb2b83; } .ant-popover-pink .ant-popover-arrow-content { background-color: #cb2b83; } .ant-popover-magenta .ant-popover-inner { background-color: #cb2b83; } .ant-popover-magenta .ant-popover-arrow-content { background-color: #cb2b83; } .ant-popover-red .ant-popover-inner { background-color: #d32029; } .ant-popover-red .ant-popover-arrow-content { background-color: #d32029; } .ant-popover-volcano .ant-popover-inner { background-color: #d84a1b; } .ant-popover-volcano .ant-popover-arrow-content { background-color: #d84a1b; } .ant-popover-orange .ant-popover-inner { background-color: #d87a16; } .ant-popover-orange .ant-popover-arrow-content { background-color: #d87a16; } .ant-popover-yellow .ant-popover-inner { background-color: #d8bd14; } .ant-popover-yellow .ant-popover-arrow-content { background-color: #d8bd14; } .ant-popover-gold .ant-popover-inner { background-color: #d89614; } .ant-popover-gold .ant-popover-arrow-content { background-color: #d89614; } .ant-popover-cyan .ant-popover-inner { background-color: #13a8a8; } .ant-popover-cyan .ant-popover-arrow-content { background-color: #13a8a8; } .ant-popover-lime .ant-popover-inner { background-color: #8bbb11; } .ant-popover-lime .ant-popover-arrow-content { background-color: #8bbb11; } .ant-popover-green .ant-popover-inner { background-color: #49aa19; } .ant-popover-green .ant-popover-arrow-content { background-color: #49aa19; } .ant-popover-blue .ant-popover-inner { background-color: #177ddc; } .ant-popover-blue .ant-popover-arrow-content { background-color: #177ddc; } .ant-popover-geekblue .ant-popover-inner { background-color: #2b4acb; } .ant-popover-geekblue .ant-popover-arrow-content { background-color: #2b4acb; } .ant-popover-purple .ant-popover-inner { background-color: #642ab5; } .ant-popover-purple .ant-popover-arrow-content { background-color: #642ab5; } .ant-popover-rtl { direction: rtl; text-align: right; } .ant-popover-rtl .ant-popover-message-icon { margin-right: 0; margin-left: 8px; } .ant-popover-rtl .ant-popover-message-title { padding-left: 16px; } .ant-popover-rtl .ant-popover-buttons { text-align: left; } .ant-popover-rtl .ant-popover-buttons button { margin-right: 8px; margin-left: 0; } .ant-progress { box-sizing: border-box; margin: 0; padding: 0; color: rgba(255, 255, 255, 0.85); font-size: 14px; font-variant: tabular-nums; line-height: 1.5715; list-style: none; font-feature-settings: 'tnum'; display: inline-block; } .ant-progress-line { position: relative; width: 100%; font-size: 14px; } .ant-progress-steps { display: inline-block; } .ant-progress-steps-outer { display: flex; flex-direction: row; align-items: center; } .ant-progress-steps-item { flex-shrink: 0; min-width: 2px; margin-right: 2px; background: rgba(255, 255, 255, 0.08); transition: all 0.3s; } .ant-progress-steps-item-active { background: #177ddc; } .ant-progress-small.ant-progress-line, .ant-progress-small.ant-progress-line .ant-progress-text .anticon { font-size: 12px; } .ant-progress-outer { display: inline-block; width: 100%; margin-right: 0; padding-right: 0; } .ant-progress-show-info .ant-progress-outer { margin-right: calc(-2em - 8px); padding-right: calc(2em + 8px); } .ant-progress-inner { position: relative; display: inline-block; width: 100%; overflow: hidden; vertical-align: middle; background-color: rgba(255, 255, 255, 0.08); border-radius: 100px; } .ant-progress-circle-trail { stroke: rgba(255, 255, 255, 0.08); } .ant-progress-circle-path { animation: ant-progress-appear 0.3s; } .ant-progress-inner:not(.ant-progress-circle-gradient) .ant-progress-circle-path { stroke: #177ddc; } .ant-progress-success-bg, .ant-progress-bg { position: relative; background-color: #177ddc; border-radius: 100px; transition: all 0.4s cubic-bezier(0.08, 0.82, 0.17, 1) 0s; } .ant-progress-success-bg { position: absolute; top: 0; left: 0; background-color: #49aa19; } .ant-progress-text { display: inline-block; width: 2em; margin-left: 8px; color: rgba(255, 255, 255, 0.85); font-size: 1em; line-height: 1; white-space: nowrap; text-align: left; vertical-align: middle; word-break: normal; } .ant-progress-text .anticon { font-size: 14px; } .ant-progress-status-active .ant-progress-bg::before { position: absolute; top: 0; right: 0; bottom: 0; left: 0; background: #141414; border-radius: 10px; opacity: 0; animation: ant-progress-active 2.4s cubic-bezier(0.23, 1, 0.32, 1) infinite; content: ''; } .ant-progress-status-exception .ant-progress-bg { background-color: #a61d24; } .ant-progress-status-exception .ant-progress-text { color: #a61d24; } .ant-progress-status-exception .ant-progress-inner:not(.ant-progress-circle-gradient) .ant-progress-circle-path { stroke: #a61d24; } .ant-progress-status-success .ant-progress-bg { background-color: #49aa19; } .ant-progress-status-success .ant-progress-text { color: #49aa19; } .ant-progress-status-success .ant-progress-inner:not(.ant-progress-circle-gradient) .ant-progress-circle-path { stroke: #49aa19; } .ant-progress-circle .ant-progress-inner { position: relative; line-height: 1; background-color: transparent; } .ant-progress-circle .ant-progress-text { position: absolute; top: 50%; left: 50%; width: 100%; margin: 0; padding: 0; color: rgba(255, 255, 255, 0.85); font-size: 1em; line-height: 1; white-space: normal; text-align: center; transform: translate(-50%, -50%); } .ant-progress-circle .ant-progress-text .anticon { font-size: 1.16666667em; } .ant-progress-circle.ant-progress-status-exception .ant-progress-text { color: #a61d24; } .ant-progress-circle.ant-progress-status-success .ant-progress-text { color: #49aa19; } @keyframes ant-progress-active { 0% { transform: translateX(-100%) scaleX(0); opacity: 0.1; } 20% { transform: translateX(-100%) scaleX(0); opacity: 0.5; } 100% { transform: translateX(0) scaleX(1); opacity: 0; } } .ant-progress-rtl { direction: rtl; } .ant-progress-rtl.ant-progress-show-info .ant-progress-outer { margin-right: 0; margin-left: calc(-2em - 8px); padding-right: 0; padding-left: calc(2em + 8px); } .ant-progress-rtl .ant-progress-success-bg { right: 0; left: auto; } .ant-progress-rtl.ant-progress-line .ant-progress-text, .ant-progress-rtl.ant-progress-steps .ant-progress-text { margin-right: 8px; margin-left: 0; text-align: right; } .ant-radio-group { box-sizing: border-box; margin: 0; padding: 0; color: rgba(255, 255, 255, 0.85); font-size: 14px; font-variant: tabular-nums; line-height: 1.5715; list-style: none; font-feature-settings: 'tnum'; display: inline-block; font-size: 0; } .ant-radio-group .ant-badge-count { z-index: 1; } .ant-radio-group > .ant-badge:not(:first-child) > .ant-radio-button-wrapper { border-left: none; } .ant-radio-wrapper { box-sizing: border-box; margin: 0; padding: 0; color: rgba(255, 255, 255, 0.85); font-size: 14px; font-variant: tabular-nums; line-height: 1.5715; list-style: none; font-feature-settings: 'tnum'; position: relative; display: inline-flex; align-items: baseline; margin-right: 8px; cursor: pointer; } .ant-radio-wrapper-disabled { cursor: not-allowed; } .ant-radio-wrapper::after { display: inline-block; width: 0; overflow: hidden; content: '\a0'; } .ant-radio-wrapper.ant-radio-wrapper-in-form-item input[type='radio'] { width: 14px; height: 14px; } .ant-radio { box-sizing: border-box; margin: 0; padding: 0; color: rgba(255, 255, 255, 0.85); font-size: 14px; font-variant: tabular-nums; line-height: 1.5715; list-style: none; font-feature-settings: 'tnum'; position: relative; top: 0.2em; display: inline-block; outline: none; cursor: pointer; } .ant-radio-wrapper:hover .ant-radio, .ant-radio:hover .ant-radio-inner, .ant-radio-input:focus + .ant-radio-inner { border-color: #177ddc; } .ant-radio-input:focus + .ant-radio-inner { box-shadow: 0 0 0 3px rgba(23, 125, 220, 0.12); } .ant-radio-checked::after { position: absolute; top: 0; left: 0; width: 100%; height: 100%; border: 1px solid #177ddc; border-radius: 50%; visibility: hidden; animation: antRadioEffect 0.36s ease-in-out; animation-fill-mode: both; content: ''; } .ant-radio:hover::after, .ant-radio-wrapper:hover .ant-radio::after { visibility: visible; } .ant-radio-inner { position: relative; top: 0; left: 0; display: block; width: 16px; height: 16px; background-color: transparent; border-color: #434343; border-style: solid; border-width: 1px; border-radius: 50%; transition: all 0.3s; } .ant-radio-inner::after { position: absolute; top: 50%; left: 50%; display: block; width: 16px; height: 16px; margin-top: -8px; margin-left: -8px; background-color: #177ddc; border-top: 0; border-left: 0; border-radius: 16px; transform: scale(0); opacity: 0; transition: all 0.3s cubic-bezier(0.78, 0.14, 0.15, 0.86); content: ' '; } .ant-radio-input { position: absolute; top: 0; right: 0; bottom: 0; left: 0; z-index: 1; cursor: pointer; opacity: 0; } .ant-radio.ant-radio-disabled .ant-radio-inner { border-color: #434343; } .ant-radio-checked .ant-radio-inner { border-color: #177ddc; } .ant-radio-checked .ant-radio-inner::after { transform: scale(0.5); opacity: 1; transition: all 0.3s cubic-bezier(0.78, 0.14, 0.15, 0.86); } .ant-radio-disabled { cursor: not-allowed; } .ant-radio-disabled .ant-radio-inner { background-color: rgba(255, 255, 255, 0.08); cursor: not-allowed; } .ant-radio-disabled .ant-radio-inner::after { background-color: rgba(255, 255, 255, 0.2); } .ant-radio-disabled .ant-radio-input { cursor: not-allowed; } .ant-radio-disabled + span { color: rgba(255, 255, 255, 0.3); cursor: not-allowed; } span.ant-radio + * { padding-right: 8px; padding-left: 8px; } .ant-radio-button-wrapper { position: relative; display: inline-block; height: 32px; margin: 0; padding: 0 15px; color: rgba(255, 255, 255, 0.85); font-size: 14px; line-height: 30px; background: transparent; border: 1px solid #434343; border-top-width: 1.02px; border-left-width: 0; cursor: pointer; transition: color 0.3s, background 0.3s, border-color 0.3s, box-shadow 0.3s; } .ant-radio-button-wrapper a { color: rgba(255, 255, 255, 0.85); } .ant-radio-button-wrapper > .ant-radio-button { position: absolute; top: 0; left: 0; z-index: -1; width: 100%; height: 100%; } .ant-radio-group-large .ant-radio-button-wrapper { height: 40px; font-size: 16px; line-height: 38px; } .ant-radio-group-small .ant-radio-button-wrapper { height: 24px; padding: 0 7px; line-height: 22px; } .ant-radio-button-wrapper:not(:first-child)::before { position: absolute; top: -1px; left: -1px; display: block; box-sizing: content-box; width: 1px; height: 100%; padding: 1px 0; background-color: #434343; transition: background-color 0.3s; content: ''; } .ant-radio-button-wrapper:first-child { border-left: 1px solid #434343; border-radius: 2px 0 0 2px; } .ant-radio-button-wrapper:last-child { border-radius: 0 2px 2px 0; } .ant-radio-button-wrapper:first-child:last-child { border-radius: 2px; } .ant-radio-button-wrapper:hover { position: relative; color: #177ddc; } .ant-radio-button-wrapper:focus-within { box-shadow: 0 0 0 3px rgba(23, 125, 220, 0.12); } .ant-radio-button-wrapper .ant-radio-inner, .ant-radio-button-wrapper input[type='checkbox'], .ant-radio-button-wrapper input[type='radio'] { width: 0; height: 0; opacity: 0; pointer-events: none; } .ant-radio-button-wrapper-checked:not(.ant-radio-button-wrapper-disabled) { z-index: 1; color: #177ddc; background: transparent; border-color: #177ddc; } .ant-radio-button-wrapper-checked:not(.ant-radio-button-wrapper-disabled)::before { background-color: #177ddc; } .ant-radio-button-wrapper-checked:not(.ant-radio-button-wrapper-disabled):first-child { border-color: #177ddc; } .ant-radio-button-wrapper-checked:not(.ant-radio-button-wrapper-disabled):hover { color: #165996; border-color: #165996; } .ant-radio-button-wrapper-checked:not(.ant-radio-button-wrapper-disabled):hover::before { background-color: #165996; } .ant-radio-button-wrapper-checked:not(.ant-radio-button-wrapper-disabled):active { color: #388ed3; border-color: #388ed3; } .ant-radio-button-wrapper-checked:not(.ant-radio-button-wrapper-disabled):active::before { background-color: #388ed3; } .ant-radio-button-wrapper-checked:not(.ant-radio-button-wrapper-disabled):focus-within { box-shadow: 0 0 0 3px rgba(23, 125, 220, 0.12); } .ant-radio-group-solid .ant-radio-button-wrapper-checked:not(.ant-radio-button-wrapper-disabled) { color: #fff; background: #177ddc; border-color: #177ddc; } .ant-radio-group-solid .ant-radio-button-wrapper-checked:not(.ant-radio-button-wrapper-disabled):hover { color: #fff; background: #165996; border-color: #165996; } .ant-radio-group-solid .ant-radio-button-wrapper-checked:not(.ant-radio-button-wrapper-disabled):active { color: #fff; background: #388ed3; border-color: #388ed3; } .ant-radio-group-solid .ant-radio-button-wrapper-checked:not(.ant-radio-button-wrapper-disabled):focus-within { box-shadow: 0 0 0 3px rgba(23, 125, 220, 0.12); } .ant-radio-button-wrapper-disabled { color: rgba(255, 255, 255, 0.3); background-color: rgba(255, 255, 255, 0.08); border-color: #434343; cursor: not-allowed; } .ant-radio-button-wrapper-disabled:first-child, .ant-radio-button-wrapper-disabled:hover { color: rgba(255, 255, 255, 0.3); background-color: rgba(255, 255, 255, 0.08); border-color: #434343; } .ant-radio-button-wrapper-disabled:first-child { border-left-color: #434343; } .ant-radio-button-wrapper-disabled.ant-radio-button-wrapper-checked { color: rgba(255, 255, 255, 0.3); background-color: rgba(255, 255, 255, 0.2); border-color: #434343; box-shadow: none; } @keyframes antRadioEffect { 0% { transform: scale(1); opacity: 0.5; } 100% { transform: scale(1.6); opacity: 0; } } .ant-radio-group.ant-radio-group-rtl { direction: rtl; } .ant-radio-wrapper.ant-radio-wrapper-rtl { margin-right: 0; margin-left: 8px; direction: rtl; } .ant-radio-button-wrapper.ant-radio-button-wrapper-rtl { border-right-width: 0; border-left-width: 1px; } .ant-radio-button-wrapper.ant-radio-button-wrapper-rtl.ant-radio-button-wrapper:not(:first-child)::before { right: -1px; left: 0; } .ant-radio-button-wrapper.ant-radio-button-wrapper-rtl.ant-radio-button-wrapper:first-child { border-right: 1px solid #434343; border-radius: 0 2px 2px 0; } .ant-radio-button-wrapper-checked:not([class*=' ant-radio-button-wrapper-disabled']).ant-radio-button-wrapper:first-child { border-right-color: #165996; } .ant-radio-button-wrapper.ant-radio-button-wrapper-rtl.ant-radio-button-wrapper:last-child { border-radius: 2px 0 0 2px; } .ant-radio-button-wrapper.ant-radio-button-wrapper-rtl.ant-radio-button-wrapper-disabled:first-child { border-right-color: #434343; } .ant-rate { box-sizing: border-box; color: rgba(255, 255, 255, 0.85); font-size: 14px; font-variant: tabular-nums; line-height: 1.5715; font-feature-settings: 'tnum'; display: inline-block; margin: 0; padding: 0; color: #d8bd14; font-size: 20px; line-height: unset; list-style: none; outline: none; } .ant-rate-disabled .ant-rate-star { cursor: default; } .ant-rate-disabled .ant-rate-star > div:hover { transform: scale(1); } .ant-rate-star { position: relative; display: inline-block; color: inherit; cursor: pointer; } .ant-rate-star:not(:last-child) { margin-right: 8px; } .ant-rate-star > div { transition: all 0.3s, outline 0s; } .ant-rate-star > div:hover { transform: scale(1.1); } .ant-rate-star > div:focus { outline: 0; } .ant-rate-star > div:focus-visible { outline: 1px dashed #d8bd14; transform: scale(1.1); } .ant-rate-star-first, .ant-rate-star-second { color: rgba(255, 255, 255, 0.12); transition: all 0.3s; user-select: none; } .ant-rate-star-first .anticon, .ant-rate-star-second .anticon { vertical-align: middle; } .ant-rate-star-first { position: absolute; top: 0; left: 0; width: 50%; height: 100%; overflow: hidden; opacity: 0; } .ant-rate-star-half .ant-rate-star-first, .ant-rate-star-half .ant-rate-star-second { opacity: 1; } .ant-rate-star-half .ant-rate-star-first, .ant-rate-star-full .ant-rate-star-second { color: inherit; } .ant-rate-text { display: inline-block; margin: 0 8px; font-size: 14px; } .ant-rate-rtl { direction: rtl; } .ant-rate-rtl .ant-rate-star:not(:last-child) { margin-right: 0; margin-left: 8px; } .ant-rate-rtl .ant-rate-star-first { right: 0; left: auto; } .ant-result { padding: 48px 32px; } .ant-result-success .ant-result-icon > .anticon { color: #49aa19; } .ant-result-error .ant-result-icon > .anticon { color: #a61d24; } .ant-result-info .ant-result-icon > .anticon { color: #177ddc; } .ant-result-warning .ant-result-icon > .anticon { color: #d89614; } .ant-result-image { width: 250px; height: 295px; margin: auto; } .ant-result-icon { margin-bottom: 24px; text-align: center; } .ant-result-icon > .anticon { font-size: 72px; } .ant-result-title { color: rgba(255, 255, 255, 0.85); font-size: 24px; line-height: 1.8; text-align: center; } .ant-result-subtitle { color: rgba(255, 255, 255, 0.45); font-size: 14px; line-height: 1.6; text-align: center; } .ant-result-extra { margin: 24px 0 0 0; text-align: center; } .ant-result-extra > * { margin-right: 8px; } .ant-result-extra > *:last-child { margin-right: 0; } .ant-result-content { margin-top: 24px; padding: 24px 40px; background-color: rgba(255, 255, 255, 0.04); } .ant-result-rtl { direction: rtl; } .ant-result-rtl .ant-result-extra > * { margin-right: 0; margin-left: 8px; } .ant-result-rtl .ant-result-extra > *:last-child { margin-left: 0; } .segmented-disabled-item, .segmented-disabled-item:hover, .segmented-disabled-item:focus { color: rgba(255, 255, 255, 0.3); cursor: not-allowed; } .segmented-item-selected { background-color: #333333; border-radius: 2px; box-shadow: 0 2px 8px -2px rgba(0, 0, 0, 0.05), 0 1px 4px -1px rgba(0, 0, 0, 0.07), 0 0 1px 0 rgba(0, 0, 0, 0.08); } .segmented-text-ellipsis { overflow: hidden; white-space: nowrap; text-overflow: ellipsis; word-break: keep-all; } .ant-segmented { box-sizing: border-box; margin: 0; padding: 0; color: rgba(255, 255, 255, 0.85); font-size: 14px; font-variant: tabular-nums; line-height: 1.5715; list-style: none; font-feature-settings: 'tnum'; display: inline-block; padding: 2px; color: rgba(255, 255, 255, 0.65); background-color: rgba(0, 0, 0, 0.25); border-radius: 2px; transition: all 0.3s cubic-bezier(0.645, 0.045, 0.355, 1); } .ant-segmented-group { position: relative; display: flex; align-items: stretch; justify-items: flex-start; width: 100%; } .ant-segmented.ant-segmented-block { display: flex; } .ant-segmented.ant-segmented-block .ant-segmented-item { flex: 1; min-width: 0; } .ant-segmented:not(.ant-segmented-disabled):hover, .ant-segmented:not(.ant-segmented-disabled):focus { background-color: rgba(0, 0, 0, 0.45); } .ant-segmented-item { position: relative; text-align: center; cursor: pointer; transition: color 0.3s cubic-bezier(0.645, 0.045, 0.355, 1); } .ant-segmented-item-selected { background-color: #333333; border-radius: 2px; box-shadow: 0 2px 8px -2px rgba(0, 0, 0, 0.05), 0 1px 4px -1px rgba(0, 0, 0, 0.07), 0 0 1px 0 rgba(0, 0, 0, 0.08); color: rgba(255, 255, 255, 0.85); } .ant-segmented-item:hover, .ant-segmented-item:focus { color: rgba(255, 255, 255, 0.85); } .ant-segmented-item-label { min-height: 28px; padding: 0 11px; line-height: 28px; overflow: hidden; white-space: nowrap; text-overflow: ellipsis; word-break: keep-all; } .ant-segmented-item-icon + * { margin-left: 6px; } .ant-segmented-item-input { position: absolute; top: 0; left: 0; width: 0; height: 0; opacity: 0; pointer-events: none; } .ant-segmented.ant-segmented-lg .ant-segmented-item-label { min-height: 36px; padding: 0 11px; font-size: 16px; line-height: 36px; } .ant-segmented.ant-segmented-sm .ant-segmented-item-label { min-height: 20px; padding: 0 7px; line-height: 20px; } .ant-segmented-item-disabled, .ant-segmented-item-disabled:hover, .ant-segmented-item-disabled:focus { color: rgba(255, 255, 255, 0.3); cursor: not-allowed; } .ant-segmented-thumb { background-color: #333333; border-radius: 2px; box-shadow: 0 2px 8px -2px rgba(0, 0, 0, 0.05), 0 1px 4px -1px rgba(0, 0, 0, 0.07), 0 0 1px 0 rgba(0, 0, 0, 0.08); position: absolute; top: 0; left: 0; width: 0; height: 100%; padding: 4px 0; } .ant-segmented-thumb-motion-appear-active { transition: transform 0.3s cubic-bezier(0.645, 0.045, 0.355, 1), width 0.3s cubic-bezier(0.645, 0.045, 0.355, 1); will-change: transform, width; } .ant-segmented.ant-segmented-rtl { direction: rtl; } .ant-segmented.ant-segmented-rtl .ant-segmented-item-icon { margin-right: 0; margin-left: 6px; } .ant-select-single .ant-select-selector { display: flex; } .ant-select-single .ant-select-selector .ant-select-selection-search { position: absolute; top: 0; right: 11px; bottom: 0; left: 11px; } .ant-select-single .ant-select-selector .ant-select-selection-search-input { width: 100%; } .ant-select-single .ant-select-selector .ant-select-selection-item, .ant-select-single .ant-select-selector .ant-select-selection-placeholder { padding: 0; line-height: 30px; transition: all 0.3s, visibility 0s; } .ant-select-single .ant-select-selector .ant-select-selection-item { position: relative; user-select: none; } .ant-select-single .ant-select-selector .ant-select-selection-placeholder { transition: none; pointer-events: none; } .ant-select-single .ant-select-selector::after, .ant-select-single .ant-select-selector .ant-select-selection-item::after, .ant-select-single .ant-select-selector .ant-select-selection-placeholder::after { display: inline-block; width: 0; visibility: hidden; content: '\a0'; } .ant-select-single.ant-select-show-arrow .ant-select-selection-search { right: 25px; } .ant-select-single.ant-select-show-arrow .ant-select-selection-item, .ant-select-single.ant-select-show-arrow .ant-select-selection-placeholder { padding-right: 18px; } .ant-select-single.ant-select-open .ant-select-selection-item { color: rgba(255, 255, 255, 0.3); } .ant-select-single:not(.ant-select-customize-input) .ant-select-selector { width: 100%; height: 32px; padding: 0 11px; } .ant-select-single:not(.ant-select-customize-input) .ant-select-selector .ant-select-selection-search-input { height: 30px; } .ant-select-single:not(.ant-select-customize-input) .ant-select-selector::after { line-height: 30px; } .ant-select-single.ant-select-customize-input .ant-select-selector::after { display: none; } .ant-select-single.ant-select-customize-input .ant-select-selector .ant-select-selection-search { position: static; width: 100%; } .ant-select-single.ant-select-customize-input .ant-select-selector .ant-select-selection-placeholder { position: absolute; right: 0; left: 0; padding: 0 11px; } .ant-select-single.ant-select-customize-input .ant-select-selector .ant-select-selection-placeholder::after { display: none; } .ant-select-single.ant-select-lg:not(.ant-select-customize-input) .ant-select-selector { height: 40px; } .ant-select-single.ant-select-lg:not(.ant-select-customize-input) .ant-select-selector::after, .ant-select-single.ant-select-lg:not(.ant-select-customize-input) .ant-select-selector .ant-select-selection-item, .ant-select-single.ant-select-lg:not(.ant-select-customize-input) .ant-select-selector .ant-select-selection-placeholder { line-height: 38px; } .ant-select-single.ant-select-lg:not(.ant-select-customize-input):not(.ant-select-customize-input) .ant-select-selection-search-input { height: 38px; } .ant-select-single.ant-select-sm:not(.ant-select-customize-input) .ant-select-selector { height: 24px; } .ant-select-single.ant-select-sm:not(.ant-select-customize-input) .ant-select-selector::after, .ant-select-single.ant-select-sm:not(.ant-select-customize-input) .ant-select-selector .ant-select-selection-item, .ant-select-single.ant-select-sm:not(.ant-select-customize-input) .ant-select-selector .ant-select-selection-placeholder { line-height: 22px; } .ant-select-single.ant-select-sm:not(.ant-select-customize-input):not(.ant-select-customize-input) .ant-select-selection-search-input { height: 22px; } .ant-select-single.ant-select-sm:not(.ant-select-customize-input) .ant-select-selection-search { right: 7px; left: 7px; } .ant-select-single.ant-select-sm:not(.ant-select-customize-input) .ant-select-selector { padding: 0 7px; } .ant-select-single.ant-select-sm:not(.ant-select-customize-input).ant-select-show-arrow .ant-select-selection-search { right: 28px; } .ant-select-single.ant-select-sm:not(.ant-select-customize-input).ant-select-show-arrow .ant-select-selection-item, .ant-select-single.ant-select-sm:not(.ant-select-customize-input).ant-select-show-arrow .ant-select-selection-placeholder { padding-right: 21px; } .ant-select-single.ant-select-lg:not(.ant-select-customize-input) .ant-select-selector { padding: 0 11px; } /** * Do not merge `height` & `line-height` under style with `selection` & `search`, * since chrome may update to redesign with its align logic. */ .ant-select-selection-overflow { position: relative; display: flex; flex: auto; flex-wrap: wrap; max-width: 100%; } .ant-select-selection-overflow-item { flex: none; align-self: center; max-width: 100%; } .ant-select-multiple .ant-select-selector { display: flex; flex-wrap: wrap; align-items: center; padding: 1px 4px; } .ant-select-show-search.ant-select-multiple .ant-select-selector { cursor: text; } .ant-select-disabled.ant-select-multiple .ant-select-selector { background: #141414; cursor: not-allowed; } .ant-select-multiple .ant-select-selector::after { display: inline-block; width: 0; margin: 2px 0; line-height: 24px; visibility: hidden; content: '\a0'; } .ant-select-multiple.ant-select-show-arrow .ant-select-selector, .ant-select-multiple.ant-select-allow-clear .ant-select-selector { padding-right: 24px; } .ant-select-multiple .ant-select-selection-item { position: relative; display: flex; flex: none; box-sizing: border-box; max-width: 100%; height: 24px; margin-top: 2px; margin-bottom: 2px; line-height: 22px; background: rgba(255, 255, 255, 0.08); border: 1px solid #303030; border-radius: 2px; cursor: default; transition: font-size 0.3s, line-height 0.3s, height 0.3s; user-select: none; margin-inline-end: 4px; padding-inline-start: 8px; padding-inline-end: 4px; } .ant-select-disabled.ant-select-multiple .ant-select-selection-item { color: #595959; border-color: #1f1f1f; cursor: not-allowed; } .ant-select-multiple .ant-select-selection-item-content { display: inline-block; margin-right: 4px; overflow: hidden; white-space: pre; text-overflow: ellipsis; } .ant-select-multiple .ant-select-selection-item-remove { color: inherit; font-style: normal; line-height: 0; text-align: center; text-transform: none; vertical-align: -0.125em; text-rendering: optimizelegibility; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; display: inline-flex; align-items: center; color: rgba(255, 255, 255, 0.45); font-weight: bold; font-size: 10px; line-height: inherit; cursor: pointer; } .ant-select-multiple .ant-select-selection-item-remove > * { line-height: 1; } .ant-select-multiple .ant-select-selection-item-remove svg { display: inline-block; } .ant-select-multiple .ant-select-selection-item-remove::before { display: none; } .ant-select-multiple .ant-select-selection-item-remove .ant-select-multiple .ant-select-selection-item-remove-icon { display: block; } .ant-select-multiple .ant-select-selection-item-remove > .anticon { vertical-align: middle; } .ant-select-multiple .ant-select-selection-item-remove:hover { color: rgba(255, 255, 255, 0.75); } .ant-select-multiple .ant-select-selection-overflow-item + .ant-select-selection-overflow-item .ant-select-selection-search { margin-inline-start: 0; } .ant-select-multiple .ant-select-selection-search { position: relative; max-width: 100%; margin-inline-start: 7px; } .ant-select-multiple .ant-select-selection-search-input, .ant-select-multiple .ant-select-selection-search-mirror { height: 24px; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, 'Noto Sans', sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol', 'Noto Color Emoji'; line-height: 24px; transition: all 0.3s; } .ant-select-multiple .ant-select-selection-search-input { width: 100%; min-width: 4.1px; } .ant-select-multiple .ant-select-selection-search-mirror { position: absolute; top: 0; left: 0; z-index: 999; white-space: pre; visibility: hidden; } .ant-select-multiple .ant-select-selection-placeholder { position: absolute; top: 50%; right: 11px; left: 11px; transform: translateY(-50%); transition: all 0.3s; } .ant-select-multiple.ant-select-lg .ant-select-selector::after { line-height: 32px; } .ant-select-multiple.ant-select-lg .ant-select-selection-item { height: 32px; line-height: 30px; } .ant-select-multiple.ant-select-lg .ant-select-selection-search { height: 32px; line-height: 32px; } .ant-select-multiple.ant-select-lg .ant-select-selection-search-input, .ant-select-multiple.ant-select-lg .ant-select-selection-search-mirror { height: 32px; line-height: 30px; } .ant-select-multiple.ant-select-sm .ant-select-selector::after { line-height: 16px; } .ant-select-multiple.ant-select-sm .ant-select-selection-item { height: 16px; line-height: 14px; } .ant-select-multiple.ant-select-sm .ant-select-selection-search { height: 16px; line-height: 16px; } .ant-select-multiple.ant-select-sm .ant-select-selection-search-input, .ant-select-multiple.ant-select-sm .ant-select-selection-search-mirror { height: 16px; line-height: 14px; } .ant-select-multiple.ant-select-sm .ant-select-selection-placeholder { left: 7px; } .ant-select-multiple.ant-select-sm .ant-select-selection-search { margin-inline-start: 3px; } .ant-select-disabled .ant-select-selection-item-remove { display: none; } .ant-select-status-error.ant-select:not(.ant-select-disabled):not(.ant-select-customize-input):not(.ant-pagination-size-changer) .ant-select-selector { background-color: transparent; border-color: #a61d24 !important; } .ant-select-status-error.ant-select:not(.ant-select-disabled):not(.ant-select-customize-input):not(.ant-pagination-size-changer).ant-select-open .ant-select-selector, .ant-select-status-error.ant-select:not(.ant-select-disabled):not(.ant-select-customize-input):not(.ant-pagination-size-changer).ant-select-focused .ant-select-selector { border-color: #a61d24; box-shadow: 0 0 0 2px rgba(166, 29, 36, 0.2); border-right-width: 1px; outline: 0; } .ant-select-status-warning.ant-select:not(.ant-select-disabled):not(.ant-select-customize-input):not(.ant-pagination-size-changer) .ant-select-selector { background-color: transparent; border-color: #d89614 !important; } .ant-select-status-warning.ant-select:not(.ant-select-disabled):not(.ant-select-customize-input):not(.ant-pagination-size-changer).ant-select-open .ant-select-selector, .ant-select-status-warning.ant-select:not(.ant-select-disabled):not(.ant-select-customize-input):not(.ant-pagination-size-changer).ant-select-focused .ant-select-selector { border-color: #d89614; box-shadow: 0 0 0 2px rgba(216, 150, 20, 0.2); border-right-width: 1px; outline: 0; } .ant-select-status-error.ant-select-has-feedback .ant-select-clear, .ant-select-status-warning.ant-select-has-feedback .ant-select-clear, .ant-select-status-success.ant-select-has-feedback .ant-select-clear, .ant-select-status-validating.ant-select-has-feedback .ant-select-clear { right: 32px; } .ant-select-status-error.ant-select-has-feedback .ant-select-selection-selected-value, .ant-select-status-warning.ant-select-has-feedback .ant-select-selection-selected-value, .ant-select-status-success.ant-select-has-feedback .ant-select-selection-selected-value, .ant-select-status-validating.ant-select-has-feedback .ant-select-selection-selected-value { padding-right: 42px; } /* Reset search input style */ .ant-select { box-sizing: border-box; margin: 0; padding: 0; color: rgba(255, 255, 255, 0.85); font-size: 14px; font-variant: tabular-nums; line-height: 1.5715; list-style: none; font-feature-settings: 'tnum'; position: relative; display: inline-block; cursor: pointer; } .ant-select:not(.ant-select-customize-input) .ant-select-selector { position: relative; background-color: transparent; border: 1px solid #434343; border-radius: 2px; transition: all 0.3s cubic-bezier(0.645, 0.045, 0.355, 1); } .ant-select:not(.ant-select-customize-input) .ant-select-selector input { cursor: pointer; } .ant-select-show-search.ant-select:not(.ant-select-customize-input) .ant-select-selector { cursor: text; } .ant-select-show-search.ant-select:not(.ant-select-customize-input) .ant-select-selector input { cursor: auto; } .ant-select-focused:not(.ant-select-disabled).ant-select:not(.ant-select-customize-input) .ant-select-selector { border-color: #177ddc; box-shadow: 0 0 0 2px rgba(23, 125, 220, 0.2); border-right-width: 1px; outline: 0; } .ant-input-rtl .ant-select-focused:not(.ant-select-disabled).ant-select:not(.ant-select-customize-input) .ant-select-selector { border-right-width: 0; border-left-width: 1px !important; } .ant-select-disabled.ant-select:not(.ant-select-customize-input) .ant-select-selector { color: rgba(255, 255, 255, 0.3); background: rgba(255, 255, 255, 0.08); cursor: not-allowed; } .ant-select-multiple.ant-select-disabled.ant-select:not(.ant-select-customize-input) .ant-select-selector { background: #141414; } .ant-select-disabled.ant-select:not(.ant-select-customize-input) .ant-select-selector input { cursor: not-allowed; } .ant-select:not(.ant-select-customize-input) .ant-select-selector .ant-select-selection-search-input { margin: 0; padding: 0; background: transparent; border: none; outline: none; appearance: none; } .ant-select:not(.ant-select-customize-input) .ant-select-selector .ant-select-selection-search-input::-webkit-search-cancel-button { display: none; /* stylelint-disable-next-line property-no-vendor-prefix */ -webkit-appearance: none; } .ant-select:not(.ant-select-disabled):hover .ant-select-selector { border-color: #165996; border-right-width: 1px; } .ant-input-rtl .ant-select:not(.ant-select-disabled):hover .ant-select-selector { border-right-width: 0; border-left-width: 1px !important; } .ant-select-selection-item { flex: 1; overflow: hidden; font-weight: normal; white-space: nowrap; text-overflow: ellipsis; } @media all and (-ms-high-contrast: none) { .ant-select-selection-item *::-ms-backdrop, .ant-select-selection-item { flex: auto; } } .ant-select-selection-placeholder { flex: 1; overflow: hidden; color: rgba(255, 255, 255, 0.3); white-space: nowrap; text-overflow: ellipsis; pointer-events: none; } @media all and (-ms-high-contrast: none) { .ant-select-selection-placeholder *::-ms-backdrop, .ant-select-selection-placeholder { flex: auto; } } .ant-select-arrow { display: inline-flex; color: inherit; font-style: normal; line-height: 0; text-transform: none; vertical-align: -0.125em; text-rendering: optimizelegibility; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; position: absolute; top: 50%; right: 11px; display: flex; align-items: center; height: 12px; margin-top: -6px; color: rgba(255, 255, 255, 0.3); font-size: 12px; line-height: 1; text-align: center; pointer-events: none; } .ant-select-arrow > * { line-height: 1; } .ant-select-arrow svg { display: inline-block; } .ant-select-arrow::before { display: none; } .ant-select-arrow .ant-select-arrow-icon { display: block; } .ant-select-arrow .anticon { vertical-align: top; transition: transform 0.3s; } .ant-select-arrow .anticon > svg { vertical-align: top; } .ant-select-arrow .anticon:not(.ant-select-suffix) { pointer-events: auto; } .ant-select-disabled .ant-select-arrow { cursor: not-allowed; } .ant-select-arrow > *:not(:last-child) { margin-inline-end: 8px; } .ant-select-clear { position: absolute; top: 50%; right: 11px; z-index: 1; display: inline-block; width: 12px; height: 12px; margin-top: -6px; color: rgba(255, 255, 255, 0.3); font-size: 12px; font-style: normal; line-height: 1; text-align: center; text-transform: none; background: #141414; cursor: pointer; opacity: 0; transition: color 0.3s ease, opacity 0.15s ease; text-rendering: auto; } .ant-select-clear::before { display: block; } .ant-select-clear:hover { color: rgba(255, 255, 255, 0.45); } .ant-select:hover .ant-select-clear { opacity: 1; } .ant-select-dropdown { margin: 0; padding: 0; color: rgba(255, 255, 255, 0.85); font-variant: tabular-nums; line-height: 1.5715; list-style: none; font-feature-settings: 'tnum'; position: absolute; top: -9999px; left: -9999px; z-index: 1050; box-sizing: border-box; padding: 4px 0; overflow: hidden; font-size: 14px; font-variant: initial; background-color: #1f1f1f; border-radius: 2px; outline: none; box-shadow: 0 3px 6px -4px rgba(0, 0, 0, 0.48), 0 6px 16px 0 rgba(0, 0, 0, 0.32), 0 9px 28px 8px rgba(0, 0, 0, 0.2); } .ant-select-dropdown.ant-slide-up-enter.ant-slide-up-enter-active.ant-select-dropdown-placement-bottomLeft, .ant-select-dropdown.ant-slide-up-appear.ant-slide-up-appear-active.ant-select-dropdown-placement-bottomLeft { animation-name: antSlideUpIn; } .ant-select-dropdown.ant-slide-up-enter.ant-slide-up-enter-active.ant-select-dropdown-placement-topLeft, .ant-select-dropdown.ant-slide-up-appear.ant-slide-up-appear-active.ant-select-dropdown-placement-topLeft { animation-name: antSlideDownIn; } .ant-select-dropdown.ant-slide-up-leave.ant-slide-up-leave-active.ant-select-dropdown-placement-bottomLeft { animation-name: antSlideUpOut; } .ant-select-dropdown.ant-slide-up-leave.ant-slide-up-leave-active.ant-select-dropdown-placement-topLeft { animation-name: antSlideDownOut; } .ant-select-dropdown-hidden { display: none; } .ant-select-dropdown-empty { color: rgba(255, 255, 255, 0.3); } .ant-select-item-empty { position: relative; display: block; min-height: 32px; padding: 5px 12px; color: rgba(255, 255, 255, 0.85); font-weight: normal; font-size: 14px; line-height: 22px; color: rgba(255, 255, 255, 0.3); } .ant-select-item { position: relative; display: block; min-height: 32px; padding: 5px 12px; color: rgba(255, 255, 255, 0.85); font-weight: normal; font-size: 14px; line-height: 22px; cursor: pointer; transition: background 0.3s ease; } .ant-select-item-group { color: rgba(255, 255, 255, 0.45); font-size: 12px; cursor: default; } .ant-select-item-option { display: flex; } .ant-select-item-option-content { flex: auto; overflow: hidden; white-space: nowrap; text-overflow: ellipsis; } .ant-select-item-option-state { flex: none; } .ant-select-item-option-active:not(.ant-select-item-option-disabled) { background-color: rgba(255, 255, 255, 0.08); } .ant-select-item-option-selected:not(.ant-select-item-option-disabled) { color: rgba(255, 255, 255, 0.85); font-weight: 600; background-color: #111b26; } .ant-select-item-option-selected:not(.ant-select-item-option-disabled) .ant-select-item-option-state { color: #177ddc; } .ant-select-item-option-disabled { color: rgba(255, 255, 255, 0.3); cursor: not-allowed; } .ant-select-item-option-disabled.ant-select-item-option-selected { background-color: #141414; } .ant-select-item-option-grouped { padding-left: 24px; } .ant-select-lg { font-size: 16px; } .ant-select-borderless .ant-select-selector { background-color: transparent !important; border-color: transparent !important; box-shadow: none !important; } .ant-select.ant-select-in-form-item { width: 100%; } .ant-select-compact-item:not(.ant-select-compact-last-item) { margin-right: -1px; } .ant-select-compact-item:not(.ant-select-compact-last-item).ant-select-compact-item-rtl { margin-right: 0; margin-left: -1px; } .ant-select-compact-item:hover > *, .ant-select-compact-item:focus > *, .ant-select-compact-item:active > * { z-index: 2; } .ant-select-compact-item.ant-select-focused > * { z-index: 2; } .ant-select-compact-item[disabled] > * { z-index: 0; } .ant-select-compact-item:not(.ant-select-compact-first-item):not(.ant-select-compact-last-item).ant-select > .ant-select-selector { border-radius: 0; } .ant-select-compact-item.ant-select-compact-first-item.ant-select:not(.ant-select-compact-last-item):not(.ant-select-compact-item-rtl) > .ant-select-selector { border-top-right-radius: 0; border-bottom-right-radius: 0; } .ant-select-compact-item.ant-select-compact-last-item.ant-select:not(.ant-select-compact-first-item):not(.ant-select-compact-item-rtl) > .ant-select-selector { border-top-left-radius: 0; border-bottom-left-radius: 0; } .ant-select-compact-item.ant-select.ant-select-compact-first-item.ant-select-compact-item-rtl:not(.ant-select-compact-last-item) > .ant-select-selector { border-top-left-radius: 0; border-bottom-left-radius: 0; } .ant-select-compact-item.ant-select.ant-select-compact-last-item.ant-select-compact-item-rtl:not(.ant-select-compact-first-item) > .ant-select-selector { border-top-right-radius: 0; border-bottom-right-radius: 0; } .ant-select-rtl { direction: rtl; } .ant-select-rtl .ant-select-arrow { right: initial; left: 11px; } .ant-select-rtl .ant-select-clear { right: initial; left: 11px; } .ant-select-dropdown-rtl { direction: rtl; } .ant-select-dropdown-rtl .ant-select-item-option-grouped { padding-right: 24px; padding-left: 12px; } .ant-select-rtl.ant-select-multiple.ant-select-show-arrow .ant-select-selector, .ant-select-rtl.ant-select-multiple.ant-select-allow-clear .ant-select-selector { padding-right: 4px; padding-left: 24px; } .ant-select-rtl.ant-select-multiple .ant-select-selection-item { text-align: right; } .ant-select-rtl.ant-select-multiple .ant-select-selection-item-content { margin-right: 0; margin-left: 4px; text-align: right; } .ant-select-rtl.ant-select-multiple .ant-select-selection-search-mirror { right: 0; left: auto; } .ant-select-rtl.ant-select-multiple .ant-select-selection-placeholder { right: 11px; left: auto; } .ant-select-rtl.ant-select-multiple.ant-select-sm .ant-select-selection-placeholder { right: 7px; } .ant-select-rtl.ant-select-single .ant-select-selector .ant-select-selection-item, .ant-select-rtl.ant-select-single .ant-select-selector .ant-select-selection-placeholder { right: 0; left: 9px; text-align: right; } .ant-select-rtl.ant-select-single.ant-select-show-arrow .ant-select-selection-search { right: 11px; left: 25px; } .ant-select-rtl.ant-select-single.ant-select-show-arrow .ant-select-selection-item, .ant-select-rtl.ant-select-single.ant-select-show-arrow .ant-select-selection-placeholder { padding-right: 0; padding-left: 18px; } .ant-select-rtl.ant-select-single.ant-select-sm:not(.ant-select-customize-input).ant-select-show-arrow .ant-select-selection-search { right: 6px; } .ant-select-rtl.ant-select-single.ant-select-sm:not(.ant-select-customize-input).ant-select-show-arrow .ant-select-selection-item, .ant-select-rtl.ant-select-single.ant-select-sm:not(.ant-select-customize-input).ant-select-show-arrow .ant-select-selection-placeholder { padding-right: 0; padding-left: 21px; } .ant-skeleton { display: table; width: 100%; } .ant-skeleton-header { display: table-cell; padding-right: 16px; vertical-align: top; } .ant-skeleton-header .ant-skeleton-avatar { display: inline-block; vertical-align: top; background: rgba(190, 190, 190, 0.2); width: 32px; height: 32px; line-height: 32px; } .ant-skeleton-header .ant-skeleton-avatar.ant-skeleton-avatar-circle { border-radius: 50%; } .ant-skeleton-header .ant-skeleton-avatar-lg { width: 40px; height: 40px; line-height: 40px; } .ant-skeleton-header .ant-skeleton-avatar-lg.ant-skeleton-avatar-circle { border-radius: 50%; } .ant-skeleton-header .ant-skeleton-avatar-sm { width: 24px; height: 24px; line-height: 24px; } .ant-skeleton-header .ant-skeleton-avatar-sm.ant-skeleton-avatar-circle { border-radius: 50%; } .ant-skeleton-content { display: table-cell; width: 100%; vertical-align: top; } .ant-skeleton-content .ant-skeleton-title { width: 100%; height: 16px; background: rgba(190, 190, 190, 0.2); border-radius: 2px; } .ant-skeleton-content .ant-skeleton-title + .ant-skeleton-paragraph { margin-top: 24px; } .ant-skeleton-content .ant-skeleton-paragraph { padding: 0; } .ant-skeleton-content .ant-skeleton-paragraph > li { width: 100%; height: 16px; list-style: none; background: rgba(190, 190, 190, 0.2); border-radius: 2px; } .ant-skeleton-content .ant-skeleton-paragraph > li:last-child:not(:first-child):not(:nth-child(2)) { width: 61%; } .ant-skeleton-content .ant-skeleton-paragraph > li + li { margin-top: 16px; } .ant-skeleton-with-avatar .ant-skeleton-content .ant-skeleton-title { margin-top: 12px; } .ant-skeleton-with-avatar .ant-skeleton-content .ant-skeleton-title + .ant-skeleton-paragraph { margin-top: 28px; } .ant-skeleton-round .ant-skeleton-content .ant-skeleton-title, .ant-skeleton-round .ant-skeleton-content .ant-skeleton-paragraph > li { border-radius: 100px; } .ant-skeleton-active .ant-skeleton-title, .ant-skeleton-active .ant-skeleton-paragraph > li, .ant-skeleton-active .ant-skeleton-avatar, .ant-skeleton-active .ant-skeleton-button, .ant-skeleton-active .ant-skeleton-input, .ant-skeleton-active .ant-skeleton-image { position: relative; /* stylelint-disable-next-line property-no-vendor-prefix,value-no-vendor-prefix */ z-index: 0; overflow: hidden; background: transparent; } .ant-skeleton-active .ant-skeleton-title::after, .ant-skeleton-active .ant-skeleton-paragraph > li::after, .ant-skeleton-active .ant-skeleton-avatar::after, .ant-skeleton-active .ant-skeleton-button::after, .ant-skeleton-active .ant-skeleton-input::after, .ant-skeleton-active .ant-skeleton-image::after { position: absolute; top: 0; right: -150%; bottom: 0; left: -150%; background: linear-gradient(90deg, rgba(190, 190, 190, 0.2) 25%, rgba(255, 255, 255, 0.16) 37%, rgba(190, 190, 190, 0.2) 63%); animation: ant-skeleton-loading 1.4s ease infinite; content: ''; } .ant-skeleton.ant-skeleton-block { width: 100%; } .ant-skeleton.ant-skeleton-block .ant-skeleton-button { width: 100%; } .ant-skeleton.ant-skeleton-block .ant-skeleton-input { width: 100%; } .ant-skeleton-element { display: inline-block; width: auto; } .ant-skeleton-element .ant-skeleton-button { display: inline-block; vertical-align: top; background: rgba(190, 190, 190, 0.2); border-radius: 2px; width: 64px; min-width: 64px; height: 32px; line-height: 32px; } .ant-skeleton-element .ant-skeleton-button.ant-skeleton-button-square { width: 32px; min-width: 32px; } .ant-skeleton-element .ant-skeleton-button.ant-skeleton-button-circle { width: 32px; min-width: 32px; border-radius: 50%; } .ant-skeleton-element .ant-skeleton-button.ant-skeleton-button-round { border-radius: 32px; } .ant-skeleton-element .ant-skeleton-button-lg { width: 80px; min-width: 80px; height: 40px; line-height: 40px; } .ant-skeleton-element .ant-skeleton-button-lg.ant-skeleton-button-square { width: 40px; min-width: 40px; } .ant-skeleton-element .ant-skeleton-button-lg.ant-skeleton-button-circle { width: 40px; min-width: 40px; border-radius: 50%; } .ant-skeleton-element .ant-skeleton-button-lg.ant-skeleton-button-round { border-radius: 40px; } .ant-skeleton-element .ant-skeleton-button-sm { width: 48px; min-width: 48px; height: 24px; line-height: 24px; } .ant-skeleton-element .ant-skeleton-button-sm.ant-skeleton-button-square { width: 24px; min-width: 24px; } .ant-skeleton-element .ant-skeleton-button-sm.ant-skeleton-button-circle { width: 24px; min-width: 24px; border-radius: 50%; } .ant-skeleton-element .ant-skeleton-button-sm.ant-skeleton-button-round { border-radius: 24px; } .ant-skeleton-element .ant-skeleton-avatar { display: inline-block; vertical-align: top; background: rgba(190, 190, 190, 0.2); width: 32px; height: 32px; line-height: 32px; } .ant-skeleton-element .ant-skeleton-avatar.ant-skeleton-avatar-circle { border-radius: 50%; } .ant-skeleton-element .ant-skeleton-avatar-lg { width: 40px; height: 40px; line-height: 40px; } .ant-skeleton-element .ant-skeleton-avatar-lg.ant-skeleton-avatar-circle { border-radius: 50%; } .ant-skeleton-element .ant-skeleton-avatar-sm { width: 24px; height: 24px; line-height: 24px; } .ant-skeleton-element .ant-skeleton-avatar-sm.ant-skeleton-avatar-circle { border-radius: 50%; } .ant-skeleton-element .ant-skeleton-input { display: inline-block; vertical-align: top; background: rgba(190, 190, 190, 0.2); width: 160px; min-width: 160px; height: 32px; line-height: 32px; } .ant-skeleton-element .ant-skeleton-input-lg { width: 200px; min-width: 200px; height: 40px; line-height: 40px; } .ant-skeleton-element .ant-skeleton-input-sm { width: 120px; min-width: 120px; height: 24px; line-height: 24px; } .ant-skeleton-element .ant-skeleton-image { display: flex; align-items: center; justify-content: center; vertical-align: top; background: rgba(190, 190, 190, 0.2); width: 96px; height: 96px; line-height: 96px; } .ant-skeleton-element .ant-skeleton-image.ant-skeleton-image-circle { border-radius: 50%; } .ant-skeleton-element .ant-skeleton-image-path { fill: #bfbfbf; } .ant-skeleton-element .ant-skeleton-image-svg { width: 48px; height: 48px; line-height: 48px; max-width: 192px; max-height: 192px; } .ant-skeleton-element .ant-skeleton-image-svg.ant-skeleton-image-circle { border-radius: 50%; } @keyframes ant-skeleton-loading { 0% { transform: translateX(-37.5%); } 100% { transform: translateX(37.5%); } } .ant-skeleton-rtl { direction: rtl; } .ant-skeleton-rtl .ant-skeleton-header { padding-right: 0; padding-left: 16px; } .ant-skeleton-rtl.ant-skeleton.ant-skeleton-active .ant-skeleton-content .ant-skeleton-title, .ant-skeleton-rtl.ant-skeleton.ant-skeleton-active .ant-skeleton-content .ant-skeleton-paragraph > li { animation-name: ant-skeleton-loading-rtl; } .ant-skeleton-rtl.ant-skeleton.ant-skeleton-active .ant-skeleton-avatar { animation-name: ant-skeleton-loading-rtl; } @keyframes ant-skeleton-loading-rtl { 0% { background-position: 0% 50%; } 100% { background-position: 100% 50%; } } .ant-slider { box-sizing: border-box; margin: 0; padding: 0; color: rgba(255, 255, 255, 0.85); font-size: 14px; font-variant: tabular-nums; line-height: 1.5715; list-style: none; font-feature-settings: 'tnum'; position: relative; height: 12px; margin: 10px 6px 10px; padding: 4px 0; cursor: pointer; touch-action: none; } .ant-slider-vertical { width: 12px; height: 100%; margin: 6px 10px; padding: 0 4px; } .ant-slider-vertical .ant-slider-rail { width: 4px; height: 100%; } .ant-slider-vertical .ant-slider-track { width: 4px; } .ant-slider-vertical .ant-slider-handle { margin-top: -6px; margin-left: -5px; } .ant-slider-vertical .ant-slider-mark { top: 0; left: 12px; width: 18px; height: 100%; } .ant-slider-vertical .ant-slider-mark-text { left: 4px; white-space: nowrap; } .ant-slider-vertical .ant-slider-step { width: 4px; height: 100%; } .ant-slider-vertical .ant-slider-dot { top: auto; margin-left: -2px; } .ant-slider-tooltip .ant-tooltip-inner { min-width: unset; } .ant-slider-rtl.ant-slider-vertical .ant-slider-handle { margin-right: -5px; margin-left: 0; } .ant-slider-rtl.ant-slider-vertical .ant-slider-mark { right: 12px; left: auto; } .ant-slider-rtl.ant-slider-vertical .ant-slider-mark-text { right: 4px; left: auto; } .ant-slider-rtl.ant-slider-vertical .ant-slider-dot { right: 2px; left: auto; } .ant-slider-with-marks { margin-bottom: 28px; } .ant-slider-rail { position: absolute; width: 100%; height: 4px; background-color: #262626; border-radius: 2px; transition: background-color 0.3s; } .ant-slider-track { position: absolute; height: 4px; background-color: #153450; border-radius: 2px; transition: background-color 0.3s; } .ant-slider-handle { position: absolute; width: 14px; height: 14px; margin-top: -5px; background-color: #141414; border: solid 2px #153450; border-radius: 50%; box-shadow: 0; cursor: pointer; transition: border-color 0.3s, box-shadow 0.6s, transform 0.3s cubic-bezier(0.18, 0.89, 0.32, 1.28); } .ant-slider-handle-dragging { z-index: 1; } .ant-slider-handle:focus { border-color: #4697e3; outline: none; box-shadow: 0 0 0 5px rgba(23, 125, 220, 0.12); } .ant-slider-handle.ant-tooltip-open { border-color: #177ddc; } .ant-slider-handle::after { position: absolute; top: -6px; right: -6px; bottom: -6px; left: -6px; content: ''; } .ant-slider:hover .ant-slider-rail { background-color: #434343; } .ant-slider:hover .ant-slider-track { background-color: #16436e; } .ant-slider:hover .ant-slider-handle:not(.ant-tooltip-open) { border-color: #16436e; } .ant-slider-mark { position: absolute; top: 14px; left: 0; width: 100%; font-size: 14px; } .ant-slider-mark-text { position: absolute; display: inline-block; color: rgba(255, 255, 255, 0.45); text-align: center; word-break: keep-all; cursor: pointer; user-select: none; } .ant-slider-mark-text-active { color: rgba(255, 255, 255, 0.85); } .ant-slider-step { position: absolute; width: 100%; height: 4px; background: transparent; pointer-events: none; } .ant-slider-dot { position: absolute; top: -2px; width: 8px; height: 8px; background-color: #141414; border: 2px solid #303030; border-radius: 50%; cursor: pointer; } .ant-slider-dot-active { border-color: #16436e; } .ant-slider-disabled { cursor: not-allowed; } .ant-slider-disabled .ant-slider-rail { background-color: #262626 !important; } .ant-slider-disabled .ant-slider-track { background-color: rgba(255, 255, 255, 0.3) !important; } .ant-slider-disabled .ant-slider-handle, .ant-slider-disabled .ant-slider-dot { background-color: #141414; border-color: rgba(255, 255, 255, 0.3) !important; box-shadow: none; cursor: not-allowed; } .ant-slider-disabled .ant-slider-mark-text, .ant-slider-disabled .ant-slider-dot { cursor: not-allowed !important; } .ant-slider-rtl { direction: rtl; } .ant-slider-rtl .ant-slider-mark { right: 0; left: auto; } .ant-space { display: inline-flex; } .ant-space-vertical { flex-direction: column; } .ant-space-align-center { align-items: center; } .ant-space-align-start { align-items: flex-start; } .ant-space-align-end { align-items: flex-end; } .ant-space-align-baseline { align-items: baseline; } .ant-space-item:empty { display: none; } .ant-space-compact { display: inline-flex; } .ant-space-compact-block { display: flex; width: 100%; } .ant-space-compact-vertical { flex-direction: column; } .ant-space-rtl { direction: rtl; } .ant-space-compact-rtl { direction: rtl; } .ant-spin { box-sizing: border-box; margin: 0; padding: 0; color: rgba(255, 255, 255, 0.85); font-size: 14px; font-variant: tabular-nums; line-height: 1.5715; list-style: none; font-feature-settings: 'tnum'; position: absolute; display: none; color: #177ddc; font-size: 0; text-align: center; vertical-align: middle; opacity: 0; transition: transform 0.3s cubic-bezier(0.78, 0.14, 0.15, 0.86); } .ant-spin-spinning { position: static; display: inline-block; opacity: 1; } .ant-spin-nested-loading { position: relative; } .ant-spin-nested-loading > div > .ant-spin { position: absolute; top: 0; left: 0; z-index: 4; display: block; width: 100%; height: 100%; max-height: 400px; } .ant-spin-nested-loading > div > .ant-spin .ant-spin-dot { position: absolute; top: 50%; left: 50%; margin: -10px; } .ant-spin-nested-loading > div > .ant-spin .ant-spin-text { position: absolute; top: 50%; width: 100%; padding-top: 5px; font-size: 14px; text-shadow: 0 1px 2px #141414; } .ant-spin-nested-loading > div > .ant-spin.ant-spin-show-text .ant-spin-dot { margin-top: -20px; } .ant-spin-nested-loading > div > .ant-spin-sm .ant-spin-dot { margin: -7px; } .ant-spin-nested-loading > div > .ant-spin-sm .ant-spin-text { padding-top: 2px; } .ant-spin-nested-loading > div > .ant-spin-sm.ant-spin-show-text .ant-spin-dot { margin-top: -17px; } .ant-spin-nested-loading > div > .ant-spin-lg .ant-spin-dot { margin: -16px; } .ant-spin-nested-loading > div > .ant-spin-lg .ant-spin-text { padding-top: 11px; } .ant-spin-nested-loading > div > .ant-spin-lg.ant-spin-show-text .ant-spin-dot { margin-top: -26px; } .ant-spin-container { position: relative; transition: opacity 0.3s; } .ant-spin-container::after { position: absolute; top: 0; right: 0; bottom: 0; left: 0; z-index: 10; display: none \9; width: 100%; height: 100%; background: #141414; opacity: 0; transition: all 0.3s; content: ''; pointer-events: none; } .ant-spin-blur { clear: both; opacity: 0.5; user-select: none; pointer-events: none; } .ant-spin-blur::after { opacity: 0.4; pointer-events: auto; } .ant-spin-tip { color: rgba(255, 255, 255, 0.45); } .ant-spin-dot { position: relative; display: inline-block; font-size: 20px; width: 1em; height: 1em; } .ant-spin-dot-item { position: absolute; display: block; width: 9px; height: 9px; background-color: #177ddc; border-radius: 100%; transform: scale(0.75); transform-origin: 50% 50%; opacity: 0.3; animation: antSpinMove 1s infinite linear alternate; } .ant-spin-dot-item:nth-child(1) { top: 0; left: 0; } .ant-spin-dot-item:nth-child(2) { top: 0; right: 0; animation-delay: 0.4s; } .ant-spin-dot-item:nth-child(3) { right: 0; bottom: 0; animation-delay: 0.8s; } .ant-spin-dot-item:nth-child(4) { bottom: 0; left: 0; animation-delay: 1.2s; } .ant-spin-dot-spin { transform: rotate(0deg); animation: antRotate 1.2s infinite linear; } .ant-spin-sm .ant-spin-dot { font-size: 14px; } .ant-spin-sm .ant-spin-dot i { width: 6px; height: 6px; } .ant-spin-lg .ant-spin-dot { font-size: 32px; } .ant-spin-lg .ant-spin-dot i { width: 14px; height: 14px; } .ant-spin.ant-spin-show-text .ant-spin-text { display: block; } @media all and (-ms-high-contrast: none), (-ms-high-contrast: active) { /* IE10+ */ .ant-spin-blur { background: #141414; opacity: 0.5; } } @keyframes antSpinMove { to { opacity: 1; } } @keyframes antRotate { to { transform: rotate(360deg); } } .ant-spin-rtl { direction: rtl; } .ant-spin-rtl .ant-spin-dot-spin { transform: rotate(-45deg); animation-name: antRotateRtl; } @keyframes antRotateRtl { to { transform: rotate(-405deg); } } .ant-statistic { box-sizing: border-box; margin: 0; padding: 0; color: rgba(255, 255, 255, 0.85); font-size: 14px; font-variant: tabular-nums; line-height: 1.5715; list-style: none; font-feature-settings: 'tnum'; } .ant-statistic-title { margin-bottom: 4px; color: rgba(255, 255, 255, 0.45); font-size: 14px; } .ant-statistic-skeleton { padding-top: 16px; } .ant-statistic-content { color: rgba(255, 255, 255, 0.85); font-size: 24px; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, 'Noto Sans', sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol', 'Noto Color Emoji'; } .ant-statistic-content-value { display: inline-block; direction: ltr; } .ant-statistic-content-prefix, .ant-statistic-content-suffix { display: inline-block; } .ant-statistic-content-prefix { margin-right: 4px; } .ant-statistic-content-suffix { margin-left: 4px; } .ant-statistic-rtl { direction: rtl; } .ant-statistic-rtl .ant-statistic-content-prefix { margin-right: 0; margin-left: 4px; } .ant-statistic-rtl .ant-statistic-content-suffix { margin-right: 4px; margin-left: 0; } .ant-steps { box-sizing: border-box; margin: 0; padding: 0; color: rgba(255, 255, 255, 0.85); font-size: 14px; font-variant: tabular-nums; line-height: 1.5715; list-style: none; font-feature-settings: 'tnum'; display: flex; width: 100%; font-size: 0; text-align: initial; } .ant-steps-item { position: relative; display: inline-block; flex: 1; overflow: hidden; vertical-align: top; } .ant-steps-item-container { outline: none; } .ant-steps-item:last-child { flex: none; } .ant-steps-item:last-child > .ant-steps-item-container > .ant-steps-item-tail, .ant-steps-item:last-child > .ant-steps-item-container > .ant-steps-item-content > .ant-steps-item-title::after { display: none; } .ant-steps-item-icon, .ant-steps-item-content { display: inline-block; vertical-align: top; } .ant-steps-item-icon { width: 32px; height: 32px; margin: 0 8px 0 0; font-size: 16px; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, 'Noto Sans', sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol', 'Noto Color Emoji'; line-height: 32px; text-align: center; border: 1px solid rgba(255, 255, 255, 0.3); border-radius: 32px; transition: background-color 0.3s, border-color 0.3s; } .ant-steps-item-icon .ant-steps-icon { position: relative; top: -0.5px; color: #177ddc; line-height: 1; } .ant-steps-item-tail { position: absolute; top: 12px; left: 0; width: 100%; padding: 0 10px; } .ant-steps-item-tail::after { display: inline-block; width: 100%; height: 1px; background: #303030; border-radius: 1px; transition: background 0.3s; content: ''; } .ant-steps-item-title { position: relative; display: inline-block; padding-right: 16px; color: rgba(255, 255, 255, 0.85); font-size: 16px; line-height: 32px; } .ant-steps-item-title::after { position: absolute; top: 16px; left: 100%; display: block; width: 9999px; height: 1px; background: #303030; content: ''; } .ant-steps-item-subtitle { display: inline; margin-left: 8px; color: rgba(255, 255, 255, 0.45); font-weight: normal; font-size: 14px; } .ant-steps-item-description { color: rgba(255, 255, 255, 0.45); font-size: 14px; } .ant-steps-item-wait .ant-steps-item-icon { background-color: transparent; border-color: rgba(255, 255, 255, 0.3); } .ant-steps-item-wait .ant-steps-item-icon > .ant-steps-icon { color: rgba(255, 255, 255, 0.3); } .ant-steps-item-wait .ant-steps-item-icon > .ant-steps-icon .ant-steps-icon-dot { background: rgba(255, 255, 255, 0.3); } .ant-steps-item-wait > .ant-steps-item-container > .ant-steps-item-content > .ant-steps-item-title { color: rgba(255, 255, 255, 0.45); } .ant-steps-item-wait > .ant-steps-item-container > .ant-steps-item-content > .ant-steps-item-title::after { background-color: #303030; } .ant-steps-item-wait > .ant-steps-item-container > .ant-steps-item-content > .ant-steps-item-description { color: rgba(255, 255, 255, 0.45); } .ant-steps-item-wait > .ant-steps-item-container > .ant-steps-item-tail::after { background-color: #303030; } .ant-steps-item-process .ant-steps-item-icon { background-color: transparent; border-color: #177ddc; } .ant-steps-item-process .ant-steps-item-icon > .ant-steps-icon { color: #177ddc; } .ant-steps-item-process .ant-steps-item-icon > .ant-steps-icon .ant-steps-icon-dot { background: #177ddc; } .ant-steps-item-process > .ant-steps-item-container > .ant-steps-item-content > .ant-steps-item-title { color: rgba(255, 255, 255, 0.85); } .ant-steps-item-process > .ant-steps-item-container > .ant-steps-item-content > .ant-steps-item-title::after { background-color: #303030; } .ant-steps-item-process > .ant-steps-item-container > .ant-steps-item-content > .ant-steps-item-description { color: rgba(255, 255, 255, 0.85); } .ant-steps-item-process > .ant-steps-item-container > .ant-steps-item-tail::after { background-color: #303030; } .ant-steps-item-process > .ant-steps-item-container > .ant-steps-item-icon { background: #177ddc; } .ant-steps-item-process > .ant-steps-item-container > .ant-steps-item-icon .ant-steps-icon { color: #fff; } .ant-steps-item-process > .ant-steps-item-container > .ant-steps-item-title { font-weight: 500; } .ant-steps-item-finish .ant-steps-item-icon { background-color: transparent; border-color: #177ddc; } .ant-steps-item-finish .ant-steps-item-icon > .ant-steps-icon { color: #177ddc; } .ant-steps-item-finish .ant-steps-item-icon > .ant-steps-icon .ant-steps-icon-dot { background: #177ddc; } .ant-steps-item-finish > .ant-steps-item-container > .ant-steps-item-content > .ant-steps-item-title { color: rgba(255, 255, 255, 0.85); } .ant-steps-item-finish > .ant-steps-item-container > .ant-steps-item-content > .ant-steps-item-title::after { background-color: #177ddc; } .ant-steps-item-finish > .ant-steps-item-container > .ant-steps-item-content > .ant-steps-item-description { color: rgba(255, 255, 255, 0.45); } .ant-steps-item-finish > .ant-steps-item-container > .ant-steps-item-tail::after { background-color: #177ddc; } .ant-steps-item-error .ant-steps-item-icon { background-color: transparent; border-color: #a61d24; } .ant-steps-item-error .ant-steps-item-icon > .ant-steps-icon { color: #a61d24; } .ant-steps-item-error .ant-steps-item-icon > .ant-steps-icon .ant-steps-icon-dot { background: #a61d24; } .ant-steps-item-error > .ant-steps-item-container > .ant-steps-item-content > .ant-steps-item-title { color: #a61d24; } .ant-steps-item-error > .ant-steps-item-container > .ant-steps-item-content > .ant-steps-item-title::after { background-color: #303030; } .ant-steps-item-error > .ant-steps-item-container > .ant-steps-item-content > .ant-steps-item-description { color: #a61d24; } .ant-steps-item-error > .ant-steps-item-container > .ant-steps-item-tail::after { background-color: #303030; } .ant-steps-item.ant-steps-next-error .ant-steps-item-title::after { background: #a61d24; } .ant-steps-item-disabled { cursor: not-allowed; } .ant-steps .ant-steps-item:not(.ant-steps-item-active) > .ant-steps-item-container[role='button'] { cursor: pointer; } .ant-steps .ant-steps-item:not(.ant-steps-item-active) > .ant-steps-item-container[role='button'] .ant-steps-item-title, .ant-steps .ant-steps-item:not(.ant-steps-item-active) > .ant-steps-item-container[role='button'] .ant-steps-item-subtitle, .ant-steps .ant-steps-item:not(.ant-steps-item-active) > .ant-steps-item-container[role='button'] .ant-steps-item-description, .ant-steps .ant-steps-item:not(.ant-steps-item-active) > .ant-steps-item-container[role='button'] .ant-steps-item-icon .ant-steps-icon { transition: color 0.3s; } .ant-steps .ant-steps-item:not(.ant-steps-item-active) > .ant-steps-item-container[role='button']:hover .ant-steps-item-title, .ant-steps .ant-steps-item:not(.ant-steps-item-active) > .ant-steps-item-container[role='button']:hover .ant-steps-item-subtitle, .ant-steps .ant-steps-item:not(.ant-steps-item-active) > .ant-steps-item-container[role='button']:hover .ant-steps-item-description { color: #177ddc; } .ant-steps .ant-steps-item:not(.ant-steps-item-active):not(.ant-steps-item-process) > .ant-steps-item-container[role='button']:hover .ant-steps-item-icon { border-color: #177ddc; } .ant-steps .ant-steps-item:not(.ant-steps-item-active):not(.ant-steps-item-process) > .ant-steps-item-container[role='button']:hover .ant-steps-item-icon .ant-steps-icon { color: #177ddc; } .ant-steps-horizontal:not(.ant-steps-label-vertical) .ant-steps-item { padding-left: 16px; white-space: nowrap; } .ant-steps-horizontal:not(.ant-steps-label-vertical) .ant-steps-item:first-child { padding-left: 0; } .ant-steps-horizontal:not(.ant-steps-label-vertical) .ant-steps-item:last-child .ant-steps-item-title { padding-right: 0; } .ant-steps-horizontal:not(.ant-steps-label-vertical) .ant-steps-item-tail { display: none; } .ant-steps-horizontal:not(.ant-steps-label-vertical) .ant-steps-item-description { max-width: 140px; white-space: normal; } .ant-steps-item-custom > .ant-steps-item-container > .ant-steps-item-icon { height: auto; background: none; border: 0; } .ant-steps-item-custom > .ant-steps-item-container > .ant-steps-item-icon > .ant-steps-icon { top: 0px; left: 0.5px; width: 32px; height: 32px; font-size: 24px; line-height: 32px; } .ant-steps-item-custom.ant-steps-item-process .ant-steps-item-icon > .ant-steps-icon { color: #177ddc; } .ant-steps:not(.ant-steps-vertical) .ant-steps-item-custom .ant-steps-item-icon { width: auto; background: none; } .ant-steps-small.ant-steps-horizontal:not(.ant-steps-label-vertical) .ant-steps-item { padding-left: 12px; } .ant-steps-small.ant-steps-horizontal:not(.ant-steps-label-vertical) .ant-steps-item:first-child { padding-left: 0; } .ant-steps-small .ant-steps-item-icon { width: 24px; height: 24px; margin: 0 8px 0 0; font-size: 12px; line-height: 24px; text-align: center; border-radius: 24px; } .ant-steps-small .ant-steps-item-title { padding-right: 12px; font-size: 14px; line-height: 24px; } .ant-steps-small .ant-steps-item-title::after { top: 12px; } .ant-steps-small .ant-steps-item-description { color: rgba(255, 255, 255, 0.45); font-size: 14px; } .ant-steps-small .ant-steps-item-tail { top: 8px; } .ant-steps-small .ant-steps-item-custom .ant-steps-item-icon { width: inherit; height: inherit; line-height: inherit; background: none; border: 0; border-radius: 0; } .ant-steps-small .ant-steps-item-custom .ant-steps-item-icon > .ant-steps-icon { font-size: 24px; line-height: 24px; transform: none; } .ant-steps-vertical { display: flex; flex-direction: column; } .ant-steps-vertical > .ant-steps-item { display: block; flex: 1 0 auto; padding-left: 0; overflow: visible; } .ant-steps-vertical > .ant-steps-item .ant-steps-item-icon { float: left; margin-right: 16px; } .ant-steps-vertical > .ant-steps-item .ant-steps-item-content { display: block; min-height: 48px; overflow: hidden; } .ant-steps-vertical > .ant-steps-item .ant-steps-item-title { line-height: 32px; } .ant-steps-vertical > .ant-steps-item .ant-steps-item-description { padding-bottom: 12px; } .ant-steps-vertical > .ant-steps-item > .ant-steps-item-container > .ant-steps-item-tail { position: absolute; top: 0; left: 15px; width: 1px; height: 100%; padding: 38px 0 6px; } .ant-steps-vertical > .ant-steps-item > .ant-steps-item-container > .ant-steps-item-tail::after { width: 1px; height: 100%; } .ant-steps-vertical > .ant-steps-item:not(:last-child) > .ant-steps-item-container > .ant-steps-item-tail { display: block; } .ant-steps-vertical > .ant-steps-item > .ant-steps-item-container > .ant-steps-item-content > .ant-steps-item-title::after { display: none; } .ant-steps-vertical.ant-steps-small .ant-steps-item-container .ant-steps-item-tail { position: absolute; top: 0; left: 11px; padding: 30px 0 6px; } .ant-steps-vertical.ant-steps-small .ant-steps-item-container .ant-steps-item-title { line-height: 24px; } .ant-steps-label-vertical .ant-steps-item { overflow: visible; } .ant-steps-label-vertical .ant-steps-item-tail { margin-left: 58px; padding: 3.5px 24px; } .ant-steps-label-vertical .ant-steps-item-content { display: block; width: 116px; margin-top: 8px; text-align: center; } .ant-steps-label-vertical .ant-steps-item-icon { display: inline-block; margin-left: 42px; } .ant-steps-label-vertical .ant-steps-item-title { padding-right: 0; padding-left: 0; } .ant-steps-label-vertical .ant-steps-item-title::after { display: none; } .ant-steps-label-vertical .ant-steps-item-subtitle { display: block; margin-bottom: 4px; margin-left: 0; line-height: 1.5715; } .ant-steps-label-vertical.ant-steps-small:not(.ant-steps-dot) .ant-steps-item-icon { margin-left: 46px; } .ant-steps-dot .ant-steps-item-title, .ant-steps-dot.ant-steps-small .ant-steps-item-title { line-height: 1.5715; } .ant-steps-dot .ant-steps-item-tail, .ant-steps-dot.ant-steps-small .ant-steps-item-tail { top: 2px; width: 100%; margin: 0 0 0 70px; padding: 0; } .ant-steps-dot .ant-steps-item-tail::after, .ant-steps-dot.ant-steps-small .ant-steps-item-tail::after { width: calc(100% - 20px); height: 3px; margin-left: 12px; } .ant-steps-dot .ant-steps-item:first-child .ant-steps-icon-dot, .ant-steps-dot.ant-steps-small .ant-steps-item:first-child .ant-steps-icon-dot { left: 2px; } .ant-steps-dot .ant-steps-item-icon, .ant-steps-dot.ant-steps-small .ant-steps-item-icon { width: 8px; height: 8px; margin-left: 67px; padding-right: 0; line-height: 8px; background: transparent; border: 0; } .ant-steps-dot .ant-steps-item-icon .ant-steps-icon-dot, .ant-steps-dot.ant-steps-small .ant-steps-item-icon .ant-steps-icon-dot { position: relative; float: left; width: 100%; height: 100%; border-radius: 100px; transition: all 0.3s; /* expand hover area */ } .ant-steps-dot .ant-steps-item-icon .ant-steps-icon-dot::after, .ant-steps-dot.ant-steps-small .ant-steps-item-icon .ant-steps-icon-dot::after { position: absolute; top: -12px; left: -26px; width: 60px; height: 32px; background: rgba(0, 0, 0, 0.001); content: ''; } .ant-steps-dot .ant-steps-item-content, .ant-steps-dot.ant-steps-small .ant-steps-item-content { width: 140px; } .ant-steps-dot .ant-steps-item-process .ant-steps-item-icon, .ant-steps-dot.ant-steps-small .ant-steps-item-process .ant-steps-item-icon { position: relative; top: -1px; width: 10px; height: 10px; line-height: 10px; background: none; } .ant-steps-dot .ant-steps-item-process .ant-steps-icon:first-child .ant-steps-icon-dot, .ant-steps-dot.ant-steps-small .ant-steps-item-process .ant-steps-icon:first-child .ant-steps-icon-dot { left: 0; } .ant-steps-vertical.ant-steps-dot .ant-steps-item-icon { margin-top: 13px; margin-left: 0; background: none; } .ant-steps-vertical.ant-steps-dot .ant-steps-item > .ant-steps-item-container > .ant-steps-item-tail { top: 6.5px; left: -9px; margin: 0; padding: 22px 0 4px; } .ant-steps-vertical.ant-steps-dot.ant-steps-small .ant-steps-item-icon { margin-top: 10px; } .ant-steps-vertical.ant-steps-dot.ant-steps-small .ant-steps-item > .ant-steps-item-container > .ant-steps-item-tail { top: 3.5px; } .ant-steps-vertical.ant-steps-dot .ant-steps-item:first-child .ant-steps-icon-dot { left: 0; } .ant-steps-vertical.ant-steps-dot .ant-steps-item-content { width: inherit; } .ant-steps-vertical.ant-steps-dot .ant-steps-item-process .ant-steps-item-container .ant-steps-item-icon .ant-steps-icon-dot { top: -1px; left: -1px; } .ant-steps-navigation { padding-top: 12px; } .ant-steps-navigation.ant-steps-small .ant-steps-item-container { margin-left: -12px; } .ant-steps-navigation .ant-steps-item { overflow: visible; text-align: center; } .ant-steps-navigation .ant-steps-item-container { display: inline-block; height: 100%; margin-left: -16px; padding-bottom: 12px; text-align: left; transition: opacity 0.3s; } .ant-steps-navigation .ant-steps-item-container .ant-steps-item-content { max-width: auto; } .ant-steps-navigation .ant-steps-item-container .ant-steps-item-title { max-width: 100%; padding-right: 0; overflow: hidden; white-space: nowrap; text-overflow: ellipsis; } .ant-steps-navigation .ant-steps-item-container .ant-steps-item-title::after { display: none; } .ant-steps-navigation .ant-steps-item:not(.ant-steps-item-active) .ant-steps-item-container[role='button'] { cursor: pointer; } .ant-steps-navigation .ant-steps-item:not(.ant-steps-item-active) .ant-steps-item-container[role='button']:hover { opacity: 0.85; } .ant-steps-navigation .ant-steps-item:last-child { flex: 1; } .ant-steps-navigation .ant-steps-item:last-child::after { display: none; } .ant-steps-navigation .ant-steps-item::after { position: absolute; top: 50%; left: 100%; display: inline-block; width: 12px; height: 12px; margin-top: -14px; margin-left: -2px; border: 1px solid rgba(255, 255, 255, 0.2); border-bottom: none; border-left: none; transform: rotate(45deg); content: ''; } .ant-steps-navigation .ant-steps-item::before { position: absolute; bottom: 0; left: 50%; display: inline-block; width: 0; height: 2px; background-color: #177ddc; transition: width 0.3s, left 0.3s; transition-timing-function: ease-out; content: ''; } .ant-steps-navigation .ant-steps-item.ant-steps-item-active::before { left: 0; width: 100%; } .ant-steps-navigation.ant-steps-vertical > .ant-steps-item { margin-right: 0 !important; } .ant-steps-navigation.ant-steps-vertical > .ant-steps-item::before { display: none; } .ant-steps-navigation.ant-steps-vertical > .ant-steps-item.ant-steps-item-active::before { top: 0; right: 0; left: unset; display: block; width: 3px; height: calc(100% - 24px); } .ant-steps-navigation.ant-steps-vertical > .ant-steps-item::after { position: relative; top: -2px; left: 50%; display: block; width: 8px; height: 8px; margin-bottom: 8px; text-align: center; transform: rotate(135deg); } .ant-steps-navigation.ant-steps-vertical > .ant-steps-item > .ant-steps-item-container > .ant-steps-item-tail { visibility: hidden; } .ant-steps-navigation.ant-steps-horizontal > .ant-steps-item > .ant-steps-item-container > .ant-steps-item-tail { visibility: hidden; } .ant-steps-rtl { direction: rtl; } .ant-steps.ant-steps-rtl .ant-steps-item-icon { margin-right: 0; margin-left: 8px; } .ant-steps-rtl .ant-steps-item-tail { right: 0; left: auto; } .ant-steps-rtl .ant-steps-item-title { padding-right: 0; padding-left: 16px; } .ant-steps-rtl .ant-steps-item-title .ant-steps-item-subtitle { float: left; margin-right: 8px; margin-left: 0; } .ant-steps-rtl .ant-steps-item-title::after { right: 100%; left: auto; } .ant-steps-rtl.ant-steps-horizontal:not(.ant-steps-label-vertical) .ant-steps-item { padding-right: 16px; padding-left: 0; } .ant-steps-rtl.ant-steps-horizontal:not(.ant-steps-label-vertical) .ant-steps-item:first-child { padding-right: 0; } .ant-steps-rtl.ant-steps-horizontal:not(.ant-steps-label-vertical) .ant-steps-item:last-child .ant-steps-item-title { padding-left: 0; } .ant-steps-rtl .ant-steps-item-custom .ant-steps-item-icon > .ant-steps-icon { right: 0.5px; left: auto; } .ant-steps-rtl.ant-steps-navigation.ant-steps-small .ant-steps-item-container { margin-right: -12px; margin-left: 0; } .ant-steps-rtl.ant-steps-navigation .ant-steps-item-container { margin-right: -16px; margin-left: 0; text-align: right; } .ant-steps-rtl.ant-steps-navigation .ant-steps-item-container .ant-steps-item-title { padding-left: 0; } .ant-steps-rtl.ant-steps-navigation .ant-steps-item::after { right: 100%; left: auto; margin-right: -2px; margin-left: 0; transform: rotate(225deg); } .ant-steps-rtl.ant-steps-small.ant-steps-horizontal:not(.ant-steps-label-vertical) .ant-steps-item { padding-right: 12px; padding-left: 0; } .ant-steps-rtl.ant-steps-small.ant-steps-horizontal:not(.ant-steps-label-vertical) .ant-steps-item:first-child { padding-right: 0; } .ant-steps-rtl.ant-steps-small .ant-steps-item-title { padding-right: 0; padding-left: 12px; } .ant-steps-rtl.ant-steps-vertical > .ant-steps-item .ant-steps-item-icon { float: right; margin-right: 0; margin-left: 16px; } .ant-steps-rtl.ant-steps-vertical > .ant-steps-item > .ant-steps-item-container > .ant-steps-item-tail { right: 16px; left: auto; } .ant-steps-rtl.ant-steps-vertical.ant-steps-small .ant-steps-item-container .ant-steps-item-tail { right: 12px; left: auto; } .ant-steps-rtl.ant-steps-label-vertical .ant-steps-item-title { padding-left: 0; } .ant-steps-rtl.ant-steps-dot .ant-steps-item-tail, .ant-steps-rtl.ant-steps-dot.ant-steps-small .ant-steps-item-tail { margin: 0 70px 0 0; } .ant-steps-rtl.ant-steps-dot .ant-steps-item-tail::after, .ant-steps-rtl.ant-steps-dot.ant-steps-small .ant-steps-item-tail::after { margin-right: 12px; margin-left: 0; } .ant-steps-rtl.ant-steps-dot .ant-steps-item:first-child .ant-steps-icon-dot, .ant-steps-rtl.ant-steps-dot.ant-steps-small .ant-steps-item:first-child .ant-steps-icon-dot { right: 2px; left: auto; } .ant-steps-rtl.ant-steps-dot .ant-steps-item-icon, .ant-steps-rtl.ant-steps-dot.ant-steps-small .ant-steps-item-icon { margin-right: 67px; margin-left: 0; } .ant-steps-dot .ant-steps-item-icon .ant-steps-icon-dot, .ant-steps-dot.ant-steps-small .ant-steps-item-icon .ant-steps-icon-dot { /* expand hover area */ } .ant-steps-rtl.ant-steps-dot .ant-steps-item-icon .ant-steps-icon-dot, .ant-steps-rtl.ant-steps-dot.ant-steps-small .ant-steps-item-icon .ant-steps-icon-dot { float: right; } .ant-steps-rtl.ant-steps-dot .ant-steps-item-icon .ant-steps-icon-dot::after, .ant-steps-rtl.ant-steps-dot.ant-steps-small .ant-steps-item-icon .ant-steps-icon-dot::after { right: -26px; left: auto; } .ant-steps-rtl.ant-steps-vertical.ant-steps-dot .ant-steps-item-icon { margin-right: 0; margin-left: 16px; } .ant-steps-rtl.ant-steps-vertical.ant-steps-dot .ant-steps-item > .ant-steps-item-container > .ant-steps-item-tail { right: -9px; left: auto; } .ant-steps-rtl.ant-steps-vertical.ant-steps-dot .ant-steps-item:first-child .ant-steps-icon-dot { right: 0; left: auto; } .ant-steps-rtl.ant-steps-vertical.ant-steps-dot .ant-steps-item-process .ant-steps-icon-dot { right: -2px; left: auto; } .ant-steps-rtl.ant-steps-with-progress.ant-steps-vertical > .ant-steps-item { padding-right: 4px; } .ant-steps-rtl.ant-steps-with-progress.ant-steps-vertical > .ant-steps-item > .ant-steps-item-container > .ant-steps-item-tail { right: 19px; } .ant-steps-rtl.ant-steps-with-progress.ant-steps-small.ant-steps-vertical > .ant-steps-item > .ant-steps-item-container > .ant-steps-item-tail { right: 15px; } .ant-steps-rtl.ant-steps-with-progress.ant-steps-horizontal.ant-steps-label-horizontal .ant-steps-item:first-child { padding-right: 4px; padding-left: 0; } .ant-steps-rtl.ant-steps-with-progress.ant-steps-horizontal.ant-steps-label-horizontal .ant-steps-item:first-child.ant-steps-item-active { padding-right: 4px; } .ant-steps-with-progress .ant-steps-item { padding-top: 4px; } .ant-steps-with-progress .ant-steps-item > .ant-steps-item-container > .ant-steps-item-tail { top: 4px; left: 19px; } .ant-steps-with-progress.ant-steps-horizontal .ant-steps-item:first-child, .ant-steps-with-progress.ant-steps-small.ant-steps-horizontal .ant-steps-item:first-child { padding-bottom: 4px; padding-left: 4px; } .ant-steps-with-progress.ant-steps-small > .ant-steps-item > .ant-steps-item-container > .ant-steps-item-tail { left: 15px; } .ant-steps-with-progress.ant-steps-vertical .ant-steps-item { padding-left: 4px; } .ant-steps-with-progress.ant-steps-label-vertical .ant-steps-item .ant-steps-item-tail { top: 14px !important; } .ant-steps-with-progress .ant-steps-item-icon { position: relative; } .ant-steps-with-progress .ant-steps-item-icon .ant-progress { position: absolute; top: -5px; right: -5px; bottom: -5px; left: -5px; } .ant-switch { margin: 0; padding: 0; color: rgba(255, 255, 255, 0.85); font-size: 14px; font-variant: tabular-nums; line-height: 1.5715; list-style: none; font-feature-settings: 'tnum'; position: relative; display: inline-block; box-sizing: border-box; min-width: 44px; height: 22px; line-height: 22px; vertical-align: middle; background-color: rgba(255, 255, 255, 0.3); border: 0; border-radius: 100px; cursor: pointer; transition: all 0.2s; user-select: none; } .ant-switch:focus { outline: 0; box-shadow: 0 0 0 2px rgba(255, 255, 255, 0.1); } .ant-switch-checked:focus { box-shadow: 0 0 0 2px #111b26; } .ant-switch:focus:hover { box-shadow: none; } .ant-switch-checked { background-color: #177ddc; } .ant-switch-loading, .ant-switch-disabled { cursor: not-allowed; opacity: 0.4; } .ant-switch-loading *, .ant-switch-disabled * { box-shadow: none; cursor: not-allowed; } .ant-switch-inner { display: block; margin: 0 7px 0 25px; color: #fff; font-size: 12px; transition: margin 0.2s; } .ant-switch-checked .ant-switch-inner { margin: 0 25px 0 7px; } .ant-switch-handle { position: absolute; top: 2px; left: 2px; width: 18px; height: 18px; transition: all 0.2s ease-in-out; } .ant-switch-handle::before { position: absolute; top: 0; right: 0; bottom: 0; left: 0; background-color: #fff; border-radius: 9px; box-shadow: 0 2px 4px 0 rgba(0, 35, 11, 0.2); transition: all 0.2s ease-in-out; content: ''; } .ant-switch-checked .ant-switch-handle { left: calc(100% - 18px - 2px); } .ant-switch:not(.ant-switch-disabled):active .ant-switch-handle::before { right: -30%; left: 0; } .ant-switch:not(.ant-switch-disabled):active.ant-switch-checked .ant-switch-handle::before { right: 0; left: -30%; } .ant-switch-loading-icon.anticon { position: relative; top: 2px; color: rgba(0, 0, 0, 0.65); vertical-align: top; } .ant-switch-checked .ant-switch-loading-icon { color: #177ddc; } .ant-switch-small { min-width: 28px; height: 16px; line-height: 16px; } .ant-switch-small .ant-switch-inner { margin: 0 5px 0 18px; font-size: 12px; } .ant-switch-small .ant-switch-handle { width: 12px; height: 12px; } .ant-switch-small .ant-switch-loading-icon { top: 1.5px; font-size: 9px; } .ant-switch-small.ant-switch-checked .ant-switch-inner { margin: 0 18px 0 5px; } .ant-switch-small.ant-switch-checked .ant-switch-handle { left: calc(100% - 12px - 2px); } .ant-switch-rtl { direction: rtl; } .ant-switch-rtl .ant-switch-inner { margin: 0 25px 0 7px; } .ant-switch-rtl .ant-switch-handle { right: 2px; left: auto; } .ant-switch-rtl:not(.ant-switch-rtl-disabled):active .ant-switch-handle::before { right: 0; left: -30%; } .ant-switch-rtl:not(.ant-switch-rtl-disabled):active.ant-switch-checked .ant-switch-handle::before { right: -30%; left: 0; } .ant-switch-rtl.ant-switch-checked .ant-switch-inner { margin: 0 7px 0 25px; } .ant-switch-rtl.ant-switch-checked .ant-switch-handle { right: calc(100% - 18px - 2px); } .ant-switch-rtl.ant-switch-small.ant-switch-checked .ant-switch-handle { right: calc(100% - 12px - 2px); } .ant-table.ant-table-middle { font-size: 14px; } .ant-table.ant-table-middle .ant-table-title, .ant-table.ant-table-middle .ant-table-footer, .ant-table.ant-table-middle .ant-table-thead > tr > th, .ant-table.ant-table-middle .ant-table-tbody > tr > td, .ant-table.ant-table-middle tfoot > tr > th, .ant-table.ant-table-middle tfoot > tr > td { padding: 12px 8px; } .ant-table.ant-table-middle .ant-table-filter-trigger { margin-right: -4px; } .ant-table.ant-table-middle .ant-table-expanded-row-fixed { margin: -12px -8px; } .ant-table.ant-table-middle .ant-table-tbody .ant-table-wrapper:only-child .ant-table { margin: -12px -8px -12px 40px; } .ant-table.ant-table-middle .ant-table-selection-column { padding-inline-start: 2px; } .ant-table.ant-table-small { font-size: 14px; } .ant-table.ant-table-small .ant-table-title, .ant-table.ant-table-small .ant-table-footer, .ant-table.ant-table-small .ant-table-thead > tr > th, .ant-table.ant-table-small .ant-table-tbody > tr > td, .ant-table.ant-table-small tfoot > tr > th, .ant-table.ant-table-small tfoot > tr > td { padding: 8px 8px; } .ant-table.ant-table-small .ant-table-filter-trigger { margin-right: -4px; } .ant-table.ant-table-small .ant-table-expanded-row-fixed { margin: -8px -8px; } .ant-table.ant-table-small .ant-table-tbody .ant-table-wrapper:only-child .ant-table { margin: -8px -8px -8px 40px; } .ant-table.ant-table-small .ant-table-selection-column { padding-inline-start: 2px; } .ant-table.ant-table-bordered > .ant-table-title { border: 1px solid #303030; border-bottom: 0; } .ant-table.ant-table-bordered > .ant-table-container { border-left: 1px solid #303030; } .ant-table.ant-table-bordered > .ant-table-container > .ant-table-content > table > thead > tr > th, .ant-table.ant-table-bordered > .ant-table-container > .ant-table-header > table > thead > tr > th, .ant-table.ant-table-bordered > .ant-table-container > .ant-table-body > table > thead > tr > th, .ant-table.ant-table-bordered > .ant-table-container > .ant-table-summary > table > thead > tr > th, .ant-table.ant-table-bordered > .ant-table-container > .ant-table-content > table > tbody > tr > td, .ant-table.ant-table-bordered > .ant-table-container > .ant-table-header > table > tbody > tr > td, .ant-table.ant-table-bordered > .ant-table-container > .ant-table-body > table > tbody > tr > td, .ant-table.ant-table-bordered > .ant-table-container > .ant-table-summary > table > tbody > tr > td, .ant-table.ant-table-bordered > .ant-table-container > .ant-table-content > table > tfoot > tr > th, .ant-table.ant-table-bordered > .ant-table-container > .ant-table-header > table > tfoot > tr > th, .ant-table.ant-table-bordered > .ant-table-container > .ant-table-body > table > tfoot > tr > th, .ant-table.ant-table-bordered > .ant-table-container > .ant-table-summary > table > tfoot > tr > th, .ant-table.ant-table-bordered > .ant-table-container > .ant-table-content > table > tfoot > tr > td, .ant-table.ant-table-bordered > .ant-table-container > .ant-table-header > table > tfoot > tr > td, .ant-table.ant-table-bordered > .ant-table-container > .ant-table-body > table > tfoot > tr > td, .ant-table.ant-table-bordered > .ant-table-container > .ant-table-summary > table > tfoot > tr > td { border-right: 1px solid #303030; } .ant-table.ant-table-bordered > .ant-table-container > .ant-table-content > table > thead > tr:not(:last-child) > th, .ant-table.ant-table-bordered > .ant-table-container > .ant-table-header > table > thead > tr:not(:last-child) > th, .ant-table.ant-table-bordered > .ant-table-container > .ant-table-body > table > thead > tr:not(:last-child) > th, .ant-table.ant-table-bordered > .ant-table-container > .ant-table-summary > table > thead > tr:not(:last-child) > th { border-bottom: 1px solid #303030; } .ant-table.ant-table-bordered > .ant-table-container > .ant-table-content > table > thead > tr > th::before, .ant-table.ant-table-bordered > .ant-table-container > .ant-table-header > table > thead > tr > th::before, .ant-table.ant-table-bordered > .ant-table-container > .ant-table-body > table > thead > tr > th::before, .ant-table.ant-table-bordered > .ant-table-container > .ant-table-summary > table > thead > tr > th::before { background-color: transparent !important; } .ant-table.ant-table-bordered > .ant-table-container > .ant-table-content > table > thead > tr > .ant-table-cell-fix-right-first::after, .ant-table.ant-table-bordered > .ant-table-container > .ant-table-header > table > thead > tr > .ant-table-cell-fix-right-first::after, .ant-table.ant-table-bordered > .ant-table-container > .ant-table-body > table > thead > tr > .ant-table-cell-fix-right-first::after, .ant-table.ant-table-bordered > .ant-table-container > .ant-table-summary > table > thead > tr > .ant-table-cell-fix-right-first::after, .ant-table.ant-table-bordered > .ant-table-container > .ant-table-content > table > tbody > tr > .ant-table-cell-fix-right-first::after, .ant-table.ant-table-bordered > .ant-table-container > .ant-table-header > table > tbody > tr > .ant-table-cell-fix-right-first::after, .ant-table.ant-table-bordered > .ant-table-container > .ant-table-body > table > tbody > tr > .ant-table-cell-fix-right-first::after, .ant-table.ant-table-bordered > .ant-table-container > .ant-table-summary > table > tbody > tr > .ant-table-cell-fix-right-first::after, .ant-table.ant-table-bordered > .ant-table-container > .ant-table-content > table > tfoot > tr > .ant-table-cell-fix-right-first::after, .ant-table.ant-table-bordered > .ant-table-container > .ant-table-header > table > tfoot > tr > .ant-table-cell-fix-right-first::after, .ant-table.ant-table-bordered > .ant-table-container > .ant-table-body > table > tfoot > tr > .ant-table-cell-fix-right-first::after, .ant-table.ant-table-bordered > .ant-table-container > .ant-table-summary > table > tfoot > tr > .ant-table-cell-fix-right-first::after { border-right: 1px solid #303030; } .ant-table.ant-table-bordered > .ant-table-container > .ant-table-content > table > tbody > tr > td > .ant-table-expanded-row-fixed, .ant-table.ant-table-bordered > .ant-table-container > .ant-table-header > table > tbody > tr > td > .ant-table-expanded-row-fixed, .ant-table.ant-table-bordered > .ant-table-container > .ant-table-body > table > tbody > tr > td > .ant-table-expanded-row-fixed, .ant-table.ant-table-bordered > .ant-table-container > .ant-table-summary > table > tbody > tr > td > .ant-table-expanded-row-fixed { margin: -16px -17px; } .ant-table.ant-table-bordered > .ant-table-container > .ant-table-content > table > tbody > tr > td > .ant-table-expanded-row-fixed::after, .ant-table.ant-table-bordered > .ant-table-container > .ant-table-header > table > tbody > tr > td > .ant-table-expanded-row-fixed::after, .ant-table.ant-table-bordered > .ant-table-container > .ant-table-body > table > tbody > tr > td > .ant-table-expanded-row-fixed::after, .ant-table.ant-table-bordered > .ant-table-container > .ant-table-summary > table > tbody > tr > td > .ant-table-expanded-row-fixed::after { position: absolute; top: 0; right: 1px; bottom: 0; border-right: 1px solid #303030; content: ''; } .ant-table.ant-table-bordered > .ant-table-container > .ant-table-content > table, .ant-table.ant-table-bordered > .ant-table-container > .ant-table-header > table { border-top: 1px solid #303030; } .ant-table.ant-table-bordered.ant-table-scroll-horizontal > .ant-table-container > .ant-table-body > table > tbody > tr.ant-table-expanded-row > td, .ant-table.ant-table-bordered.ant-table-scroll-horizontal > .ant-table-container > .ant-table-body > table > tbody > tr.ant-table-placeholder > td { border-right: 0; } .ant-table.ant-table-bordered.ant-table-middle > .ant-table-container > .ant-table-content > table > tbody > tr > td > .ant-table-expanded-row-fixed, .ant-table.ant-table-bordered.ant-table-middle > .ant-table-container > .ant-table-body > table > tbody > tr > td > .ant-table-expanded-row-fixed { margin: -12px -9px; } .ant-table.ant-table-bordered.ant-table-small > .ant-table-container > .ant-table-content > table > tbody > tr > td > .ant-table-expanded-row-fixed, .ant-table.ant-table-bordered.ant-table-small > .ant-table-container > .ant-table-body > table > tbody > tr > td > .ant-table-expanded-row-fixed { margin: -8px -9px; } .ant-table.ant-table-bordered > .ant-table-footer { border: 1px solid #303030; border-top: 0; } .ant-table-cell .ant-table-container:first-child { border-top: 0; } .ant-table-cell-scrollbar:not([rowspan]) { box-shadow: 0 1px 0 1px #1d1d1d; } .ant-table-wrapper { clear: both; max-width: 100%; } .ant-table-wrapper::before { display: table; content: ''; } .ant-table-wrapper::after { display: table; clear: both; content: ''; } .ant-table-wrapper::before { display: table; content: ''; } .ant-table-wrapper::after { display: table; clear: both; content: ''; } .ant-table { box-sizing: border-box; margin: 0; padding: 0; color: rgba(255, 255, 255, 0.85); font-variant: tabular-nums; line-height: 1.5715; list-style: none; font-feature-settings: 'tnum'; position: relative; font-size: 14px; background: #141414; border-radius: 2px; } .ant-table table { width: 100%; text-align: left; border-radius: 2px 2px 0 0; border-collapse: separate; border-spacing: 0; } .ant-table-thead > tr > th, .ant-table-tbody > tr > td, .ant-table tfoot > tr > th, .ant-table tfoot > tr > td { position: relative; padding: 16px 16px; overflow-wrap: break-word; } .ant-table-cell-ellipsis { overflow: hidden; white-space: nowrap; text-overflow: ellipsis; word-break: keep-all; } .ant-table-cell-ellipsis.ant-table-cell-fix-left-last, .ant-table-cell-ellipsis.ant-table-cell-fix-right-first { overflow: visible; } .ant-table-cell-ellipsis.ant-table-cell-fix-left-last .ant-table-cell-content, .ant-table-cell-ellipsis.ant-table-cell-fix-right-first .ant-table-cell-content { display: block; overflow: hidden; text-overflow: ellipsis; } .ant-table-cell-ellipsis .ant-table-column-title { overflow: hidden; text-overflow: ellipsis; word-break: keep-all; } .ant-table-title { padding: 16px 16px; } .ant-table-footer { padding: 16px 16px; color: rgba(255, 255, 255, 0.85); background: rgba(255, 255, 255, 0.04); } .ant-table-thead > tr > th { position: relative; color: rgba(255, 255, 255, 0.85); font-weight: 500; text-align: left; background: #1d1d1d; border-bottom: 1px solid #303030; transition: background 0.3s ease; } .ant-table-thead > tr > th[colspan]:not([colspan='1']) { text-align: center; } .ant-table-thead > tr > th:not(:last-child):not(.ant-table-selection-column):not(.ant-table-row-expand-icon-cell):not([colspan])::before { position: absolute; top: 50%; right: 0; width: 1px; height: 1.6em; background-color: rgba(255, 255, 255, 0.08); transform: translateY(-50%); transition: background-color 0.3s; content: ''; } .ant-table-thead > tr:not(:last-child) > th[colspan] { border-bottom: 0; } .ant-table-tbody > tr > td { border-bottom: 1px solid #303030; transition: background 0.3s; } .ant-table-tbody > tr > td > .ant-table-wrapper:only-child .ant-table, .ant-table-tbody > tr > td > .ant-table-expanded-row-fixed > .ant-table-wrapper:only-child .ant-table { margin: -16px -16px -16px 32px; } .ant-table-tbody > tr > td > .ant-table-wrapper:only-child .ant-table-tbody > tr:last-child > td, .ant-table-tbody > tr > td > .ant-table-expanded-row-fixed > .ant-table-wrapper:only-child .ant-table-tbody > tr:last-child > td { border-bottom: 0; } .ant-table-tbody > tr > td > .ant-table-wrapper:only-child .ant-table-tbody > tr:last-child > td:first-child, .ant-table-tbody > tr > td > .ant-table-expanded-row-fixed > .ant-table-wrapper:only-child .ant-table-tbody > tr:last-child > td:first-child, .ant-table-tbody > tr > td > .ant-table-wrapper:only-child .ant-table-tbody > tr:last-child > td:last-child, .ant-table-tbody > tr > td > .ant-table-expanded-row-fixed > .ant-table-wrapper:only-child .ant-table-tbody > tr:last-child > td:last-child { border-radius: 0; } .ant-table-tbody > tr.ant-table-row:hover > td, .ant-table-tbody > tr > td.ant-table-cell-row-hover { background: #262626; } .ant-table-tbody > tr.ant-table-row-selected > td { background: #111b26; border-color: rgba(0, 0, 0, 0.03); } .ant-table-tbody > tr.ant-table-row-selected:hover > td { background: #0e161f; } .ant-table-summary { position: relative; z-index: 2; background: #141414; } div.ant-table-summary { box-shadow: 0 -1px 0 #303030; } .ant-table-summary > tr > th, .ant-table-summary > tr > td { border-bottom: 1px solid #303030; } .ant-table-pagination.ant-pagination { margin: 16px 0; } .ant-table-pagination { display: flex; flex-wrap: wrap; row-gap: 8px; } .ant-table-pagination > * { flex: none; } .ant-table-pagination-left { justify-content: flex-start; } .ant-table-pagination-center { justify-content: center; } .ant-table-pagination-right { justify-content: flex-end; } .ant-table-thead th.ant-table-column-has-sorters { outline: none; cursor: pointer; transition: all 0.3s; } .ant-table-thead th.ant-table-column-has-sorters:hover { background: #303030; } .ant-table-thead th.ant-table-column-has-sorters:hover::before { background-color: transparent !important; } .ant-table-thead th.ant-table-column-has-sorters:focus-visible { color: #177ddc; } .ant-table-thead th.ant-table-column-has-sorters.ant-table-cell-fix-left:hover, .ant-table-thead th.ant-table-column-has-sorters.ant-table-cell-fix-right:hover { background: #222; } .ant-table-thead th.ant-table-column-sort { background: #262626; } .ant-table-thead th.ant-table-column-sort::before { background-color: transparent !important; } td.ant-table-column-sort { background: rgba(255, 255, 255, 0.01); } .ant-table-column-title { position: relative; z-index: 1; flex: 1; } .ant-table-column-sorters { display: flex; flex: auto; align-items: center; justify-content: space-between; } .ant-table-column-sorters::after { position: absolute; top: 0; right: 0; bottom: 0; left: 0; width: 100%; height: 100%; content: ''; } .ant-table-column-sorter { margin-left: 4px; color: #bfbfbf; font-size: 0; transition: color 0.3s; } .ant-table-column-sorter-inner { display: inline-flex; flex-direction: column; align-items: center; } .ant-table-column-sorter-up, .ant-table-column-sorter-down { font-size: 11px; } .ant-table-column-sorter-up.active, .ant-table-column-sorter-down.active { color: #177ddc; } .ant-table-column-sorter-up + .ant-table-column-sorter-down { margin-top: -0.3em; } .ant-table-column-sorters:hover .ant-table-column-sorter { color: #a6a6a6; } .ant-table-filter-column { display: flex; justify-content: space-between; } .ant-table-filter-trigger { position: relative; display: flex; align-items: center; margin: -4px -8px -4px 4px; padding: 0 4px; color: #bfbfbf; font-size: 12px; border-radius: 2px; cursor: pointer; transition: all 0.3s; } .ant-table-filter-trigger:hover { color: rgba(255, 255, 255, 0.45); background: #434343; } .ant-table-filter-trigger.active { color: #177ddc; } .ant-table-filter-dropdown { box-sizing: border-box; margin: 0; padding: 0; color: rgba(255, 255, 255, 0.85); font-size: 14px; font-variant: tabular-nums; line-height: 1.5715; list-style: none; font-feature-settings: 'tnum'; min-width: 120px; background-color: #1f1f1f; border-radius: 2px; box-shadow: 0 3px 6px -4px rgba(0, 0, 0, 0.48), 0 6px 16px 0 rgba(0, 0, 0, 0.32), 0 9px 28px 8px rgba(0, 0, 0, 0.2); } .ant-table-filter-dropdown .ant-dropdown-menu { max-height: 264px; overflow-x: hidden; border: 0; box-shadow: none; } .ant-table-filter-dropdown .ant-dropdown-menu:empty::after { display: block; padding: 8px 0; color: rgba(255, 255, 255, 0.3); font-size: 12px; text-align: center; content: 'Not Found'; } .ant-table-filter-dropdown-tree { padding: 8px 8px 0; } .ant-table-filter-dropdown-tree .ant-tree-treenode .ant-tree-node-content-wrapper:hover { background-color: rgba(255, 255, 255, 0.08); } .ant-table-filter-dropdown-tree .ant-tree-treenode-checkbox-checked .ant-tree-node-content-wrapper, .ant-table-filter-dropdown-tree .ant-tree-treenode-checkbox-checked .ant-tree-node-content-wrapper:hover { background-color: #11263c; } .ant-table-filter-dropdown-search { padding: 8px; border-bottom: 1px #303030 solid; } .ant-table-filter-dropdown-search-input input { min-width: 140px; } .ant-table-filter-dropdown-search-input .anticon { color: rgba(255, 255, 255, 0.3); } .ant-table-filter-dropdown-checkall { width: 100%; margin-bottom: 4px; margin-left: 4px; } .ant-table-filter-dropdown-submenu > ul { max-height: calc(100vh - 130px); overflow-x: hidden; overflow-y: auto; } .ant-table-filter-dropdown .ant-checkbox-wrapper + span, .ant-table-filter-dropdown-submenu .ant-checkbox-wrapper + span { padding-left: 8px; } .ant-table-filter-dropdown-btns { display: flex; justify-content: space-between; padding: 7px 8px; overflow: hidden; background-color: #1f1f1f; border-top: 1px solid #303030; } .ant-table-selection-col { width: 32px; } .ant-table-bordered .ant-table-selection-col { width: 50px; } table tr th.ant-table-selection-column, table tr td.ant-table-selection-column { padding-right: 8px; padding-left: 8px; text-align: center; } table tr th.ant-table-selection-column .ant-radio-wrapper, table tr td.ant-table-selection-column .ant-radio-wrapper { margin-right: 0; } table tr th.ant-table-selection-column.ant-table-cell-fix-left { z-index: 3; } table tr th.ant-table-selection-column::after { background-color: transparent !important; } .ant-table-selection { position: relative; display: inline-flex; flex-direction: column; } .ant-table-selection-extra { position: absolute; top: 0; z-index: 1; cursor: pointer; transition: all 0.3s; margin-inline-start: 100%; padding-inline-start: 4px; } .ant-table-selection-extra .anticon { color: #bfbfbf; font-size: 10px; } .ant-table-selection-extra .anticon:hover { color: #a6a6a6; } .ant-table-expand-icon-col { width: 48px; } .ant-table-row-expand-icon-cell { text-align: center; } .ant-table-row-expand-icon-cell .ant-table-row-expand-icon { display: inline-flex; float: none; vertical-align: sub; } .ant-table-row-indent { float: left; height: 1px; } .ant-table-row-expand-icon { color: #177ddc; outline: none; cursor: pointer; transition: color 0.3s; position: relative; float: left; box-sizing: border-box; width: 17px; height: 17px; padding: 0; color: inherit; line-height: 17px; background: transparent; border: 1px solid #303030; border-radius: 2px; transform: scale(0.94117647); transition: all 0.3s; user-select: none; } .ant-table-row-expand-icon:focus-visible, .ant-table-row-expand-icon:hover { color: #165996; } .ant-table-row-expand-icon:active { color: #388ed3; } .ant-table-row-expand-icon:focus, .ant-table-row-expand-icon:hover, .ant-table-row-expand-icon:active { border-color: currentcolor; } .ant-table-row-expand-icon::before, .ant-table-row-expand-icon::after { position: absolute; background: currentcolor; transition: transform 0.3s ease-out; content: ''; } .ant-table-row-expand-icon::before { top: 7px; right: 3px; left: 3px; height: 1px; } .ant-table-row-expand-icon::after { top: 3px; bottom: 3px; left: 7px; width: 1px; transform: rotate(90deg); } .ant-table-row-expand-icon-collapsed::before { transform: rotate(-180deg); } .ant-table-row-expand-icon-collapsed::after { transform: rotate(0deg); } .ant-table-row-expand-icon-spaced { background: transparent; border: 0; visibility: hidden; } .ant-table-row-expand-icon-spaced::before, .ant-table-row-expand-icon-spaced::after { display: none; content: none; } .ant-table-row-indent + .ant-table-row-expand-icon { margin-top: 2.5005px; margin-right: 8px; } tr.ant-table-expanded-row > td, tr.ant-table-expanded-row:hover > td { background: #1d1d1d; } tr.ant-table-expanded-row .ant-descriptions-view { display: flex; } tr.ant-table-expanded-row .ant-descriptions-view table { flex: auto; width: auto; } .ant-table .ant-table-expanded-row-fixed { position: relative; margin: -16px -16px; padding: 16px 16px; } .ant-table-tbody > tr.ant-table-placeholder { text-align: center; } .ant-table-empty .ant-table-tbody > tr.ant-table-placeholder { color: rgba(255, 255, 255, 0.3); } .ant-table-tbody > tr.ant-table-placeholder:hover > td { background: #141414; } .ant-table-cell-fix-left, .ant-table-cell-fix-right { position: sticky !important; z-index: 2; background: #141414; } .ant-table-cell-fix-left-first::after, .ant-table-cell-fix-left-last::after { position: absolute; top: 0; right: 0; bottom: -1px; width: 30px; transform: translateX(100%); transition: box-shadow 0.3s; content: ''; pointer-events: none; } .ant-table-cell-fix-left-all::after { display: none; } .ant-table-cell-fix-right-first::after, .ant-table-cell-fix-right-last::after { position: absolute; top: 0; bottom: -1px; left: 0; width: 30px; transform: translateX(-100%); transition: box-shadow 0.3s; content: ''; pointer-events: none; } .ant-table .ant-table-container::before, .ant-table .ant-table-container::after { position: absolute; top: 0; bottom: 0; z-index: calc(calc(2 + 1) + 1); width: 30px; transition: box-shadow 0.3s; content: ''; pointer-events: none; } .ant-table .ant-table-container::before { left: 0; } .ant-table .ant-table-container::after { right: 0; } .ant-table-ping-left:not(.ant-table-has-fix-left) > .ant-table-container { position: relative; } .ant-table-ping-left:not(.ant-table-has-fix-left) > .ant-table-container::before { box-shadow: inset 10px 0 8px -8px rgba(0, 0, 0, 0.45); } .ant-table-ping-left .ant-table-cell-fix-left-first::after, .ant-table-ping-left .ant-table-cell-fix-left-last::after { box-shadow: inset 10px 0 8px -8px rgba(0, 0, 0, 0.45); } .ant-table-ping-left .ant-table-cell-fix-left-last::before { background-color: transparent !important; } .ant-table-ping-right:not(.ant-table-has-fix-right) > .ant-table-container { position: relative; } .ant-table-ping-right:not(.ant-table-has-fix-right) > .ant-table-container::after { box-shadow: inset -10px 0 8px -8px rgba(0, 0, 0, 0.45); } .ant-table-ping-right .ant-table-cell-fix-right-first::after, .ant-table-ping-right .ant-table-cell-fix-right-last::after { box-shadow: inset -10px 0 8px -8px rgba(0, 0, 0, 0.45); } .ant-table-sticky-holder { position: sticky; z-index: calc(2 + 1); background: #141414; } .ant-table-sticky-scroll { position: sticky; bottom: 0; z-index: calc(2 + 1); display: flex; align-items: center; background: #fcfcfc; border-top: 1px solid #303030; opacity: 0.6; } .ant-table-sticky-scroll:hover { transform-origin: center bottom; } .ant-table-sticky-scroll-bar { height: 8px; background-color: rgba(0, 0, 0, 0.35); border-radius: 4px; } .ant-table-sticky-scroll-bar:hover { background-color: rgba(0, 0, 0, 0.8); } .ant-table-sticky-scroll-bar-active { background-color: rgba(0, 0, 0, 0.8); } @media all and (-ms-high-contrast: none) { .ant-table-ping-left .ant-table-cell-fix-left-last::after { box-shadow: none !important; } .ant-table-ping-right .ant-table-cell-fix-right-first::after { box-shadow: none !important; } } .ant-table { /* title + table */ /* table */ /* table + footer */ } .ant-table-title { border-radius: 2px 2px 0 0; } .ant-table-title + .ant-table-container { border-top-left-radius: 0; border-top-right-radius: 0; } .ant-table-title + .ant-table-container table { border-radius: 0; } .ant-table-title + .ant-table-container table > thead > tr:first-child th:first-child { border-radius: 0; } .ant-table-title + .ant-table-container table > thead > tr:first-child th:last-child { border-radius: 0; } .ant-table-container { border-top-left-radius: 2px; border-top-right-radius: 2px; } .ant-table-container table > thead > tr:first-child th:first-child { border-top-left-radius: 2px; } .ant-table-container table > thead > tr:first-child th:last-child { border-top-right-radius: 2px; } .ant-table-footer { border-radius: 0 0 2px 2px; } .ant-table-wrapper-rtl { direction: rtl; } .ant-table-rtl { direction: rtl; } .ant-table-wrapper-rtl .ant-table table { text-align: right; } .ant-table-wrapper-rtl .ant-table-thead > tr > th[colspan]:not([colspan='1']) { text-align: center; } .ant-table-wrapper-rtl .ant-table-thead > tr > th:not(:last-child):not(.ant-table-selection-column):not(.ant-table-row-expand-icon-cell):not([colspan])::before { right: auto; left: 0; } .ant-table-wrapper-rtl .ant-table-thead > tr > th { text-align: right; } .ant-table-tbody > tr .ant-table-wrapper:only-child .ant-table.ant-table-rtl { margin: -16px 33px -16px -16px; } .ant-table-wrapper.ant-table-wrapper-rtl .ant-table-pagination-left { justify-content: flex-end; } .ant-table-wrapper.ant-table-wrapper-rtl .ant-table-pagination-right { justify-content: flex-start; } .ant-table-wrapper-rtl .ant-table-column-sorter { margin-right: 4px; margin-left: 0; } .ant-table-wrapper-rtl .ant-table-filter-column-title { padding: 16px 16px 16px 2.3em; } .ant-table-rtl .ant-table-thead tr th.ant-table-column-has-sorters .ant-table-filter-column-title { padding: 0 0 0 2.3em; } .ant-table-wrapper-rtl .ant-table-filter-trigger { margin: -4px 4px -4px -8px; } .ant-dropdown-rtl .ant-table-filter-dropdown .ant-checkbox-wrapper + span, .ant-dropdown-rtl .ant-table-filter-dropdown-submenu .ant-checkbox-wrapper + span, .ant-dropdown-menu-submenu-rtl.ant-table-filter-dropdown .ant-checkbox-wrapper + span, .ant-dropdown-menu-submenu-rtl.ant-table-filter-dropdown-submenu .ant-checkbox-wrapper + span { padding-right: 8px; padding-left: 0; } .ant-table-wrapper-rtl .ant-table-selection { text-align: center; } .ant-table-wrapper-rtl .ant-table-row-indent { float: right; } .ant-table-wrapper-rtl .ant-table-row-expand-icon { float: right; } .ant-table-wrapper-rtl .ant-table-row-indent + .ant-table-row-expand-icon { margin-right: 0; margin-left: 8px; } .ant-table-wrapper-rtl .ant-table-row-expand-icon::after { transform: rotate(-90deg); } .ant-table-wrapper-rtl .ant-table-row-expand-icon-collapsed::before { transform: rotate(180deg); } .ant-table-wrapper-rtl .ant-table-row-expand-icon-collapsed::after { transform: rotate(0deg); } .ant-tabs-small > .ant-tabs-nav .ant-tabs-tab { padding: 8px 0; font-size: 14px; } .ant-tabs-large > .ant-tabs-nav .ant-tabs-tab { padding: 16px 0; font-size: 16px; } .ant-tabs-card.ant-tabs-small > .ant-tabs-nav .ant-tabs-tab { padding: 6px 16px; } .ant-tabs-card.ant-tabs-large > .ant-tabs-nav .ant-tabs-tab { padding: 7px 16px 6px; } .ant-tabs-rtl { direction: rtl; } .ant-tabs-rtl .ant-tabs-nav .ant-tabs-tab { margin: 0 0 0 32px; } .ant-tabs-rtl .ant-tabs-nav .ant-tabs-tab:last-of-type { margin-left: 0; } .ant-tabs-rtl .ant-tabs-nav .ant-tabs-tab .anticon { margin-right: 0; margin-left: 12px; } .ant-tabs-rtl .ant-tabs-nav .ant-tabs-tab .ant-tabs-tab-remove { margin-right: 8px; margin-left: -4px; } .ant-tabs-rtl .ant-tabs-nav .ant-tabs-tab .ant-tabs-tab-remove .anticon { margin: 0; } .ant-tabs-rtl.ant-tabs-left > .ant-tabs-nav { order: 1; } .ant-tabs-rtl.ant-tabs-left > .ant-tabs-content-holder { order: 0; } .ant-tabs-rtl.ant-tabs-right > .ant-tabs-nav { order: 0; } .ant-tabs-rtl.ant-tabs-right > .ant-tabs-content-holder { order: 1; } .ant-tabs-rtl.ant-tabs-card.ant-tabs-top > .ant-tabs-nav .ant-tabs-tab + .ant-tabs-tab, .ant-tabs-rtl.ant-tabs-card.ant-tabs-bottom > .ant-tabs-nav .ant-tabs-tab + .ant-tabs-tab, .ant-tabs-rtl.ant-tabs-card.ant-tabs-top > div > .ant-tabs-nav .ant-tabs-tab + .ant-tabs-tab, .ant-tabs-rtl.ant-tabs-card.ant-tabs-bottom > div > .ant-tabs-nav .ant-tabs-tab + .ant-tabs-tab { margin-right: 2px; margin-left: 0; } .ant-tabs-rtl.ant-tabs-card.ant-tabs-top > .ant-tabs-nav .ant-tabs-nav-add, .ant-tabs-rtl.ant-tabs-card.ant-tabs-bottom > .ant-tabs-nav .ant-tabs-nav-add, .ant-tabs-rtl.ant-tabs-card.ant-tabs-top > div > .ant-tabs-nav .ant-tabs-nav-add, .ant-tabs-rtl.ant-tabs-card.ant-tabs-bottom > div > .ant-tabs-nav .ant-tabs-nav-add { margin-right: 2px; margin-left: 0; } .ant-tabs-dropdown-rtl { direction: rtl; } .ant-tabs-dropdown-rtl .ant-tabs-dropdown-menu-item { text-align: right; } .ant-tabs-top, .ant-tabs-bottom { flex-direction: column; } .ant-tabs-top > .ant-tabs-nav, .ant-tabs-bottom > .ant-tabs-nav, .ant-tabs-top > div > .ant-tabs-nav, .ant-tabs-bottom > div > .ant-tabs-nav { margin: 0 0 16px 0; } .ant-tabs-top > .ant-tabs-nav::before, .ant-tabs-bottom > .ant-tabs-nav::before, .ant-tabs-top > div > .ant-tabs-nav::before, .ant-tabs-bottom > div > .ant-tabs-nav::before { position: absolute; right: 0; left: 0; border-bottom: 1px solid #303030; content: ''; } .ant-tabs-top > .ant-tabs-nav .ant-tabs-ink-bar, .ant-tabs-bottom > .ant-tabs-nav .ant-tabs-ink-bar, .ant-tabs-top > div > .ant-tabs-nav .ant-tabs-ink-bar, .ant-tabs-bottom > div > .ant-tabs-nav .ant-tabs-ink-bar { height: 2px; } .ant-tabs-top > .ant-tabs-nav .ant-tabs-ink-bar-animated, .ant-tabs-bottom > .ant-tabs-nav .ant-tabs-ink-bar-animated, .ant-tabs-top > div > .ant-tabs-nav .ant-tabs-ink-bar-animated, .ant-tabs-bottom > div > .ant-tabs-nav .ant-tabs-ink-bar-animated { transition: width 0.3s, left 0.3s, right 0.3s; } .ant-tabs-top > .ant-tabs-nav .ant-tabs-nav-wrap::before, .ant-tabs-bottom > .ant-tabs-nav .ant-tabs-nav-wrap::before, .ant-tabs-top > div > .ant-tabs-nav .ant-tabs-nav-wrap::before, .ant-tabs-bottom > div > .ant-tabs-nav .ant-tabs-nav-wrap::before, .ant-tabs-top > .ant-tabs-nav .ant-tabs-nav-wrap::after, .ant-tabs-bottom > .ant-tabs-nav .ant-tabs-nav-wrap::after, .ant-tabs-top > div > .ant-tabs-nav .ant-tabs-nav-wrap::after, .ant-tabs-bottom > div > .ant-tabs-nav .ant-tabs-nav-wrap::after { top: 0; bottom: 0; width: 30px; } .ant-tabs-top > .ant-tabs-nav .ant-tabs-nav-wrap::before, .ant-tabs-bottom > .ant-tabs-nav .ant-tabs-nav-wrap::before, .ant-tabs-top > div > .ant-tabs-nav .ant-tabs-nav-wrap::before, .ant-tabs-bottom > div > .ant-tabs-nav .ant-tabs-nav-wrap::before { left: 0; box-shadow: inset 10px 0 8px -8px rgba(0, 0, 0, 0.08); } .ant-tabs-top > .ant-tabs-nav .ant-tabs-nav-wrap::after, .ant-tabs-bottom > .ant-tabs-nav .ant-tabs-nav-wrap::after, .ant-tabs-top > div > .ant-tabs-nav .ant-tabs-nav-wrap::after, .ant-tabs-bottom > div > .ant-tabs-nav .ant-tabs-nav-wrap::after { right: 0; box-shadow: inset -10px 0 8px -8px rgba(0, 0, 0, 0.08); } .ant-tabs-top > .ant-tabs-nav .ant-tabs-nav-wrap.ant-tabs-nav-wrap-ping-left::before, .ant-tabs-bottom > .ant-tabs-nav .ant-tabs-nav-wrap.ant-tabs-nav-wrap-ping-left::before, .ant-tabs-top > div > .ant-tabs-nav .ant-tabs-nav-wrap.ant-tabs-nav-wrap-ping-left::before, .ant-tabs-bottom > div > .ant-tabs-nav .ant-tabs-nav-wrap.ant-tabs-nav-wrap-ping-left::before { opacity: 1; } .ant-tabs-top > .ant-tabs-nav .ant-tabs-nav-wrap.ant-tabs-nav-wrap-ping-right::after, .ant-tabs-bottom > .ant-tabs-nav .ant-tabs-nav-wrap.ant-tabs-nav-wrap-ping-right::after, .ant-tabs-top > div > .ant-tabs-nav .ant-tabs-nav-wrap.ant-tabs-nav-wrap-ping-right::after, .ant-tabs-bottom > div > .ant-tabs-nav .ant-tabs-nav-wrap.ant-tabs-nav-wrap-ping-right::after { opacity: 1; } .ant-tabs-top > .ant-tabs-nav::before, .ant-tabs-top > div > .ant-tabs-nav::before { bottom: 0; } .ant-tabs-top > .ant-tabs-nav .ant-tabs-ink-bar, .ant-tabs-top > div > .ant-tabs-nav .ant-tabs-ink-bar { bottom: 0; } .ant-tabs-bottom > .ant-tabs-nav, .ant-tabs-bottom > div > .ant-tabs-nav { order: 1; margin-top: 16px; margin-bottom: 0; } .ant-tabs-bottom > .ant-tabs-nav::before, .ant-tabs-bottom > div > .ant-tabs-nav::before { top: 0; } .ant-tabs-bottom > .ant-tabs-nav .ant-tabs-ink-bar, .ant-tabs-bottom > div > .ant-tabs-nav .ant-tabs-ink-bar { top: 0; } .ant-tabs-bottom > .ant-tabs-content-holder, .ant-tabs-bottom > div > .ant-tabs-content-holder { order: 0; } .ant-tabs-left > .ant-tabs-nav, .ant-tabs-right > .ant-tabs-nav, .ant-tabs-left > div > .ant-tabs-nav, .ant-tabs-right > div > .ant-tabs-nav { flex-direction: column; min-width: 50px; } .ant-tabs-left > .ant-tabs-nav .ant-tabs-tab, .ant-tabs-right > .ant-tabs-nav .ant-tabs-tab, .ant-tabs-left > div > .ant-tabs-nav .ant-tabs-tab, .ant-tabs-right > div > .ant-tabs-nav .ant-tabs-tab { padding: 8px 24px; text-align: center; } .ant-tabs-left > .ant-tabs-nav .ant-tabs-tab + .ant-tabs-tab, .ant-tabs-right > .ant-tabs-nav .ant-tabs-tab + .ant-tabs-tab, .ant-tabs-left > div > .ant-tabs-nav .ant-tabs-tab + .ant-tabs-tab, .ant-tabs-right > div > .ant-tabs-nav .ant-tabs-tab + .ant-tabs-tab { margin: 16px 0 0 0; } .ant-tabs-left > .ant-tabs-nav .ant-tabs-nav-wrap, .ant-tabs-right > .ant-tabs-nav .ant-tabs-nav-wrap, .ant-tabs-left > div > .ant-tabs-nav .ant-tabs-nav-wrap, .ant-tabs-right > div > .ant-tabs-nav .ant-tabs-nav-wrap { flex-direction: column; } .ant-tabs-left > .ant-tabs-nav .ant-tabs-nav-wrap::before, .ant-tabs-right > .ant-tabs-nav .ant-tabs-nav-wrap::before, .ant-tabs-left > div > .ant-tabs-nav .ant-tabs-nav-wrap::before, .ant-tabs-right > div > .ant-tabs-nav .ant-tabs-nav-wrap::before, .ant-tabs-left > .ant-tabs-nav .ant-tabs-nav-wrap::after, .ant-tabs-right > .ant-tabs-nav .ant-tabs-nav-wrap::after, .ant-tabs-left > div > .ant-tabs-nav .ant-tabs-nav-wrap::after, .ant-tabs-right > div > .ant-tabs-nav .ant-tabs-nav-wrap::after { right: 0; left: 0; height: 30px; } .ant-tabs-left > .ant-tabs-nav .ant-tabs-nav-wrap::before, .ant-tabs-right > .ant-tabs-nav .ant-tabs-nav-wrap::before, .ant-tabs-left > div > .ant-tabs-nav .ant-tabs-nav-wrap::before, .ant-tabs-right > div > .ant-tabs-nav .ant-tabs-nav-wrap::before { top: 0; box-shadow: inset 0 10px 8px -8px rgba(0, 0, 0, 0.08); } .ant-tabs-left > .ant-tabs-nav .ant-tabs-nav-wrap::after, .ant-tabs-right > .ant-tabs-nav .ant-tabs-nav-wrap::after, .ant-tabs-left > div > .ant-tabs-nav .ant-tabs-nav-wrap::after, .ant-tabs-right > div > .ant-tabs-nav .ant-tabs-nav-wrap::after { bottom: 0; box-shadow: inset 0 -10px 8px -8px rgba(0, 0, 0, 0.08); } .ant-tabs-left > .ant-tabs-nav .ant-tabs-nav-wrap.ant-tabs-nav-wrap-ping-top::before, .ant-tabs-right > .ant-tabs-nav .ant-tabs-nav-wrap.ant-tabs-nav-wrap-ping-top::before, .ant-tabs-left > div > .ant-tabs-nav .ant-tabs-nav-wrap.ant-tabs-nav-wrap-ping-top::before, .ant-tabs-right > div > .ant-tabs-nav .ant-tabs-nav-wrap.ant-tabs-nav-wrap-ping-top::before { opacity: 1; } .ant-tabs-left > .ant-tabs-nav .ant-tabs-nav-wrap.ant-tabs-nav-wrap-ping-bottom::after, .ant-tabs-right > .ant-tabs-nav .ant-tabs-nav-wrap.ant-tabs-nav-wrap-ping-bottom::after, .ant-tabs-left > div > .ant-tabs-nav .ant-tabs-nav-wrap.ant-tabs-nav-wrap-ping-bottom::after, .ant-tabs-right > div > .ant-tabs-nav .ant-tabs-nav-wrap.ant-tabs-nav-wrap-ping-bottom::after { opacity: 1; } .ant-tabs-left > .ant-tabs-nav .ant-tabs-ink-bar, .ant-tabs-right > .ant-tabs-nav .ant-tabs-ink-bar, .ant-tabs-left > div > .ant-tabs-nav .ant-tabs-ink-bar, .ant-tabs-right > div > .ant-tabs-nav .ant-tabs-ink-bar { width: 2px; } .ant-tabs-left > .ant-tabs-nav .ant-tabs-ink-bar-animated, .ant-tabs-right > .ant-tabs-nav .ant-tabs-ink-bar-animated, .ant-tabs-left > div > .ant-tabs-nav .ant-tabs-ink-bar-animated, .ant-tabs-right > div > .ant-tabs-nav .ant-tabs-ink-bar-animated { transition: height 0.3s, top 0.3s; } .ant-tabs-left > .ant-tabs-nav .ant-tabs-nav-list, .ant-tabs-right > .ant-tabs-nav .ant-tabs-nav-list, .ant-tabs-left > div > .ant-tabs-nav .ant-tabs-nav-list, .ant-tabs-right > div > .ant-tabs-nav .ant-tabs-nav-list, .ant-tabs-left > .ant-tabs-nav .ant-tabs-nav-operations, .ant-tabs-right > .ant-tabs-nav .ant-tabs-nav-operations, .ant-tabs-left > div > .ant-tabs-nav .ant-tabs-nav-operations, .ant-tabs-right > div > .ant-tabs-nav .ant-tabs-nav-operations { flex: 1 0 auto; flex-direction: column; } .ant-tabs-left > .ant-tabs-nav .ant-tabs-ink-bar, .ant-tabs-left > div > .ant-tabs-nav .ant-tabs-ink-bar { right: 0; } .ant-tabs-left > .ant-tabs-content-holder, .ant-tabs-left > div > .ant-tabs-content-holder { margin-left: -1px; border-left: 1px solid #303030; } .ant-tabs-left > .ant-tabs-content-holder > .ant-tabs-content > .ant-tabs-tabpane, .ant-tabs-left > div > .ant-tabs-content-holder > .ant-tabs-content > .ant-tabs-tabpane { padding-left: 24px; } .ant-tabs-right > .ant-tabs-nav, .ant-tabs-right > div > .ant-tabs-nav { order: 1; } .ant-tabs-right > .ant-tabs-nav .ant-tabs-ink-bar, .ant-tabs-right > div > .ant-tabs-nav .ant-tabs-ink-bar { left: 0; } .ant-tabs-right > .ant-tabs-content-holder, .ant-tabs-right > div > .ant-tabs-content-holder { order: 0; margin-right: -1px; border-right: 1px solid #303030; } .ant-tabs-right > .ant-tabs-content-holder > .ant-tabs-content > .ant-tabs-tabpane, .ant-tabs-right > div > .ant-tabs-content-holder > .ant-tabs-content > .ant-tabs-tabpane { padding-right: 24px; } .ant-tabs-dropdown { box-sizing: border-box; margin: 0; padding: 0; color: rgba(255, 255, 255, 0.85); font-size: 14px; font-variant: tabular-nums; line-height: 1.5715; list-style: none; font-feature-settings: 'tnum'; position: absolute; top: -9999px; left: -9999px; z-index: 1050; display: block; } .ant-tabs-dropdown-hidden { display: none; } .ant-tabs-dropdown-menu { max-height: 200px; margin: 0; padding: 4px 0; overflow-x: hidden; overflow-y: auto; text-align: left; list-style-type: none; background-color: #1f1f1f; background-clip: padding-box; border-radius: 2px; outline: none; box-shadow: 0 3px 6px -4px rgba(0, 0, 0, 0.48), 0 6px 16px 0 rgba(0, 0, 0, 0.32), 0 9px 28px 8px rgba(0, 0, 0, 0.2); } .ant-tabs-dropdown-menu-item { display: flex; align-items: center; min-width: 120px; margin: 0; padding: 5px 12px; overflow: hidden; color: rgba(255, 255, 255, 0.85); font-weight: normal; font-size: 14px; line-height: 22px; white-space: nowrap; text-overflow: ellipsis; cursor: pointer; transition: all 0.3s; } .ant-tabs-dropdown-menu-item > span { flex: 1; white-space: nowrap; } .ant-tabs-dropdown-menu-item-remove { flex: none; margin-left: 12px; color: rgba(255, 255, 255, 0.45); font-size: 12px; background: transparent; border: 0; cursor: pointer; } .ant-tabs-dropdown-menu-item-remove:hover { color: #165996; } .ant-tabs-dropdown-menu-item:hover { background: rgba(255, 255, 255, 0.08); } .ant-tabs-dropdown-menu-item-disabled, .ant-tabs-dropdown-menu-item-disabled:hover { color: rgba(255, 255, 255, 0.3); background: transparent; cursor: not-allowed; } .ant-tabs-card > .ant-tabs-nav .ant-tabs-tab, .ant-tabs-card > div > .ant-tabs-nav .ant-tabs-tab { margin: 0; padding: 8px 16px; background: rgba(255, 255, 255, 0.04); border: 1px solid #303030; transition: all 0.3s cubic-bezier(0.645, 0.045, 0.355, 1); } .ant-tabs-card > .ant-tabs-nav .ant-tabs-tab-active, .ant-tabs-card > div > .ant-tabs-nav .ant-tabs-tab-active { color: #177ddc; background: #141414; } .ant-tabs-card > .ant-tabs-nav .ant-tabs-ink-bar, .ant-tabs-card > div > .ant-tabs-nav .ant-tabs-ink-bar { visibility: hidden; } .ant-tabs-card.ant-tabs-top > .ant-tabs-nav .ant-tabs-tab + .ant-tabs-tab, .ant-tabs-card.ant-tabs-bottom > .ant-tabs-nav .ant-tabs-tab + .ant-tabs-tab, .ant-tabs-card.ant-tabs-top > div > .ant-tabs-nav .ant-tabs-tab + .ant-tabs-tab, .ant-tabs-card.ant-tabs-bottom > div > .ant-tabs-nav .ant-tabs-tab + .ant-tabs-tab { margin-left: 2px; } .ant-tabs-card.ant-tabs-top > .ant-tabs-nav .ant-tabs-tab, .ant-tabs-card.ant-tabs-top > div > .ant-tabs-nav .ant-tabs-tab { border-radius: 2px 2px 0 0; } .ant-tabs-card.ant-tabs-top > .ant-tabs-nav .ant-tabs-tab-active, .ant-tabs-card.ant-tabs-top > div > .ant-tabs-nav .ant-tabs-tab-active { border-bottom-color: #141414; } .ant-tabs-card.ant-tabs-bottom > .ant-tabs-nav .ant-tabs-tab, .ant-tabs-card.ant-tabs-bottom > div > .ant-tabs-nav .ant-tabs-tab { border-radius: 0 0 2px 2px; } .ant-tabs-card.ant-tabs-bottom > .ant-tabs-nav .ant-tabs-tab-active, .ant-tabs-card.ant-tabs-bottom > div > .ant-tabs-nav .ant-tabs-tab-active { border-top-color: #141414; } .ant-tabs-card.ant-tabs-left > .ant-tabs-nav .ant-tabs-tab + .ant-tabs-tab, .ant-tabs-card.ant-tabs-right > .ant-tabs-nav .ant-tabs-tab + .ant-tabs-tab, .ant-tabs-card.ant-tabs-left > div > .ant-tabs-nav .ant-tabs-tab + .ant-tabs-tab, .ant-tabs-card.ant-tabs-right > div > .ant-tabs-nav .ant-tabs-tab + .ant-tabs-tab { margin-top: 2px; } .ant-tabs-card.ant-tabs-left > .ant-tabs-nav .ant-tabs-tab, .ant-tabs-card.ant-tabs-left > div > .ant-tabs-nav .ant-tabs-tab { border-radius: 2px 0 0 2px; } .ant-tabs-card.ant-tabs-left > .ant-tabs-nav .ant-tabs-tab-active, .ant-tabs-card.ant-tabs-left > div > .ant-tabs-nav .ant-tabs-tab-active { border-right-color: #141414; } .ant-tabs-card.ant-tabs-right > .ant-tabs-nav .ant-tabs-tab, .ant-tabs-card.ant-tabs-right > div > .ant-tabs-nav .ant-tabs-tab { border-radius: 0 2px 2px 0; } .ant-tabs-card.ant-tabs-right > .ant-tabs-nav .ant-tabs-tab-active, .ant-tabs-card.ant-tabs-right > div > .ant-tabs-nav .ant-tabs-tab-active { border-left-color: #141414; } .ant-tabs { box-sizing: border-box; margin: 0; padding: 0; color: rgba(255, 255, 255, 0.85); font-size: 14px; font-variant: tabular-nums; line-height: 1.5715; list-style: none; font-feature-settings: 'tnum'; display: flex; } .ant-tabs > .ant-tabs-nav, .ant-tabs > div > .ant-tabs-nav { position: relative; display: flex; flex: none; align-items: center; } .ant-tabs > .ant-tabs-nav .ant-tabs-nav-wrap, .ant-tabs > div > .ant-tabs-nav .ant-tabs-nav-wrap { position: relative; display: inline-block; display: flex; flex: auto; align-self: stretch; overflow: hidden; white-space: nowrap; transform: translate(0); } .ant-tabs > .ant-tabs-nav .ant-tabs-nav-wrap::before, .ant-tabs > div > .ant-tabs-nav .ant-tabs-nav-wrap::before, .ant-tabs > .ant-tabs-nav .ant-tabs-nav-wrap::after, .ant-tabs > div > .ant-tabs-nav .ant-tabs-nav-wrap::after { position: absolute; z-index: 1; opacity: 0; transition: opacity 0.3s; content: ''; pointer-events: none; } .ant-tabs > .ant-tabs-nav .ant-tabs-nav-list, .ant-tabs > div > .ant-tabs-nav .ant-tabs-nav-list { position: relative; display: flex; transition: transform 0.3s; } .ant-tabs > .ant-tabs-nav .ant-tabs-nav-operations, .ant-tabs > div > .ant-tabs-nav .ant-tabs-nav-operations { display: flex; align-self: stretch; } .ant-tabs > .ant-tabs-nav .ant-tabs-nav-operations-hidden, .ant-tabs > div > .ant-tabs-nav .ant-tabs-nav-operations-hidden { position: absolute; visibility: hidden; pointer-events: none; } .ant-tabs > .ant-tabs-nav .ant-tabs-nav-more, .ant-tabs > div > .ant-tabs-nav .ant-tabs-nav-more { position: relative; padding: 8px 16px; background: transparent; border: 0; } .ant-tabs > .ant-tabs-nav .ant-tabs-nav-more::after, .ant-tabs > div > .ant-tabs-nav .ant-tabs-nav-more::after { position: absolute; right: 0; bottom: 0; left: 0; height: 5px; transform: translateY(100%); content: ''; } .ant-tabs > .ant-tabs-nav .ant-tabs-nav-add, .ant-tabs > div > .ant-tabs-nav .ant-tabs-nav-add { min-width: 40px; margin-left: 2px; padding: 0 8px; background: rgba(255, 255, 255, 0.04); border: 1px solid #303030; border-radius: 2px 2px 0 0; outline: none; cursor: pointer; transition: all 0.3s cubic-bezier(0.645, 0.045, 0.355, 1); } .ant-tabs > .ant-tabs-nav .ant-tabs-nav-add:hover, .ant-tabs > div > .ant-tabs-nav .ant-tabs-nav-add:hover { color: #165996; } .ant-tabs > .ant-tabs-nav .ant-tabs-nav-add:active, .ant-tabs > div > .ant-tabs-nav .ant-tabs-nav-add:active, .ant-tabs > .ant-tabs-nav .ant-tabs-nav-add:focus, .ant-tabs > div > .ant-tabs-nav .ant-tabs-nav-add:focus { color: #388ed3; } .ant-tabs-extra-content { flex: none; } .ant-tabs-centered > .ant-tabs-nav .ant-tabs-nav-wrap:not([class*='ant-tabs-nav-wrap-ping']), .ant-tabs-centered > div > .ant-tabs-nav .ant-tabs-nav-wrap:not([class*='ant-tabs-nav-wrap-ping']) { justify-content: center; } .ant-tabs-ink-bar { position: absolute; background: #177ddc; pointer-events: none; } .ant-tabs-tab { position: relative; display: inline-flex; align-items: center; padding: 12px 0; font-size: 14px; background: transparent; border: 0; outline: none; cursor: pointer; } .ant-tabs-tab-btn:focus, .ant-tabs-tab-remove:focus, .ant-tabs-tab-btn:active, .ant-tabs-tab-remove:active { color: #388ed3; } .ant-tabs-tab-btn { outline: none; transition: all 0.3s; } .ant-tabs-tab-remove { flex: none; margin-right: -4px; margin-left: 8px; color: rgba(255, 255, 255, 0.45); font-size: 12px; background: transparent; border: none; outline: none; cursor: pointer; transition: all 0.3s; } .ant-tabs-tab-remove:hover { color: rgba(255, 255, 255, 0.85); } .ant-tabs-tab:hover { color: #165996; } .ant-tabs-tab.ant-tabs-tab-active .ant-tabs-tab-btn { color: #177ddc; text-shadow: 0 0 0.25px currentcolor; } .ant-tabs-tab.ant-tabs-tab-disabled { color: rgba(255, 255, 255, 0.3); cursor: not-allowed; } .ant-tabs-tab.ant-tabs-tab-disabled .ant-tabs-tab-btn:focus, .ant-tabs-tab.ant-tabs-tab-disabled .ant-tabs-tab-remove:focus, .ant-tabs-tab.ant-tabs-tab-disabled .ant-tabs-tab-btn:active, .ant-tabs-tab.ant-tabs-tab-disabled .ant-tabs-tab-remove:active { color: rgba(255, 255, 255, 0.3); } .ant-tabs-tab .ant-tabs-tab-remove .anticon { margin: 0; } .ant-tabs-tab .anticon { margin-right: 12px; } .ant-tabs-tab + .ant-tabs-tab { margin: 0 0 0 32px; } .ant-tabs-content { position: relative; width: 100%; } .ant-tabs-content-holder { flex: auto; min-width: 0; min-height: 0; } .ant-tabs-tabpane { outline: none; } .ant-tabs-tabpane-hidden { display: none; } .ant-tabs-switch-appear, .ant-tabs-switch-enter { transition: none; } .ant-tabs-switch-appear-start, .ant-tabs-switch-enter-start { opacity: 0; } .ant-tabs-switch-appear-active, .ant-tabs-switch-enter-active { opacity: 1; transition: opacity 0.3s; } .ant-tabs-switch-leave { position: absolute; transition: none; inset: 0; } .ant-tabs-switch-leave-start { opacity: 1; } .ant-tabs-switch-leave-active { opacity: 0; transition: opacity 0.3s; } .ant-tag { box-sizing: border-box; margin: 0; padding: 0; color: rgba(255, 255, 255, 0.85); font-size: 14px; font-variant: tabular-nums; line-height: 1.5715; list-style: none; font-feature-settings: 'tnum'; display: inline-block; height: auto; margin-right: 8px; padding: 0 7px; font-size: 12px; line-height: 20px; white-space: nowrap; background: rgba(255, 255, 255, 0.04); border: 1px solid #434343; border-radius: 2px; opacity: 1; transition: all 0.3s; } .ant-tag, .ant-tag a, .ant-tag a:hover { color: rgba(255, 255, 255, 0.85); } .ant-tag > a:first-child:last-child { display: inline-block; margin: 0 -8px; padding: 0 8px; } .ant-tag-close-icon { margin-left: 3px; color: rgba(255, 255, 255, 0.45); font-size: 10px; cursor: pointer; transition: all 0.3s; } .ant-tag-close-icon:hover { color: rgba(255, 255, 255, 0.85); } .ant-tag-has-color { border-color: transparent; } .ant-tag-has-color, .ant-tag-has-color a, .ant-tag-has-color a:hover, .ant-tag-has-color .anticon-close, .ant-tag-has-color .anticon-close:hover { color: #fff; } .ant-tag-checkable { background-color: transparent; border-color: transparent; cursor: pointer; } .ant-tag-checkable:not(.ant-tag-checkable-checked):hover { color: #177ddc; } .ant-tag-checkable:active, .ant-tag-checkable-checked { color: #fff; } .ant-tag-checkable-checked { background-color: #177ddc; } .ant-tag-checkable:active { background-color: #388ed3; } .ant-tag-hidden { display: none; } .ant-tag-pink { color: #e0529c; background: #291321; border-color: #551c3b; } .ant-tag-pink-inverse { color: #fff; background: #cb2b83; border-color: #cb2b83; } .ant-tag-magenta { color: #e0529c; background: #291321; border-color: #551c3b; } .ant-tag-magenta-inverse { color: #fff; background: #cb2b83; border-color: #cb2b83; } .ant-tag-red { color: #e84749; background: #2a1215; border-color: #58181c; } .ant-tag-red-inverse { color: #fff; background: #d32029; border-color: #d32029; } .ant-tag-volcano { color: #e87040; background: #2b1611; border-color: #592716; } .ant-tag-volcano-inverse { color: #fff; background: #d84a1b; border-color: #d84a1b; } .ant-tag-orange { color: #e89a3c; background: #2b1d11; border-color: #593815; } .ant-tag-orange-inverse { color: #fff; background: #d87a16; border-color: #d87a16; } .ant-tag-yellow { color: #e8d639; background: #2b2611; border-color: #595014; } .ant-tag-yellow-inverse { color: #fff; background: #d8bd14; border-color: #d8bd14; } .ant-tag-gold { color: #e8b339; background: #2b2111; border-color: #594214; } .ant-tag-gold-inverse { color: #fff; background: #d89614; border-color: #d89614; } .ant-tag-cyan { color: #33bcb7; background: #112123; border-color: #144848; } .ant-tag-cyan-inverse { color: #fff; background: #13a8a8; border-color: #13a8a8; } .ant-tag-lime { color: #a9d134; background: #1f2611; border-color: #3e4f13; } .ant-tag-lime-inverse { color: #fff; background: #8bbb11; border-color: #8bbb11; } .ant-tag-green { color: #6abe39; background: #162312; border-color: #274916; } .ant-tag-green-inverse { color: #fff; background: #49aa19; border-color: #49aa19; } .ant-tag-blue { color: #3c9ae8; background: #111d2c; border-color: #15395b; } .ant-tag-blue-inverse { color: #fff; background: #177ddc; border-color: #177ddc; } .ant-tag-geekblue { color: #5273e0; background: #131629; border-color: #1c2755; } .ant-tag-geekblue-inverse { color: #fff; background: #2b4acb; border-color: #2b4acb; } .ant-tag-purple { color: #854eca; background: #1a1325; border-color: #301c4d; } .ant-tag-purple-inverse { color: #fff; background: #642ab5; border-color: #642ab5; } .ant-tag-success { color: #49aa19; background: #162312; border-color: #274916; } .ant-tag-processing { color: #177ddc; background: #111b26; border-color: #153450; } .ant-tag-error { color: #a61d24; background: #2a1215; border-color: #58181c; } .ant-tag-warning { color: #d89614; background: #2b1d11; border-color: #593815; } .ant-tag > .anticon + span, .ant-tag > span + .anticon { margin-left: 7px; } .ant-tag.ant-tag-rtl { margin-right: 0; margin-left: 8px; direction: rtl; text-align: right; } .ant-tag-rtl .ant-tag-close-icon { margin-right: 3px; margin-left: 0; } .ant-tag-rtl.ant-tag > .anticon + span, .ant-tag-rtl.ant-tag > span + .anticon { margin-right: 7px; margin-left: 0; } .ant-timeline { box-sizing: border-box; color: rgba(255, 255, 255, 0.85); font-size: 14px; font-variant: tabular-nums; line-height: 1.5715; font-feature-settings: 'tnum'; margin: 0; padding: 0; list-style: none; } .ant-timeline-item { position: relative; margin: 0; padding-bottom: 20px; font-size: 14px; list-style: none; } .ant-timeline-item-tail { position: absolute; top: 10px; left: 4px; height: calc(100% - 10px); border-left: 2px solid #303030; } .ant-timeline-item-pending .ant-timeline-item-head { font-size: 12px; background-color: transparent; } .ant-timeline-item-pending .ant-timeline-item-tail { display: none; } .ant-timeline-item-head { position: absolute; width: 10px; height: 10px; background-color: #141414; border: 2px solid transparent; border-radius: 100px; } .ant-timeline-item-head-blue { color: #177ddc; border-color: #177ddc; } .ant-timeline-item-head-red { color: #a61d24; border-color: #a61d24; } .ant-timeline-item-head-green { color: #49aa19; border-color: #49aa19; } .ant-timeline-item-head-gray { color: rgba(255, 255, 255, 0.3); border-color: rgba(255, 255, 255, 0.3); } .ant-timeline-item-head-custom { position: absolute; top: 5.5px; left: 5px; width: auto; height: auto; margin-top: 0; padding: 3px 1px; line-height: 1; text-align: center; border: 0; border-radius: 0; transform: translate(-50%, -50%); } .ant-timeline-item-content { position: relative; top: -7.001px; margin: 0 0 0 26px; word-break: break-word; } .ant-timeline-item-last > .ant-timeline-item-tail { display: none; } .ant-timeline-item-last > .ant-timeline-item-content { min-height: 48px; } .ant-timeline.ant-timeline-alternate .ant-timeline-item-tail, .ant-timeline.ant-timeline-right .ant-timeline-item-tail, .ant-timeline.ant-timeline-label .ant-timeline-item-tail, .ant-timeline.ant-timeline-alternate .ant-timeline-item-head, .ant-timeline.ant-timeline-right .ant-timeline-item-head, .ant-timeline.ant-timeline-label .ant-timeline-item-head, .ant-timeline.ant-timeline-alternate .ant-timeline-item-head-custom, .ant-timeline.ant-timeline-right .ant-timeline-item-head-custom, .ant-timeline.ant-timeline-label .ant-timeline-item-head-custom { left: 50%; } .ant-timeline.ant-timeline-alternate .ant-timeline-item-head, .ant-timeline.ant-timeline-right .ant-timeline-item-head, .ant-timeline.ant-timeline-label .ant-timeline-item-head { margin-left: -4px; } .ant-timeline.ant-timeline-alternate .ant-timeline-item-head-custom, .ant-timeline.ant-timeline-right .ant-timeline-item-head-custom, .ant-timeline.ant-timeline-label .ant-timeline-item-head-custom { margin-left: 1px; } .ant-timeline.ant-timeline-alternate .ant-timeline-item-left .ant-timeline-item-content, .ant-timeline.ant-timeline-right .ant-timeline-item-left .ant-timeline-item-content, .ant-timeline.ant-timeline-label .ant-timeline-item-left .ant-timeline-item-content { left: calc(50% - 4px); width: calc(50% - 14px); text-align: left; } .ant-timeline.ant-timeline-alternate .ant-timeline-item-right .ant-timeline-item-content, .ant-timeline.ant-timeline-right .ant-timeline-item-right .ant-timeline-item-content, .ant-timeline.ant-timeline-label .ant-timeline-item-right .ant-timeline-item-content { width: calc(50% - 12px); margin: 0; text-align: right; } .ant-timeline.ant-timeline-right .ant-timeline-item-right .ant-timeline-item-tail, .ant-timeline.ant-timeline-right .ant-timeline-item-right .ant-timeline-item-head, .ant-timeline.ant-timeline-right .ant-timeline-item-right .ant-timeline-item-head-custom { left: calc(100% - 4px - 2px); } .ant-timeline.ant-timeline-right .ant-timeline-item-right .ant-timeline-item-content { width: calc(100% - 18px); } .ant-timeline.ant-timeline-pending .ant-timeline-item-last .ant-timeline-item-tail { display: block; height: calc(100% - 14px); border-left: 2px dotted #303030; } .ant-timeline.ant-timeline-reverse .ant-timeline-item-last .ant-timeline-item-tail { display: none; } .ant-timeline.ant-timeline-reverse .ant-timeline-item-pending .ant-timeline-item-tail { top: 15px; display: block; height: calc(100% - 15px); border-left: 2px dotted #303030; } .ant-timeline.ant-timeline-reverse .ant-timeline-item-pending .ant-timeline-item-content { min-height: 48px; } .ant-timeline.ant-timeline-label .ant-timeline-item-label { position: absolute; top: -7.001px; width: calc(50% - 12px); text-align: right; } .ant-timeline.ant-timeline-label .ant-timeline-item-right .ant-timeline-item-label { left: calc(50% + 14px); width: calc(50% - 14px); text-align: left; } .ant-timeline-rtl { direction: rtl; } .ant-timeline-rtl .ant-timeline-item-tail { right: 4px; left: auto; border-right: 2px solid #303030; border-left: none; } .ant-timeline-rtl .ant-timeline-item-head-custom { right: 5px; left: auto; transform: translate(50%, -50%); } .ant-timeline-rtl .ant-timeline-item-content { margin: 0 18px 0 0; } .ant-timeline-rtl.ant-timeline.ant-timeline-alternate .ant-timeline-item-tail, .ant-timeline-rtl.ant-timeline.ant-timeline-right .ant-timeline-item-tail, .ant-timeline-rtl.ant-timeline.ant-timeline-label .ant-timeline-item-tail, .ant-timeline-rtl.ant-timeline.ant-timeline-alternate .ant-timeline-item-head, .ant-timeline-rtl.ant-timeline.ant-timeline-right .ant-timeline-item-head, .ant-timeline-rtl.ant-timeline.ant-timeline-label .ant-timeline-item-head, .ant-timeline-rtl.ant-timeline.ant-timeline-alternate .ant-timeline-item-head-custom, .ant-timeline-rtl.ant-timeline.ant-timeline-right .ant-timeline-item-head-custom, .ant-timeline-rtl.ant-timeline.ant-timeline-label .ant-timeline-item-head-custom { right: 50%; left: auto; } .ant-timeline-rtl.ant-timeline.ant-timeline-alternate .ant-timeline-item-head, .ant-timeline-rtl.ant-timeline.ant-timeline-right .ant-timeline-item-head, .ant-timeline-rtl.ant-timeline.ant-timeline-label .ant-timeline-item-head { margin-right: -4px; margin-left: 0; } .ant-timeline-rtl.ant-timeline.ant-timeline-alternate .ant-timeline-item-head-custom, .ant-timeline-rtl.ant-timeline.ant-timeline-right .ant-timeline-item-head-custom, .ant-timeline-rtl.ant-timeline.ant-timeline-label .ant-timeline-item-head-custom { margin-right: 1px; margin-left: 0; } .ant-timeline-rtl.ant-timeline.ant-timeline-alternate .ant-timeline-item-left .ant-timeline-item-content, .ant-timeline-rtl.ant-timeline.ant-timeline-right .ant-timeline-item-left .ant-timeline-item-content, .ant-timeline-rtl.ant-timeline.ant-timeline-label .ant-timeline-item-left .ant-timeline-item-content { right: calc(50% - 4px); left: auto; text-align: right; } .ant-timeline-rtl.ant-timeline.ant-timeline-alternate .ant-timeline-item-right .ant-timeline-item-content, .ant-timeline-rtl.ant-timeline.ant-timeline-right .ant-timeline-item-right .ant-timeline-item-content, .ant-timeline-rtl.ant-timeline.ant-timeline-label .ant-timeline-item-right .ant-timeline-item-content { text-align: left; } .ant-timeline-rtl.ant-timeline.ant-timeline-right .ant-timeline-item-right .ant-timeline-item-tail, .ant-timeline-rtl.ant-timeline.ant-timeline-right .ant-timeline-item-right .ant-timeline-item-head, .ant-timeline-rtl.ant-timeline.ant-timeline-right .ant-timeline-item-right .ant-timeline-item-head-custom { right: 0; left: auto; } .ant-timeline-rtl.ant-timeline.ant-timeline-right .ant-timeline-item-right .ant-timeline-item-content { width: 100%; margin-right: 18px; text-align: right; } .ant-timeline-rtl.ant-timeline.ant-timeline-pending .ant-timeline-item-last .ant-timeline-item-tail { border-right: 2px dotted #303030; border-left: none; } .ant-timeline-rtl.ant-timeline.ant-timeline-reverse .ant-timeline-item-pending .ant-timeline-item-tail { border-right: 2px dotted #303030; border-left: none; } .ant-timeline-rtl.ant-timeline.ant-timeline-label .ant-timeline-item-label { text-align: left; } .ant-timeline-rtl.ant-timeline.ant-timeline-label .ant-timeline-item-right .ant-timeline-item-label { right: calc(50% + 14px); text-align: right; } .ant-tooltip { box-sizing: border-box; margin: 0; padding: 0; color: rgba(255, 255, 255, 0.85); font-size: 14px; font-variant: tabular-nums; line-height: 1.5715; list-style: none; font-feature-settings: 'tnum'; position: absolute; z-index: 1070; display: block; width: max-content; width: intrinsic; max-width: 250px; visibility: visible; } .ant-tooltip-content { position: relative; } .ant-tooltip-hidden { display: none; } .ant-tooltip-placement-top, .ant-tooltip-placement-topLeft, .ant-tooltip-placement-topRight { padding-bottom: 14.3137085px; } .ant-tooltip-placement-right, .ant-tooltip-placement-rightTop, .ant-tooltip-placement-rightBottom { padding-left: 14.3137085px; } .ant-tooltip-placement-bottom, .ant-tooltip-placement-bottomLeft, .ant-tooltip-placement-bottomRight { padding-top: 14.3137085px; } .ant-tooltip-placement-left, .ant-tooltip-placement-leftTop, .ant-tooltip-placement-leftBottom { padding-right: 14.3137085px; } .ant-tooltip-inner { min-width: 30px; min-height: 32px; padding: 6px 8px; color: #fff; text-align: left; text-decoration: none; word-wrap: break-word; background-color: #434343; border-radius: 2px; box-shadow: 0 3px 6px -4px rgba(0, 0, 0, 0.48), 0 6px 16px 0 rgba(0, 0, 0, 0.32), 0 9px 28px 8px rgba(0, 0, 0, 0.2); } .ant-tooltip-arrow { position: absolute; z-index: 2; display: block; width: 22px; height: 22px; overflow: hidden; background: transparent; pointer-events: none; } .ant-tooltip-arrow-content { --antd-arrow-background-color: linear-gradient(to right bottom, rgba(67, 67, 67, 0.9), #434343); position: absolute; top: 0; right: 0; bottom: 0; left: 0; display: block; width: 11.3137085px; height: 11.3137085px; margin: auto; content: ''; pointer-events: auto; border-radius: 0 0 2px; pointer-events: none; } .ant-tooltip-arrow-content::before { position: absolute; top: -11.3137085px; left: -11.3137085px; width: 33.9411255px; height: 33.9411255px; background: var(--antd-arrow-background-color); background-repeat: no-repeat; background-position: -10px -10px; content: ''; clip-path: inset(33% 33%); clip-path: path('M 9.849242404917499 24.091883092036785 A 5 5 0 0 1 13.384776310850237 22.627416997969522 L 20.627416997969522 22.627416997969522 A 2 2 0 0 0 22.627416997969522 20.627416997969522 L 22.627416997969522 13.384776310850237 A 5 5 0 0 1 24.091883092036785 9.849242404917499 L 23.091883092036785 9.849242404917499 L 9.849242404917499 23.091883092036785 Z'); } .ant-tooltip-placement-top .ant-tooltip-arrow, .ant-tooltip-placement-topLeft .ant-tooltip-arrow, .ant-tooltip-placement-topRight .ant-tooltip-arrow { bottom: 0; transform: translateY(100%); } .ant-tooltip-placement-top .ant-tooltip-arrow-content, .ant-tooltip-placement-topLeft .ant-tooltip-arrow-content, .ant-tooltip-placement-topRight .ant-tooltip-arrow-content { box-shadow: 3px 3px 7px rgba(0, 0, 0, 0.07); transform: translateY(-11px) rotate(45deg); } .ant-tooltip-placement-top .ant-tooltip-arrow { left: 50%; transform: translateY(100%) translateX(-50%); } .ant-tooltip-placement-topLeft .ant-tooltip-arrow { left: 13px; } .ant-tooltip-placement-topRight .ant-tooltip-arrow { right: 13px; } .ant-tooltip-placement-right .ant-tooltip-arrow, .ant-tooltip-placement-rightTop .ant-tooltip-arrow, .ant-tooltip-placement-rightBottom .ant-tooltip-arrow { left: 0; transform: translateX(-100%); } .ant-tooltip-placement-right .ant-tooltip-arrow-content, .ant-tooltip-placement-rightTop .ant-tooltip-arrow-content, .ant-tooltip-placement-rightBottom .ant-tooltip-arrow-content { box-shadow: -3px 3px 7px rgba(0, 0, 0, 0.07); transform: translateX(11px) rotate(135deg); } .ant-tooltip-placement-right .ant-tooltip-arrow { top: 50%; transform: translateX(-100%) translateY(-50%); } .ant-tooltip-placement-rightTop .ant-tooltip-arrow { top: 5px; } .ant-tooltip-placement-rightBottom .ant-tooltip-arrow { bottom: 5px; } .ant-tooltip-placement-left .ant-tooltip-arrow, .ant-tooltip-placement-leftTop .ant-tooltip-arrow, .ant-tooltip-placement-leftBottom .ant-tooltip-arrow { right: 0; transform: translateX(100%); } .ant-tooltip-placement-left .ant-tooltip-arrow-content, .ant-tooltip-placement-leftTop .ant-tooltip-arrow-content, .ant-tooltip-placement-leftBottom .ant-tooltip-arrow-content { box-shadow: 3px -3px 7px rgba(0, 0, 0, 0.07); transform: translateX(-11px) rotate(315deg); } .ant-tooltip-placement-left .ant-tooltip-arrow { top: 50%; transform: translateX(100%) translateY(-50%); } .ant-tooltip-placement-leftTop .ant-tooltip-arrow { top: 5px; } .ant-tooltip-placement-leftBottom .ant-tooltip-arrow { bottom: 5px; } .ant-tooltip-placement-bottom .ant-tooltip-arrow, .ant-tooltip-placement-bottomLeft .ant-tooltip-arrow, .ant-tooltip-placement-bottomRight .ant-tooltip-arrow { top: 0; transform: translateY(-100%); } .ant-tooltip-placement-bottom .ant-tooltip-arrow-content, .ant-tooltip-placement-bottomLeft .ant-tooltip-arrow-content, .ant-tooltip-placement-bottomRight .ant-tooltip-arrow-content { box-shadow: -3px -3px 7px rgba(0, 0, 0, 0.07); transform: translateY(11px) rotate(225deg); } .ant-tooltip-placement-bottom .ant-tooltip-arrow { left: 50%; transform: translateY(-100%) translateX(-50%); } .ant-tooltip-placement-bottomLeft .ant-tooltip-arrow { left: 13px; } .ant-tooltip-placement-bottomRight .ant-tooltip-arrow { right: 13px; } .ant-tooltip-pink .ant-tooltip-inner { background-color: #cb2b83; } .ant-tooltip-pink .ant-tooltip-arrow-content::before { background: #cb2b83; } .ant-tooltip-magenta .ant-tooltip-inner { background-color: #cb2b83; } .ant-tooltip-magenta .ant-tooltip-arrow-content::before { background: #cb2b83; } .ant-tooltip-red .ant-tooltip-inner { background-color: #d32029; } .ant-tooltip-red .ant-tooltip-arrow-content::before { background: #d32029; } .ant-tooltip-volcano .ant-tooltip-inner { background-color: #d84a1b; } .ant-tooltip-volcano .ant-tooltip-arrow-content::before { background: #d84a1b; } .ant-tooltip-orange .ant-tooltip-inner { background-color: #d87a16; } .ant-tooltip-orange .ant-tooltip-arrow-content::before { background: #d87a16; } .ant-tooltip-yellow .ant-tooltip-inner { background-color: #d8bd14; } .ant-tooltip-yellow .ant-tooltip-arrow-content::before { background: #d8bd14; } .ant-tooltip-gold .ant-tooltip-inner { background-color: #d89614; } .ant-tooltip-gold .ant-tooltip-arrow-content::before { background: #d89614; } .ant-tooltip-cyan .ant-tooltip-inner { background-color: #13a8a8; } .ant-tooltip-cyan .ant-tooltip-arrow-content::before { background: #13a8a8; } .ant-tooltip-lime .ant-tooltip-inner { background-color: #8bbb11; } .ant-tooltip-lime .ant-tooltip-arrow-content::before { background: #8bbb11; } .ant-tooltip-green .ant-tooltip-inner { background-color: #49aa19; } .ant-tooltip-green .ant-tooltip-arrow-content::before { background: #49aa19; } .ant-tooltip-blue .ant-tooltip-inner { background-color: #177ddc; } .ant-tooltip-blue .ant-tooltip-arrow-content::before { background: #177ddc; } .ant-tooltip-geekblue .ant-tooltip-inner { background-color: #2b4acb; } .ant-tooltip-geekblue .ant-tooltip-arrow-content::before { background: #2b4acb; } .ant-tooltip-purple .ant-tooltip-inner { background-color: #642ab5; } .ant-tooltip-purple .ant-tooltip-arrow-content::before { background: #642ab5; } .ant-tooltip-rtl { direction: rtl; } .ant-tooltip-rtl .ant-tooltip-inner { text-align: right; } .ant-transfer-customize-list .ant-transfer-list { flex: 1 1 50%; width: auto; height: auto; min-height: 200px; } .ant-transfer-customize-list .ant-table-wrapper .ant-table-small { border: 0; border-radius: 0; } .ant-transfer-customize-list .ant-table-wrapper .ant-table-small .ant-table-selection-column { width: 40px; min-width: 40px; } .ant-transfer-customize-list .ant-table-wrapper .ant-table-small > .ant-table-content > .ant-table-body > table > .ant-table-thead > tr > th { background: #1d1d1d; } .ant-transfer-customize-list .ant-table-wrapper .ant-table-small > .ant-table-content .ant-table-row:last-child td { border-bottom: 1px solid #303030; } .ant-transfer-customize-list .ant-table-wrapper .ant-table-small .ant-table-body { margin: 0; } .ant-transfer-customize-list .ant-table-wrapper .ant-table-pagination.ant-pagination { margin: 16px 0 4px; } .ant-transfer-customize-list .ant-input[disabled] { background-color: transparent; } .ant-transfer-status-error .ant-transfer-list { border-color: #a61d24; } .ant-transfer-status-error .ant-transfer-list-search:not([disabled]) { border-color: #434343; } .ant-transfer-status-error .ant-transfer-list-search:not([disabled]):hover { border-color: #165996; border-right-width: 1px; } .ant-input-rtl .ant-transfer-status-error .ant-transfer-list-search:not([disabled]):hover { border-right-width: 0; border-left-width: 1px !important; } .ant-transfer-status-error .ant-transfer-list-search:not([disabled]):focus { border-color: #177ddc; box-shadow: 0 0 0 2px rgba(23, 125, 220, 0.2); border-right-width: 1px; outline: 0; } .ant-input-rtl .ant-transfer-status-error .ant-transfer-list-search:not([disabled]):focus { border-right-width: 0; border-left-width: 1px !important; } .ant-transfer-status-warning .ant-transfer-list { border-color: #d89614; } .ant-transfer-status-warning .ant-transfer-list-search:not([disabled]) { border-color: #434343; } .ant-transfer-status-warning .ant-transfer-list-search:not([disabled]):hover { border-color: #165996; border-right-width: 1px; } .ant-input-rtl .ant-transfer-status-warning .ant-transfer-list-search:not([disabled]):hover { border-right-width: 0; border-left-width: 1px !important; } .ant-transfer-status-warning .ant-transfer-list-search:not([disabled]):focus { border-color: #177ddc; box-shadow: 0 0 0 2px rgba(23, 125, 220, 0.2); border-right-width: 1px; outline: 0; } .ant-input-rtl .ant-transfer-status-warning .ant-transfer-list-search:not([disabled]):focus { border-right-width: 0; border-left-width: 1px !important; } .ant-transfer { box-sizing: border-box; margin: 0; padding: 0; color: rgba(255, 255, 255, 0.85); font-size: 14px; font-variant: tabular-nums; line-height: 1.5715; list-style: none; font-feature-settings: 'tnum'; position: relative; display: flex; align-items: stretch; } .ant-transfer-disabled .ant-transfer-list { background: rgba(255, 255, 255, 0.08); } .ant-transfer-list { display: flex; flex-direction: column; width: 180px; height: 200px; border: 1px solid #434343; border-radius: 2px; } .ant-transfer-list-with-pagination { width: 250px; height: auto; } .ant-transfer-list-search .anticon-search { color: rgba(255, 255, 255, 0.3); } .ant-transfer-list-header { display: flex; flex: none; align-items: center; height: 40px; padding: 8px 12px 9px; color: rgba(255, 255, 255, 0.85); background: #141414; border-bottom: 1px solid #303030; border-radius: 2px 2px 0 0; } .ant-transfer-list-header > *:not(:last-child) { margin-right: 4px; } .ant-transfer-list-header > * { flex: none; } .ant-transfer-list-header-title { flex: auto; overflow: hidden; white-space: nowrap; text-align: right; text-overflow: ellipsis; } .ant-transfer-list-header-dropdown { font-size: 10px; transform: translateY(10%); cursor: pointer; } .ant-transfer-list-header-dropdown[disabled] { cursor: not-allowed; } .ant-transfer-list-body { display: flex; flex: auto; flex-direction: column; overflow: hidden; font-size: 14px; } .ant-transfer-list-body-search-wrapper { position: relative; flex: none; padding: 12px; } .ant-transfer-list-content { flex: auto; margin: 0; padding: 0; overflow: auto; list-style: none; } .ant-transfer-list-content-item { display: flex; align-items: center; min-height: 32px; padding: 6px 12px; line-height: 20px; transition: all 0.3s; } .ant-transfer-list-content-item > *:not(:last-child) { margin-right: 8px; } .ant-transfer-list-content-item > * { flex: none; } .ant-transfer-list-content-item-text { flex: auto; overflow: hidden; white-space: nowrap; text-overflow: ellipsis; } .ant-transfer-list-content-item-remove { position: relative; color: #434343; cursor: pointer; transition: all 0.3s; } .ant-transfer-list-content-item-remove:hover { color: #165996; } .ant-transfer-list-content-item-remove::after { position: absolute; top: -6px; right: -50%; bottom: -6px; left: -50%; content: ''; } .ant-transfer-list-content-item:not(.ant-transfer-list-content-item-disabled):hover { background-color: #262626; cursor: pointer; } .ant-transfer-list-content-item:not(.ant-transfer-list-content-item-disabled).ant-transfer-list-content-item-checked:hover { background-color: #0e161f; } .ant-transfer-list-content-show-remove .ant-transfer-list-content-item:not(.ant-transfer-list-content-item-disabled):hover { background: transparent; cursor: default; } .ant-transfer-list-content-item-checked { background-color: #111b26; } .ant-transfer-list-content-item-disabled { color: rgba(255, 255, 255, 0.3); cursor: not-allowed; } .ant-transfer-list-pagination { padding: 8px 0; text-align: right; border-top: 1px solid #303030; } .ant-transfer-list-body-not-found { flex: none; width: 100%; margin: auto 0; color: rgba(255, 255, 255, 0.3); text-align: center; } .ant-transfer-list-footer { border-top: 1px solid #303030; } .ant-transfer-operation { display: flex; flex: none; flex-direction: column; align-self: center; margin: 0 8px; vertical-align: middle; } .ant-transfer-operation .ant-btn { display: block; } .ant-transfer-operation .ant-btn:first-child { margin-bottom: 4px; } .ant-transfer-operation .ant-btn .anticon { font-size: 12px; } .ant-transfer .ant-empty-image { max-height: -2px; } .ant-transfer-rtl { direction: rtl; } .ant-transfer-rtl .ant-transfer-list-search { padding-right: 8px; padding-left: 24px; } .ant-transfer-rtl .ant-transfer-list-search-action { right: auto; left: 12px; } .ant-transfer-rtl .ant-transfer-list-header > *:not(:last-child) { margin-right: 0; margin-left: 4px; } .ant-transfer-rtl .ant-transfer-list-header { right: 0; left: auto; } .ant-transfer-rtl .ant-transfer-list-header-title { text-align: left; } .ant-transfer-rtl .ant-transfer-list-content-item > *:not(:last-child) { margin-right: 0; margin-left: 8px; } .ant-transfer-rtl .ant-transfer-list-pagination { text-align: left; } .ant-transfer-rtl .ant-transfer-list-footer { right: 0; left: auto; } @keyframes ant-tree-node-fx-do-not-use { 0% { opacity: 0; } 100% { opacity: 1; } } .ant-tree.ant-tree-directory .ant-tree-treenode { position: relative; } .ant-tree.ant-tree-directory .ant-tree-treenode::before { position: absolute; top: 0; right: 0; bottom: 4px; left: 0; transition: background-color 0.3s; content: ''; pointer-events: none; } .ant-tree.ant-tree-directory .ant-tree-treenode:hover::before { background: rgba(255, 255, 255, 0.08); } .ant-tree.ant-tree-directory .ant-tree-treenode > * { z-index: 1; } .ant-tree.ant-tree-directory .ant-tree-treenode .ant-tree-switcher { transition: color 0.3s; } .ant-tree.ant-tree-directory .ant-tree-treenode .ant-tree-node-content-wrapper { border-radius: 0; user-select: none; } .ant-tree.ant-tree-directory .ant-tree-treenode .ant-tree-node-content-wrapper:hover { background: transparent; } .ant-tree.ant-tree-directory .ant-tree-treenode .ant-tree-node-content-wrapper.ant-tree-node-selected { color: #fff; background: transparent; } .ant-tree.ant-tree-directory .ant-tree-treenode-selected:hover::before, .ant-tree.ant-tree-directory .ant-tree-treenode-selected::before { background: #177ddc; } .ant-tree.ant-tree-directory .ant-tree-treenode-selected .ant-tree-switcher { color: #fff; } .ant-tree.ant-tree-directory .ant-tree-treenode-selected .ant-tree-node-content-wrapper { color: #fff; background: transparent; } .ant-tree-checkbox { box-sizing: border-box; margin: 0; padding: 0; color: rgba(255, 255, 255, 0.85); font-size: 14px; font-variant: tabular-nums; line-height: 1.5715; list-style: none; font-feature-settings: 'tnum'; position: relative; top: 0.2em; line-height: 1; white-space: nowrap; outline: none; cursor: pointer; } .ant-tree-checkbox-wrapper:hover .ant-tree-checkbox-inner, .ant-tree-checkbox:hover .ant-tree-checkbox-inner, .ant-tree-checkbox-input:focus + .ant-tree-checkbox-inner { border-color: #177ddc; } .ant-tree-checkbox-checked::after { position: absolute; top: 0; left: 0; width: 100%; height: 100%; border: 1px solid #177ddc; border-radius: 2px; visibility: hidden; animation: antCheckboxEffect 0.36s ease-in-out; animation-fill-mode: backwards; content: ''; } .ant-tree-checkbox:hover::after, .ant-tree-checkbox-wrapper:hover .ant-tree-checkbox::after { visibility: visible; } .ant-tree-checkbox-inner { position: relative; top: 0; left: 0; display: block; width: 16px; height: 16px; direction: ltr; background-color: transparent; border: 1px solid #434343; border-radius: 2px; border-collapse: separate; transition: all 0.3s; } .ant-tree-checkbox-inner::after { position: absolute; top: 50%; left: 21.5%; display: table; width: 5.71428571px; height: 9.14285714px; border: 2px solid #fff; border-top: 0; border-left: 0; transform: rotate(45deg) scale(0) translate(-50%, -50%); opacity: 0; transition: all 0.1s cubic-bezier(0.71, -0.46, 0.88, 0.6), opacity 0.1s; content: ' '; } .ant-tree-checkbox-input { position: absolute; top: 0; right: 0; bottom: 0; left: 0; z-index: 1; width: 100%; height: 100%; cursor: pointer; opacity: 0; } .ant-tree-checkbox-checked .ant-tree-checkbox-inner::after { position: absolute; display: table; border: 2px solid #fff; border-top: 0; border-left: 0; transform: rotate(45deg) scale(1) translate(-50%, -50%); opacity: 1; transition: all 0.2s cubic-bezier(0.12, 0.4, 0.29, 1.46) 0.1s; content: ' '; } .ant-tree-checkbox-checked .ant-tree-checkbox-inner { background-color: #177ddc; border-color: #177ddc; } .ant-tree-checkbox-disabled { cursor: not-allowed; } .ant-tree-checkbox-disabled.ant-tree-checkbox-checked .ant-tree-checkbox-inner::after { border-color: rgba(255, 255, 255, 0.3); animation-name: none; } .ant-tree-checkbox-disabled .ant-tree-checkbox-input { cursor: not-allowed; pointer-events: none; } .ant-tree-checkbox-disabled .ant-tree-checkbox-inner { background-color: rgba(255, 255, 255, 0.08); border-color: #434343 !important; } .ant-tree-checkbox-disabled .ant-tree-checkbox-inner::after { border-color: rgba(255, 255, 255, 0.08); border-collapse: separate; animation-name: none; } .ant-tree-checkbox-disabled + span { color: rgba(255, 255, 255, 0.3); cursor: not-allowed; } .ant-tree-checkbox-disabled:hover::after, .ant-tree-checkbox-wrapper:hover .ant-tree-checkbox-disabled::after { visibility: hidden; } .ant-tree-checkbox-wrapper { box-sizing: border-box; margin: 0; padding: 0; color: rgba(255, 255, 255, 0.85); font-size: 14px; font-variant: tabular-nums; line-height: 1.5715; list-style: none; font-feature-settings: 'tnum'; display: inline-flex; align-items: baseline; line-height: unset; cursor: pointer; } .ant-tree-checkbox-wrapper::after { display: inline-block; width: 0; overflow: hidden; content: '\a0'; } .ant-tree-checkbox-wrapper.ant-tree-checkbox-wrapper-disabled { cursor: not-allowed; } .ant-tree-checkbox-wrapper + .ant-tree-checkbox-wrapper { margin-left: 8px; } .ant-tree-checkbox-wrapper.ant-tree-checkbox-wrapper-in-form-item input[type='checkbox'] { width: 14px; height: 14px; } .ant-tree-checkbox + span { padding-right: 8px; padding-left: 8px; } .ant-tree-checkbox-group { box-sizing: border-box; margin: 0; padding: 0; color: rgba(255, 255, 255, 0.85); font-size: 14px; font-variant: tabular-nums; line-height: 1.5715; list-style: none; font-feature-settings: 'tnum'; display: inline-block; } .ant-tree-checkbox-group-item { margin-right: 8px; } .ant-tree-checkbox-group-item:last-child { margin-right: 0; } .ant-tree-checkbox-group-item + .ant-tree-checkbox-group-item { margin-left: 0; } .ant-tree-checkbox-indeterminate .ant-tree-checkbox-inner { background-color: transparent; border-color: #434343; } .ant-tree-checkbox-indeterminate .ant-tree-checkbox-inner::after { top: 50%; left: 50%; width: 8px; height: 8px; background-color: #177ddc; border: 0; transform: translate(-50%, -50%) scale(1); opacity: 1; content: ' '; } .ant-tree-checkbox-indeterminate.ant-tree-checkbox-disabled .ant-tree-checkbox-inner::after { background-color: rgba(255, 255, 255, 0.3); border-color: rgba(255, 255, 255, 0.3); } .ant-tree-checkbox-rtl { direction: rtl; } .ant-tree-checkbox-group-rtl .ant-tree-checkbox-group-item { margin-right: 0; margin-left: 8px; } .ant-tree-checkbox-group-rtl .ant-tree-checkbox-group-item:last-child { margin-left: 0 !important; } .ant-tree-checkbox-group-rtl .ant-tree-checkbox-group-item + .ant-tree-checkbox-group-item { margin-left: 8px; } .ant-tree { box-sizing: border-box; margin: 0; padding: 0; color: rgba(255, 255, 255, 0.85); font-size: 14px; font-variant: tabular-nums; line-height: 1.5715; list-style: none; font-feature-settings: 'tnum'; background: transparent; border-radius: 2px; transition: background-color 0.3s; } .ant-tree-focused:not(:hover):not(.ant-tree-active-focused) { background: #111b26; } .ant-tree-list-holder-inner { align-items: flex-start; } .ant-tree.ant-tree-block-node .ant-tree-list-holder-inner { align-items: stretch; } .ant-tree.ant-tree-block-node .ant-tree-list-holder-inner .ant-tree-node-content-wrapper { flex: auto; } .ant-tree.ant-tree-block-node .ant-tree-list-holder-inner .ant-tree-treenode.dragging { position: relative; } .ant-tree.ant-tree-block-node .ant-tree-list-holder-inner .ant-tree-treenode.dragging::after { position: absolute; top: 0; right: 0; bottom: 4px; left: 0; border: 1px solid #177ddc; opacity: 0; animation: ant-tree-node-fx-do-not-use 0.3s; animation-play-state: running; animation-fill-mode: forwards; content: ''; pointer-events: none; } .ant-tree .ant-tree-treenode { display: flex; align-items: flex-start; padding: 0 0 4px 0; outline: none; } .ant-tree .ant-tree-treenode-disabled .ant-tree-node-content-wrapper { color: rgba(255, 255, 255, 0.3); cursor: not-allowed; } .ant-tree .ant-tree-treenode-disabled .ant-tree-node-content-wrapper:hover { background: transparent; } .ant-tree .ant-tree-treenode-active .ant-tree-node-content-wrapper { background: rgba(255, 255, 255, 0.08); } .ant-tree .ant-tree-treenode:not(.ant-tree .ant-tree-treenode-disabled).filter-node .ant-tree-title { color: inherit; font-weight: 500; } .ant-tree .ant-tree-treenode-draggable .ant-tree-draggable-icon { width: 24px; line-height: 24px; text-align: center; visibility: visible; opacity: 0.2; transition: opacity 0.3s; } .ant-tree-treenode:hover .ant-tree .ant-tree-treenode-draggable .ant-tree-draggable-icon { opacity: 0.45; } .ant-tree .ant-tree-treenode-draggable.ant-tree-treenode-disabled .ant-tree-draggable-icon { visibility: hidden; } .ant-tree-indent { align-self: stretch; white-space: nowrap; user-select: none; } .ant-tree-indent-unit { display: inline-block; width: 24px; } .ant-tree-draggable-icon { visibility: hidden; } .ant-tree-switcher { position: relative; flex: none; align-self: stretch; width: 24px; margin: 0; line-height: 24px; text-align: center; cursor: pointer; user-select: none; } .ant-tree-switcher .ant-tree-switcher-icon, .ant-tree-switcher .ant-select-tree-switcher-icon { display: inline-block; font-size: 10px; vertical-align: baseline; } .ant-tree-switcher .ant-tree-switcher-icon svg, .ant-tree-switcher .ant-select-tree-switcher-icon svg { transition: transform 0.3s; } .ant-tree-switcher-noop { cursor: default; } .ant-tree-switcher_close .ant-tree-switcher-icon svg { transform: rotate(-90deg); } .ant-tree-switcher-loading-icon { color: #177ddc; } .ant-tree-switcher-leaf-line { position: relative; z-index: 1; display: inline-block; width: 100%; height: 100%; } .ant-tree-switcher-leaf-line::before { position: absolute; top: 0; right: 12px; bottom: -4px; margin-left: -1px; border-right: 1px solid #d9d9d9; content: ' '; } .ant-tree-switcher-leaf-line::after { position: absolute; width: 10px; height: 14px; border-bottom: 1px solid #d9d9d9; content: ' '; } .ant-tree-checkbox { top: initial; margin: 4px 8px 0 0; } .ant-tree .ant-tree-node-content-wrapper { position: relative; z-index: auto; min-height: 24px; margin: 0; padding: 0 4px; color: inherit; line-height: 24px; background: transparent; border-radius: 2px; cursor: pointer; transition: all 0.3s, border 0s, line-height 0s, box-shadow 0s; } .ant-tree .ant-tree-node-content-wrapper:hover { background-color: rgba(255, 255, 255, 0.08); } .ant-tree .ant-tree-node-content-wrapper.ant-tree-node-selected { background-color: #11263c; } .ant-tree .ant-tree-node-content-wrapper .ant-tree-iconEle { display: inline-block; width: 24px; height: 24px; line-height: 24px; text-align: center; vertical-align: top; } .ant-tree .ant-tree-node-content-wrapper .ant-tree-iconEle:empty { display: none; } .ant-tree-unselectable .ant-tree-node-content-wrapper:hover { background-color: transparent; } .ant-tree-node-content-wrapper { line-height: 24px; user-select: none; } .ant-tree-node-content-wrapper .ant-tree-drop-indicator { position: absolute; z-index: 1; height: 2px; background-color: #177ddc; border-radius: 1px; pointer-events: none; } .ant-tree-node-content-wrapper .ant-tree-drop-indicator::after { position: absolute; top: -3px; left: -6px; width: 8px; height: 8px; background-color: transparent; border: 2px solid #177ddc; border-radius: 50%; content: ''; } .ant-tree .ant-tree-treenode.drop-container > [draggable] { box-shadow: 0 0 0 2px #177ddc; } .ant-tree-show-line .ant-tree-indent-unit { position: relative; height: 100%; } .ant-tree-show-line .ant-tree-indent-unit::before { position: absolute; top: 0; right: 12px; bottom: -4px; border-right: 1px solid #434343; content: ''; } .ant-tree-show-line .ant-tree-indent-unit-end::before { display: none; } .ant-tree-show-line .ant-tree-switcher { background: #141414; } .ant-tree-show-line .ant-tree-switcher-line-icon { vertical-align: -0.15em; } .ant-tree .ant-tree-treenode-leaf-last .ant-tree-switcher-leaf-line::before { top: auto !important; bottom: auto !important; height: 14px !important; } .ant-tree-rtl { direction: rtl; } .ant-tree-rtl .ant-tree-node-content-wrapper[draggable='true'] .ant-tree-drop-indicator::after { right: -6px; left: unset; } .ant-tree .ant-tree-treenode-rtl { direction: rtl; } .ant-tree-rtl .ant-tree-switcher_close .ant-tree-switcher-icon svg { transform: rotate(90deg); } .ant-tree-rtl.ant-tree-show-line .ant-tree-indent-unit::before { right: auto; left: -13px; border-right: none; border-left: 1px solid #434343; } .ant-tree-rtl .ant-tree-checkbox { margin: 4px 0 0 8px; } .ant-tree-select-dropdown-rtl .ant-select-tree-checkbox { margin: 4px 0 0 8px; } .ant-select-tree-checkbox { box-sizing: border-box; margin: 0; padding: 0; color: rgba(255, 255, 255, 0.85); font-size: 14px; font-variant: tabular-nums; line-height: 1.5715; list-style: none; font-feature-settings: 'tnum'; position: relative; top: 0.2em; line-height: 1; white-space: nowrap; outline: none; cursor: pointer; } .ant-select-tree-checkbox-wrapper:hover .ant-select-tree-checkbox-inner, .ant-select-tree-checkbox:hover .ant-select-tree-checkbox-inner, .ant-select-tree-checkbox-input:focus + .ant-select-tree-checkbox-inner { border-color: #177ddc; } .ant-select-tree-checkbox-checked::after { position: absolute; top: 0; left: 0; width: 100%; height: 100%; border: 1px solid #177ddc; border-radius: 2px; visibility: hidden; animation: antCheckboxEffect 0.36s ease-in-out; animation-fill-mode: backwards; content: ''; } .ant-select-tree-checkbox:hover::after, .ant-select-tree-checkbox-wrapper:hover .ant-select-tree-checkbox::after { visibility: visible; } .ant-select-tree-checkbox-inner { position: relative; top: 0; left: 0; display: block; width: 16px; height: 16px; direction: ltr; background-color: transparent; border: 1px solid #434343; border-radius: 2px; border-collapse: separate; transition: all 0.3s; } .ant-select-tree-checkbox-inner::after { position: absolute; top: 50%; left: 21.5%; display: table; width: 5.71428571px; height: 9.14285714px; border: 2px solid #fff; border-top: 0; border-left: 0; transform: rotate(45deg) scale(0) translate(-50%, -50%); opacity: 0; transition: all 0.1s cubic-bezier(0.71, -0.46, 0.88, 0.6), opacity 0.1s; content: ' '; } .ant-select-tree-checkbox-input { position: absolute; top: 0; right: 0; bottom: 0; left: 0; z-index: 1; width: 100%; height: 100%; cursor: pointer; opacity: 0; } .ant-select-tree-checkbox-checked .ant-select-tree-checkbox-inner::after { position: absolute; display: table; border: 2px solid #fff; border-top: 0; border-left: 0; transform: rotate(45deg) scale(1) translate(-50%, -50%); opacity: 1; transition: all 0.2s cubic-bezier(0.12, 0.4, 0.29, 1.46) 0.1s; content: ' '; } .ant-select-tree-checkbox-checked .ant-select-tree-checkbox-inner { background-color: #177ddc; border-color: #177ddc; } .ant-select-tree-checkbox-disabled { cursor: not-allowed; } .ant-select-tree-checkbox-disabled.ant-select-tree-checkbox-checked .ant-select-tree-checkbox-inner::after { border-color: rgba(255, 255, 255, 0.3); animation-name: none; } .ant-select-tree-checkbox-disabled .ant-select-tree-checkbox-input { cursor: not-allowed; pointer-events: none; } .ant-select-tree-checkbox-disabled .ant-select-tree-checkbox-inner { background-color: rgba(255, 255, 255, 0.08); border-color: #434343 !important; } .ant-select-tree-checkbox-disabled .ant-select-tree-checkbox-inner::after { border-color: rgba(255, 255, 255, 0.08); border-collapse: separate; animation-name: none; } .ant-select-tree-checkbox-disabled + span { color: rgba(255, 255, 255, 0.3); cursor: not-allowed; } .ant-select-tree-checkbox-disabled:hover::after, .ant-select-tree-checkbox-wrapper:hover .ant-select-tree-checkbox-disabled::after { visibility: hidden; } .ant-select-tree-checkbox-wrapper { box-sizing: border-box; margin: 0; padding: 0; color: rgba(255, 255, 255, 0.85); font-size: 14px; font-variant: tabular-nums; line-height: 1.5715; list-style: none; font-feature-settings: 'tnum'; display: inline-flex; align-items: baseline; line-height: unset; cursor: pointer; } .ant-select-tree-checkbox-wrapper::after { display: inline-block; width: 0; overflow: hidden; content: '\a0'; } .ant-select-tree-checkbox-wrapper.ant-select-tree-checkbox-wrapper-disabled { cursor: not-allowed; } .ant-select-tree-checkbox-wrapper + .ant-select-tree-checkbox-wrapper { margin-left: 8px; } .ant-select-tree-checkbox-wrapper.ant-select-tree-checkbox-wrapper-in-form-item input[type='checkbox'] { width: 14px; height: 14px; } .ant-select-tree-checkbox + span { padding-right: 8px; padding-left: 8px; } .ant-select-tree-checkbox-group { box-sizing: border-box; margin: 0; padding: 0; color: rgba(255, 255, 255, 0.85); font-size: 14px; font-variant: tabular-nums; line-height: 1.5715; list-style: none; font-feature-settings: 'tnum'; display: inline-block; } .ant-select-tree-checkbox-group-item { margin-right: 8px; } .ant-select-tree-checkbox-group-item:last-child { margin-right: 0; } .ant-select-tree-checkbox-group-item + .ant-select-tree-checkbox-group-item { margin-left: 0; } .ant-select-tree-checkbox-indeterminate .ant-select-tree-checkbox-inner { background-color: transparent; border-color: #434343; } .ant-select-tree-checkbox-indeterminate .ant-select-tree-checkbox-inner::after { top: 50%; left: 50%; width: 8px; height: 8px; background-color: #177ddc; border: 0; transform: translate(-50%, -50%) scale(1); opacity: 1; content: ' '; } .ant-select-tree-checkbox-indeterminate.ant-select-tree-checkbox-disabled .ant-select-tree-checkbox-inner::after { background-color: rgba(255, 255, 255, 0.3); border-color: rgba(255, 255, 255, 0.3); } .ant-select-tree-checkbox-rtl { direction: rtl; } .ant-select-tree-checkbox-group-rtl .ant-select-tree-checkbox-group-item { margin-right: 0; margin-left: 8px; } .ant-select-tree-checkbox-group-rtl .ant-select-tree-checkbox-group-item:last-child { margin-left: 0 !important; } .ant-select-tree-checkbox-group-rtl .ant-select-tree-checkbox-group-item + .ant-select-tree-checkbox-group-item { margin-left: 8px; } .ant-tree-select-dropdown { padding: 8px 4px; } .ant-tree-select-dropdown-rtl { direction: rtl; } .ant-tree-select-dropdown .ant-select-tree { border-radius: 0; } .ant-tree-select-dropdown .ant-select-tree-list-holder-inner { align-items: stretch; } .ant-tree-select-dropdown .ant-select-tree-list-holder-inner .ant-select-tree-treenode .ant-select-tree-node-content-wrapper { flex: auto; } .ant-select-tree { box-sizing: border-box; margin: 0; padding: 0; color: rgba(255, 255, 255, 0.85); font-size: 14px; font-variant: tabular-nums; line-height: 1.5715; list-style: none; font-feature-settings: 'tnum'; background: transparent; border-radius: 2px; transition: background-color 0.3s; } .ant-select-tree-focused:not(:hover):not(.ant-select-tree-active-focused) { background: #111b26; } .ant-select-tree-list-holder-inner { align-items: flex-start; } .ant-select-tree.ant-select-tree-block-node .ant-select-tree-list-holder-inner { align-items: stretch; } .ant-select-tree.ant-select-tree-block-node .ant-select-tree-list-holder-inner .ant-select-tree-node-content-wrapper { flex: auto; } .ant-select-tree.ant-select-tree-block-node .ant-select-tree-list-holder-inner .ant-select-tree-treenode.dragging { position: relative; } .ant-select-tree.ant-select-tree-block-node .ant-select-tree-list-holder-inner .ant-select-tree-treenode.dragging::after { position: absolute; top: 0; right: 0; bottom: 4px; left: 0; border: 1px solid #177ddc; opacity: 0; animation: ant-tree-node-fx-do-not-use 0.3s; animation-play-state: running; animation-fill-mode: forwards; content: ''; pointer-events: none; } .ant-select-tree .ant-select-tree-treenode { display: flex; align-items: flex-start; padding: 0 0 4px 0; outline: none; } .ant-select-tree .ant-select-tree-treenode-disabled .ant-select-tree-node-content-wrapper { color: rgba(255, 255, 255, 0.3); cursor: not-allowed; } .ant-select-tree .ant-select-tree-treenode-disabled .ant-select-tree-node-content-wrapper:hover { background: transparent; } .ant-select-tree .ant-select-tree-treenode-active .ant-select-tree-node-content-wrapper { background: rgba(255, 255, 255, 0.08); } .ant-select-tree .ant-select-tree-treenode:not(.ant-select-tree .ant-select-tree-treenode-disabled).filter-node .ant-select-tree-title { color: inherit; font-weight: 500; } .ant-select-tree .ant-select-tree-treenode-draggable .ant-select-tree-draggable-icon { width: 24px; line-height: 24px; text-align: center; visibility: visible; opacity: 0.2; transition: opacity 0.3s; } .ant-select-tree-treenode:hover .ant-select-tree .ant-select-tree-treenode-draggable .ant-select-tree-draggable-icon { opacity: 0.45; } .ant-select-tree .ant-select-tree-treenode-draggable.ant-select-tree-treenode-disabled .ant-select-tree-draggable-icon { visibility: hidden; } .ant-select-tree-indent { align-self: stretch; white-space: nowrap; user-select: none; } .ant-select-tree-indent-unit { display: inline-block; width: 24px; } .ant-select-tree-draggable-icon { visibility: hidden; } .ant-select-tree-switcher { position: relative; flex: none; align-self: stretch; width: 24px; margin: 0; line-height: 24px; text-align: center; cursor: pointer; user-select: none; } .ant-select-tree-switcher .ant-tree-switcher-icon, .ant-select-tree-switcher .ant-select-tree-switcher-icon { display: inline-block; font-size: 10px; vertical-align: baseline; } .ant-select-tree-switcher .ant-tree-switcher-icon svg, .ant-select-tree-switcher .ant-select-tree-switcher-icon svg { transition: transform 0.3s; } .ant-select-tree-switcher-noop { cursor: default; } .ant-select-tree-switcher_close .ant-select-tree-switcher-icon svg { transform: rotate(-90deg); } .ant-select-tree-switcher-loading-icon { color: #177ddc; } .ant-select-tree-switcher-leaf-line { position: relative; z-index: 1; display: inline-block; width: 100%; height: 100%; } .ant-select-tree-switcher-leaf-line::before { position: absolute; top: 0; right: 12px; bottom: -4px; margin-left: -1px; border-right: 1px solid #d9d9d9; content: ' '; } .ant-select-tree-switcher-leaf-line::after { position: absolute; width: 10px; height: 14px; border-bottom: 1px solid #d9d9d9; content: ' '; } .ant-select-tree-checkbox { top: initial; margin: 4px 8px 0 0; } .ant-select-tree .ant-select-tree-node-content-wrapper { position: relative; z-index: auto; min-height: 24px; margin: 0; padding: 0 4px; color: inherit; line-height: 24px; background: transparent; border-radius: 2px; cursor: pointer; transition: all 0.3s, border 0s, line-height 0s, box-shadow 0s; } .ant-select-tree .ant-select-tree-node-content-wrapper:hover { background-color: rgba(255, 255, 255, 0.08); } .ant-select-tree .ant-select-tree-node-content-wrapper.ant-select-tree-node-selected { background-color: #11263c; } .ant-select-tree .ant-select-tree-node-content-wrapper .ant-select-tree-iconEle { display: inline-block; width: 24px; height: 24px; line-height: 24px; text-align: center; vertical-align: top; } .ant-select-tree .ant-select-tree-node-content-wrapper .ant-select-tree-iconEle:empty { display: none; } .ant-select-tree-unselectable .ant-select-tree-node-content-wrapper:hover { background-color: transparent; } .ant-select-tree-node-content-wrapper { line-height: 24px; user-select: none; } .ant-select-tree-node-content-wrapper .ant-tree-drop-indicator { position: absolute; z-index: 1; height: 2px; background-color: #177ddc; border-radius: 1px; pointer-events: none; } .ant-select-tree-node-content-wrapper .ant-tree-drop-indicator::after { position: absolute; top: -3px; left: -6px; width: 8px; height: 8px; background-color: transparent; border: 2px solid #177ddc; border-radius: 50%; content: ''; } .ant-select-tree .ant-select-tree-treenode.drop-container > [draggable] { box-shadow: 0 0 0 2px #177ddc; } .ant-select-tree-show-line .ant-select-tree-indent-unit { position: relative; height: 100%; } .ant-select-tree-show-line .ant-select-tree-indent-unit::before { position: absolute; top: 0; right: 12px; bottom: -4px; border-right: 1px solid #434343; content: ''; } .ant-select-tree-show-line .ant-select-tree-indent-unit-end::before { display: none; } .ant-select-tree-show-line .ant-select-tree-switcher { background: #141414; } .ant-select-tree-show-line .ant-select-tree-switcher-line-icon { vertical-align: -0.15em; } .ant-select-tree .ant-select-tree-treenode-leaf-last .ant-select-tree-switcher-leaf-line::before { top: auto !important; bottom: auto !important; height: 14px !important; } .ant-tree-select-dropdown-rtl .ant-select-tree .ant-select-tree-switcher_close .ant-select-tree-switcher-icon svg { transform: rotate(90deg); } .ant-tree-select-dropdown-rtl .ant-select-tree .ant-select-tree-switcher-loading-icon { transform: scaleY(-1); } .ant-typography { color: rgba(255, 255, 255, 0.85); word-break: break-word; } .ant-typography.ant-typography-secondary { color: rgba(255, 255, 255, 0.45); } .ant-typography.ant-typography-success { color: #49aa19; } .ant-typography.ant-typography-warning { color: #d89614; } .ant-typography.ant-typography-danger { color: #a61d24; } a.ant-typography.ant-typography-danger:active, a.ant-typography.ant-typography-danger:focus { color: #800f19; } a.ant-typography.ant-typography-danger:hover { color: #b33b3d; } .ant-typography.ant-typography-disabled { color: rgba(255, 255, 255, 0.3); cursor: not-allowed; user-select: none; } div.ant-typography, .ant-typography p { margin-bottom: 1em; } h1.ant-typography, div.ant-typography-h1, div.ant-typography-h1 > textarea, .ant-typography h1 { margin-bottom: 0.5em; color: rgba(255, 255, 255, 0.85); font-weight: 600; font-size: 38px; line-height: 1.23; } h2.ant-typography, div.ant-typography-h2, div.ant-typography-h2 > textarea, .ant-typography h2 { margin-bottom: 0.5em; color: rgba(255, 255, 255, 0.85); font-weight: 600; font-size: 30px; line-height: 1.35; } h3.ant-typography, div.ant-typography-h3, div.ant-typography-h3 > textarea, .ant-typography h3 { margin-bottom: 0.5em; color: rgba(255, 255, 255, 0.85); font-weight: 600; font-size: 24px; line-height: 1.35; } h4.ant-typography, div.ant-typography-h4, div.ant-typography-h4 > textarea, .ant-typography h4 { margin-bottom: 0.5em; color: rgba(255, 255, 255, 0.85); font-weight: 600; font-size: 20px; line-height: 1.4; } h5.ant-typography, div.ant-typography-h5, div.ant-typography-h5 > textarea, .ant-typography h5 { margin-bottom: 0.5em; color: rgba(255, 255, 255, 0.85); font-weight: 600; font-size: 16px; line-height: 1.5; } .ant-typography + h1.ant-typography, .ant-typography + h2.ant-typography, .ant-typography + h3.ant-typography, .ant-typography + h4.ant-typography, .ant-typography + h5.ant-typography { margin-top: 1.2em; } .ant-typography div + h1, .ant-typography ul + h1, .ant-typography li + h1, .ant-typography p + h1, .ant-typography h1 + h1, .ant-typography h2 + h1, .ant-typography h3 + h1, .ant-typography h4 + h1, .ant-typography h5 + h1, .ant-typography div + h2, .ant-typography ul + h2, .ant-typography li + h2, .ant-typography p + h2, .ant-typography h1 + h2, .ant-typography h2 + h2, .ant-typography h3 + h2, .ant-typography h4 + h2, .ant-typography h5 + h2, .ant-typography div + h3, .ant-typography ul + h3, .ant-typography li + h3, .ant-typography p + h3, .ant-typography h1 + h3, .ant-typography h2 + h3, .ant-typography h3 + h3, .ant-typography h4 + h3, .ant-typography h5 + h3, .ant-typography div + h4, .ant-typography ul + h4, .ant-typography li + h4, .ant-typography p + h4, .ant-typography h1 + h4, .ant-typography h2 + h4, .ant-typography h3 + h4, .ant-typography h4 + h4, .ant-typography h5 + h4, .ant-typography div + h5, .ant-typography ul + h5, .ant-typography li + h5, .ant-typography p + h5, .ant-typography h1 + h5, .ant-typography h2 + h5, .ant-typography h3 + h5, .ant-typography h4 + h5, .ant-typography h5 + h5 { margin-top: 1.2em; } a.ant-typography-ellipsis, span.ant-typography-ellipsis { display: inline-block; max-width: 100%; } a.ant-typography, .ant-typography a { color: #177ddc; outline: none; cursor: pointer; transition: color 0.3s; text-decoration: none; } a.ant-typography:focus-visible, .ant-typography a:focus-visible, a.ant-typography:hover, .ant-typography a:hover { color: #165996; } a.ant-typography:active, .ant-typography a:active { color: #388ed3; } a.ant-typography:active, .ant-typography a:active, a.ant-typography:hover, .ant-typography a:hover { text-decoration: none; } a.ant-typography[disabled], .ant-typography a[disabled], a.ant-typography.ant-typography-disabled, .ant-typography a.ant-typography-disabled { color: rgba(255, 255, 255, 0.3); cursor: not-allowed; } a.ant-typography[disabled]:active, .ant-typography a[disabled]:active, a.ant-typography.ant-typography-disabled:active, .ant-typography a.ant-typography-disabled:active, a.ant-typography[disabled]:hover, .ant-typography a[disabled]:hover, a.ant-typography.ant-typography-disabled:hover, .ant-typography a.ant-typography-disabled:hover { color: rgba(255, 255, 255, 0.3); } a.ant-typography[disabled]:active, .ant-typography a[disabled]:active, a.ant-typography.ant-typography-disabled:active, .ant-typography a.ant-typography-disabled:active { pointer-events: none; } .ant-typography code { margin: 0 0.2em; padding: 0.2em 0.4em 0.1em; font-size: 85%; background: rgba(150, 150, 150, 0.1); border: 1px solid rgba(100, 100, 100, 0.2); border-radius: 3px; } .ant-typography kbd { margin: 0 0.2em; padding: 0.15em 0.4em 0.1em; font-size: 90%; background: rgba(150, 150, 150, 0.06); border: 1px solid rgba(100, 100, 100, 0.2); border-bottom-width: 2px; border-radius: 3px; } .ant-typography mark { padding: 0; background-color: #594214; } .ant-typography u, .ant-typography ins { text-decoration: underline; text-decoration-skip-ink: auto; } .ant-typography s, .ant-typography del { text-decoration: line-through; } .ant-typography strong { font-weight: 600; } .ant-typography-expand, .ant-typography-edit, .ant-typography-copy { color: #177ddc; outline: none; cursor: pointer; transition: color 0.3s; margin-left: 4px; } .ant-typography-expand:focus-visible, .ant-typography-edit:focus-visible, .ant-typography-copy:focus-visible, .ant-typography-expand:hover, .ant-typography-edit:hover, .ant-typography-copy:hover { color: #165996; } .ant-typography-expand:active, .ant-typography-edit:active, .ant-typography-copy:active { color: #388ed3; } .ant-typography-copy-success, .ant-typography-copy-success:hover, .ant-typography-copy-success:focus { color: #49aa19; } .ant-typography-edit-content { position: relative; } div.ant-typography-edit-content { left: -12px; margin-top: -5px; margin-bottom: calc(1em - 4px - 1px); } .ant-typography-edit-content-confirm { position: absolute; right: 10px; bottom: 8px; color: rgba(255, 255, 255, 0.45); font-weight: normal; font-size: 14px; font-style: normal; pointer-events: none; } .ant-typography-edit-content textarea { height: 1em; margin: 0 !important; /* stylelint-disable-next-line property-no-vendor-prefix */ -moz-transition: none; } .ant-typography ul, .ant-typography ol { margin: 0 0 1em; padding: 0; } .ant-typography ul li, .ant-typography ol li { margin: 0 0 0 20px; padding: 0 0 0 4px; } .ant-typography ul { list-style-type: circle; } .ant-typography ul ul { list-style-type: disc; } .ant-typography ol { list-style-type: decimal; } .ant-typography pre, .ant-typography blockquote { margin: 1em 0; } .ant-typography pre { padding: 0.4em 0.6em; white-space: pre-wrap; word-wrap: break-word; background: rgba(150, 150, 150, 0.1); border: 1px solid rgba(100, 100, 100, 0.2); border-radius: 3px; } .ant-typography pre code { display: inline; margin: 0; padding: 0; font-size: inherit; font-family: inherit; background: transparent; border: 0; } .ant-typography blockquote { padding: 0 0 0 0.6em; border-left: 4px solid rgba(100, 100, 100, 0.2); opacity: 0.85; } .ant-typography-single-line { white-space: nowrap; } .ant-typography-ellipsis-single-line { overflow: hidden; text-overflow: ellipsis; } a.ant-typography-ellipsis-single-line, span.ant-typography-ellipsis-single-line { vertical-align: bottom; } .ant-typography-ellipsis-multiple-line { /* stylelint-disable-next-line value-no-vendor-prefix */ display: -webkit-box; overflow: hidden; -webkit-line-clamp: 3; /*! autoprefixer: ignore next */ -webkit-box-orient: vertical; } .ant-typography-rtl { direction: rtl; } .ant-typography-rtl .ant-typography-expand, .ant-typography-rtl .ant-typography-edit, .ant-typography-rtl .ant-typography-copy { margin-right: 4px; margin-left: 0; } .ant-typography-rtl .ant-typography-expand { float: left; } div.ant-typography-edit-content.ant-typography-rtl { right: -12px; left: auto; } .ant-typography-rtl .ant-typography-edit-content-confirm { right: auto; left: 10px; } .ant-typography-rtl.ant-typography ul li, .ant-typography-rtl.ant-typography ol li { margin: 0 20px 0 0; padding: 0 4px 0 0; } .ant-upload { box-sizing: border-box; margin: 0; padding: 0; color: rgba(255, 255, 255, 0.85); font-size: 14px; font-variant: tabular-nums; line-height: 1.5715; list-style: none; font-feature-settings: 'tnum'; outline: 0; } .ant-upload p { margin: 0; } .ant-upload-btn { display: block; width: 100%; outline: none; } .ant-upload input[type='file'] { cursor: pointer; } .ant-upload.ant-upload-select { display: inline-block; } .ant-upload.ant-upload-disabled { color: rgba(255, 255, 255, 0.3); cursor: not-allowed; } .ant-upload.ant-upload-select-picture-card { width: 104px; height: 104px; margin-right: 8px; margin-bottom: 8px; text-align: center; vertical-align: top; background-color: rgba(255, 255, 255, 0.04); border: 1px dashed #434343; border-radius: 2px; cursor: pointer; transition: border-color 0.3s; } .ant-upload.ant-upload-select-picture-card > .ant-upload { display: flex; align-items: center; justify-content: center; height: 100%; text-align: center; } .ant-upload.ant-upload-select-picture-card:hover { border-color: #177ddc; } .ant-upload-disabled.ant-upload.ant-upload-select-picture-card:hover { border-color: #434343; } .ant-upload.ant-upload-drag { position: relative; width: 100%; height: 100%; text-align: center; background: rgba(255, 255, 255, 0.04); border: 1px dashed #434343; border-radius: 2px; cursor: pointer; transition: border-color 0.3s; } .ant-upload.ant-upload-drag .ant-upload { padding: 16px 0; } .ant-upload.ant-upload-drag.ant-upload-drag-hover:not(.ant-upload-disabled) { border-color: #388ed3; } .ant-upload.ant-upload-drag.ant-upload-disabled { cursor: not-allowed; } .ant-upload.ant-upload-drag .ant-upload-btn { display: table; height: 100%; } .ant-upload.ant-upload-drag .ant-upload-drag-container { display: table-cell; vertical-align: middle; } .ant-upload.ant-upload-drag:not(.ant-upload-disabled):hover { border-color: #165996; } .ant-upload.ant-upload-drag p.ant-upload-drag-icon { margin-bottom: 20px; } .ant-upload.ant-upload-drag p.ant-upload-drag-icon .anticon { color: #165996; font-size: 48px; } .ant-upload.ant-upload-drag p.ant-upload-text { margin: 0 0 4px; color: rgba(255, 255, 255, 0.85); font-size: 16px; } .ant-upload.ant-upload-drag p.ant-upload-hint { color: rgba(255, 255, 255, 0.45); font-size: 14px; } .ant-upload.ant-upload-drag .anticon-plus { color: rgba(255, 255, 255, 0.3); font-size: 30px; transition: all 0.3s; } .ant-upload.ant-upload-drag .anticon-plus:hover { color: rgba(255, 255, 255, 0.45); } .ant-upload.ant-upload-drag:hover .anticon-plus { color: rgba(255, 255, 255, 0.45); } .ant-upload-picture-card-wrapper { display: inline-block; width: 100%; } .ant-upload-picture-card-wrapper::before { display: table; content: ''; } .ant-upload-picture-card-wrapper::after { display: table; clear: both; content: ''; } .ant-upload-picture-card-wrapper::before { display: table; content: ''; } .ant-upload-picture-card-wrapper::after { display: table; clear: both; content: ''; } .ant-upload-list { box-sizing: border-box; margin: 0; padding: 0; color: rgba(255, 255, 255, 0.85); font-size: 14px; font-variant: tabular-nums; list-style: none; font-feature-settings: 'tnum'; line-height: 1.5715; } .ant-upload-list::before { display: table; content: ''; } .ant-upload-list::after { display: table; clear: both; content: ''; } .ant-upload-list::before { display: table; content: ''; } .ant-upload-list::after { display: table; clear: both; content: ''; } .ant-upload-list-item { position: relative; height: 22.001px; margin-top: 8px; font-size: 14px; } .ant-upload-list-item-name { display: inline-block; width: 100%; padding-left: 22px; overflow: hidden; line-height: 1.5715; white-space: nowrap; text-overflow: ellipsis; } .ant-upload-list-item-card-actions { position: absolute; right: 0; } .ant-upload-list-item-card-actions-btn { opacity: 0; } .ant-upload-list-item-card-actions-btn.ant-btn-sm { height: 22.001px; line-height: 1; vertical-align: top; } .ant-upload-list-item-card-actions.picture { top: 22px; line-height: 0; } .ant-upload-list-item-card-actions-btn:focus, .ant-upload-list-item-card-actions.picture .ant-upload-list-item-card-actions-btn { opacity: 1; } .ant-upload-list-item-card-actions .anticon { color: rgba(255, 255, 255, 0.45); transition: all 0.3s; } .ant-upload-list-item-card-actions:hover .anticon { color: rgba(255, 255, 255, 0.85); } .ant-upload-list-item-info { height: 100%; transition: background-color 0.3s; } .ant-upload-list-item-info > span { display: block; width: 100%; height: 100%; } .ant-upload-list-item-info .anticon-loading .anticon, .ant-upload-list-item-info .ant-upload-text-icon .anticon { position: absolute; top: 5px; color: rgba(255, 255, 255, 0.45); font-size: 14px; } .ant-upload-list-item:hover .ant-upload-list-item-info { background-color: rgba(255, 255, 255, 0.08); } .ant-upload-list-item:hover .ant-upload-list-item-card-actions-btn { opacity: 1; } .ant-upload-list-item-error, .ant-upload-list-item-error .ant-upload-text-icon > .anticon, .ant-upload-list-item-error .ant-upload-list-item-name { color: #a61d24; } .ant-upload-list-item-error .ant-upload-list-item-card-actions .anticon { color: #a61d24; } .ant-upload-list-item-error .ant-upload-list-item-card-actions-btn { opacity: 1; } .ant-upload-list-item-progress { position: absolute; bottom: -12px; width: 100%; padding-left: 26px; font-size: 14px; line-height: 0; } .ant-upload-list-picture .ant-upload-list-item, .ant-upload-list-picture-card .ant-upload-list-item { position: relative; height: 66px; padding: 8px; border: 1px solid #434343; border-radius: 2px; } .ant-upload-list-picture .ant-upload-list-item:hover, .ant-upload-list-picture-card .ant-upload-list-item:hover { background: transparent; } .ant-upload-list-picture .ant-upload-list-item-error, .ant-upload-list-picture-card .ant-upload-list-item-error { border-color: #a61d24; } .ant-upload-list-picture .ant-upload-list-item-info, .ant-upload-list-picture-card .ant-upload-list-item-info { padding: 0; } .ant-upload-list-picture .ant-upload-list-item:hover .ant-upload-list-item-info, .ant-upload-list-picture-card .ant-upload-list-item:hover .ant-upload-list-item-info { background: transparent; } .ant-upload-list-picture .ant-upload-list-item-uploading, .ant-upload-list-picture-card .ant-upload-list-item-uploading { border-style: dashed; } .ant-upload-list-picture .ant-upload-list-item-thumbnail, .ant-upload-list-picture-card .ant-upload-list-item-thumbnail { width: 48px; height: 48px; line-height: 60px; text-align: center; opacity: 0.8; } .ant-upload-list-picture .ant-upload-list-item-thumbnail .anticon, .ant-upload-list-picture-card .ant-upload-list-item-thumbnail .anticon { font-size: 26px; } .ant-upload-list-picture .ant-upload-list-item-error .ant-upload-list-item-thumbnail .anticon svg path[fill='#e6f7ff'], .ant-upload-list-picture-card .ant-upload-list-item-error .ant-upload-list-item-thumbnail .anticon svg path[fill='#e6f7ff'] { fill: #2a1215; } .ant-upload-list-picture .ant-upload-list-item-error .ant-upload-list-item-thumbnail .anticon svg path[fill='#1890ff'], .ant-upload-list-picture-card .ant-upload-list-item-error .ant-upload-list-item-thumbnail .anticon svg path[fill='#1890ff'] { fill: #a61d24; } .ant-upload-list-picture .ant-upload-list-item-icon, .ant-upload-list-picture-card .ant-upload-list-item-icon { position: absolute; top: 50%; left: 50%; font-size: 26px; transform: translate(-50%, -50%); } .ant-upload-list-picture .ant-upload-list-item-icon .anticon, .ant-upload-list-picture-card .ant-upload-list-item-icon .anticon { font-size: 26px; } .ant-upload-list-picture .ant-upload-list-item-image, .ant-upload-list-picture-card .ant-upload-list-item-image { max-width: 100%; } .ant-upload-list-picture .ant-upload-list-item-thumbnail img, .ant-upload-list-picture-card .ant-upload-list-item-thumbnail img { display: block; width: 48px; height: 48px; overflow: hidden; } .ant-upload-list-picture .ant-upload-list-item-name, .ant-upload-list-picture-card .ant-upload-list-item-name { display: inline-block; box-sizing: border-box; max-width: 100%; margin: 0 0 0 8px; padding-right: 8px; padding-left: 48px; overflow: hidden; line-height: 44px; white-space: nowrap; text-overflow: ellipsis; transition: all 0.3s; } .ant-upload-list-picture .ant-upload-list-item-uploading .ant-upload-list-item-name, .ant-upload-list-picture-card .ant-upload-list-item-uploading .ant-upload-list-item-name { margin-bottom: 12px; } .ant-upload-list-picture .ant-upload-list-item-progress, .ant-upload-list-picture-card .ant-upload-list-item-progress { bottom: 14px; width: calc(100% - 24px); margin-top: 0; padding-left: 56px; } .ant-upload-list-picture-card-container { display: inline-block; width: 104px; height: 104px; margin: 0 8px 8px 0; vertical-align: top; } .ant-upload-list-picture-card .ant-upload-list-item { height: 100%; margin: 0; } .ant-upload-list-picture-card .ant-upload-list-item-info { position: relative; height: 100%; overflow: hidden; } .ant-upload-list-picture-card .ant-upload-list-item-info::before { position: absolute; z-index: 1; width: 100%; height: 100%; background-color: rgba(0, 0, 0, 0.5); opacity: 0; transition: all 0.3s; content: ' '; } .ant-upload-list-picture-card .ant-upload-list-item:hover .ant-upload-list-item-info::before { opacity: 1; } .ant-upload-list-picture-card .ant-upload-list-item-actions { position: absolute; top: 50%; left: 50%; z-index: 10; white-space: nowrap; transform: translate(-50%, -50%); opacity: 0; transition: all 0.3s; } .ant-upload-list-picture-card .ant-upload-list-item-actions .anticon-eye, .ant-upload-list-picture-card .ant-upload-list-item-actions .anticon-download, .ant-upload-list-picture-card .ant-upload-list-item-actions .anticon-delete { z-index: 10; width: 16px; margin: 0 4px; color: rgba(255, 255, 255, 0.85); font-size: 16px; cursor: pointer; transition: all 0.3s; } .ant-upload-list-picture-card .ant-upload-list-item-actions .anticon-eye:hover, .ant-upload-list-picture-card .ant-upload-list-item-actions .anticon-download:hover, .ant-upload-list-picture-card .ant-upload-list-item-actions .anticon-delete:hover { color: #fff; } .ant-upload-list-picture-card .ant-upload-list-item-info:hover + .ant-upload-list-item-actions, .ant-upload-list-picture-card .ant-upload-list-item-actions:hover { opacity: 1; } .ant-upload-list-picture-card .ant-upload-list-item-thumbnail, .ant-upload-list-picture-card .ant-upload-list-item-thumbnail img { position: static; display: block; width: 100%; height: 100%; object-fit: contain; } .ant-upload-list-picture-card .ant-upload-list-item-name { display: none; margin: 8px 0 0; padding: 0; line-height: 1.5715; text-align: center; } .ant-upload-list-picture-card .ant-upload-list-item-file + .ant-upload-list-item-name { position: absolute; bottom: 10px; display: block; } .ant-upload-list-picture-card .ant-upload-list-item-uploading.ant-upload-list-item { background-color: rgba(255, 255, 255, 0.04); } .ant-upload-list-picture-card .ant-upload-list-item-uploading .ant-upload-list-item-info { height: auto; } .ant-upload-list-picture-card .ant-upload-list-item-uploading .ant-upload-list-item-info::before, .ant-upload-list-picture-card .ant-upload-list-item-uploading .ant-upload-list-item-info .anticon-eye, .ant-upload-list-picture-card .ant-upload-list-item-uploading .ant-upload-list-item-info .anticon-delete { display: none; } .ant-upload-list-picture-card .ant-upload-list-item-progress { bottom: 32px; width: calc(100% - 14px); padding-left: 0; } .ant-upload-list-text-container, .ant-upload-list-picture-container { transition: opacity 0.3s, height 0.3s; } .ant-upload-list-text-container::before, .ant-upload-list-picture-container::before { display: table; width: 0; height: 0; content: ''; } .ant-upload-list-text-container .ant-upload-span, .ant-upload-list-picture-container .ant-upload-span { display: block; flex: auto; } .ant-upload-list-text .ant-upload-span, .ant-upload-list-picture .ant-upload-span { display: flex; align-items: center; } .ant-upload-list-text .ant-upload-span > *, .ant-upload-list-picture .ant-upload-span > * { flex: none; } .ant-upload-list-text .ant-upload-list-item-name, .ant-upload-list-picture .ant-upload-list-item-name { flex: auto; margin: 0; padding: 0 8px; } .ant-upload-list-text .ant-upload-list-item-card-actions, .ant-upload-list-picture .ant-upload-list-item-card-actions { position: static; } .ant-upload-list-text .ant-upload-text-icon .anticon { position: static; } .ant-upload-list .ant-upload-animate-inline-appear, .ant-upload-list .ant-upload-animate-inline-enter, .ant-upload-list .ant-upload-animate-inline-leave { animation-duration: 0.3s; animation-timing-function: cubic-bezier(0.78, 0.14, 0.15, 0.86); animation-fill-mode: forwards; } .ant-upload-list .ant-upload-animate-inline-appear, .ant-upload-list .ant-upload-animate-inline-enter { animation-name: uploadAnimateInlineIn; } .ant-upload-list .ant-upload-animate-inline-leave { animation-name: uploadAnimateInlineOut; } @keyframes uploadAnimateInlineIn { from { width: 0; height: 0; margin: 0; padding: 0; opacity: 0; } } @keyframes uploadAnimateInlineOut { to { width: 0; height: 0; margin: 0; padding: 0; opacity: 0; } } .ant-upload-rtl { direction: rtl; } .ant-upload-rtl.ant-upload.ant-upload-select-picture-card { margin-right: auto; margin-left: 8px; } .ant-upload-list-rtl { direction: rtl; } .ant-upload-list-rtl .ant-upload-list-item-list-type-text:hover .ant-upload-list-item-name-icon-count-1 { padding-right: 22px; padding-left: 14px; } .ant-upload-list-rtl .ant-upload-list-item-list-type-text:hover .ant-upload-list-item-name-icon-count-2 { padding-right: 22px; padding-left: 28px; } .ant-upload-list-rtl .ant-upload-list-item-name { padding-right: 22px; padding-left: 0; } .ant-upload-list-rtl .ant-upload-list-item-name-icon-count-1 { padding-left: 14px; } .ant-upload-list-rtl .ant-upload-list-item-card-actions { right: auto; left: 0; } .ant-upload-list-rtl .ant-upload-list-item-card-actions .anticon { padding-right: 0; padding-left: 5px; } .ant-upload-list-rtl .ant-upload-list-item-info { padding: 0 4px 0 12px; } .ant-upload-list-rtl .ant-upload-list-item-error .ant-upload-list-item-card-actions .anticon { padding-right: 0; padding-left: 5px; } .ant-upload-list-rtl .ant-upload-list-item-progress { padding-right: 26px; padding-left: 0; } .ant-upload-list-picture .ant-upload-list-item-info, .ant-upload-list-picture-card .ant-upload-list-item-info { padding: 0; } .ant-upload-list-rtl.ant-upload-list-picture .ant-upload-list-item-thumbnail, .ant-upload-list-rtl.ant-upload-list-picture-card .ant-upload-list-item-thumbnail { right: 8px; left: auto; } .ant-upload-list-rtl.ant-upload-list-picture .ant-upload-list-item-icon, .ant-upload-list-rtl.ant-upload-list-picture-card .ant-upload-list-item-icon { right: 50%; left: auto; transform: translate(50%, -50%); } .ant-upload-list-rtl.ant-upload-list-picture .ant-upload-list-item-name, .ant-upload-list-rtl.ant-upload-list-picture-card .ant-upload-list-item-name { margin: 0 8px 0 0; padding-right: 48px; padding-left: 8px; } .ant-upload-list-rtl.ant-upload-list-picture .ant-upload-list-item-name-icon-count-1, .ant-upload-list-rtl.ant-upload-list-picture-card .ant-upload-list-item-name-icon-count-1 { padding-right: 48px; padding-left: 18px; } .ant-upload-list-rtl.ant-upload-list-picture .ant-upload-list-item-name-icon-count-2, .ant-upload-list-rtl.ant-upload-list-picture-card .ant-upload-list-item-name-icon-count-2 { padding-right: 48px; padding-left: 36px; } .ant-upload-list-rtl.ant-upload-list-picture .ant-upload-list-item-progress, .ant-upload-list-rtl.ant-upload-list-picture-card .ant-upload-list-item-progress { padding-right: 0; padding-left: 0; } .ant-upload-list-rtl .ant-upload-list-picture-card-container { margin: 0 0 8px 8px; } .ant-upload-list-rtl.ant-upload-list-picture-card .ant-upload-list-item-actions { right: 50%; left: auto; transform: translate(50%, -50%); } .ant-upload-list-rtl.ant-upload-list-picture-card .ant-upload-list-item-file + .ant-upload-list-item-name { margin: 8px 0 0; padding: 0; } @font-face { font-family: 'Oxanium'; src: local('Oxanium'), url(./fonts/Oxanium-Medium.ttf) format('truetype'); } :root { --app-max-width: 1200px; --app-background-color: var(--vscode-editor-background, #1e1e1e); --diff-background-color: #1e1e1e; --diff-text-color: #fafafa; --diff-selection-background-color: #5a5f80; --diff-gutter-insert-background-color: #082525; --diff-gutter-delete-background-color: #2b1523; --diff-gutter-selected-background-color: #5a5f80; --diff-code-insert-background-color: #082525; --diff-code-delete-background-color: #2b1523; --diff-code-insert-edit-background-color: #00462f; --diff-code-delete-edit-background-color: #4e2436; --diff-code-selected-background-color: #5a5f80; --diff-omit-background-color: #101120; --diff-decoration-gutter-background-color: #222; --diff-decoration-gutter-color: #ababab; --diff-decoration-content-background-color: #222; --diff-decoration-content-color: #ababab; } body[data-content="sidebar"] { --app-background-color: var(--vscode-sideBar-background); } body.windhawk-no-pointer-events { pointer-events: none; } body.windhawk-no-pointer-events .ant-modal-content { pointer-events: none; } body.windhawk-no-pointer-events .ant-select.ant-select-open, body.windhawk-no-pointer-events .windhawk-popup-content, body.windhawk-no-pointer-events .windhawk-popup-content-no-select { pointer-events: auto; } .windhawk-popup-content-no-select { user-select: none; } .ant-select-item-option-active:not(.ant-select-item-option-disabled) { color: #fff; background-color: #1f1f1f; } .ant-select-item-option-selected:not(.ant-select-item-option-disabled) { color: #fff; background-color: #177ddc; } .rc-virtual-list-scrollbar-thumb { border-radius: 0 !important; background: rgba(121, 121, 121, 0.4) !important; } .ant-dropdown-menu-item-active:not(.ant-dropdown-menu-item-disabled) { color: #fff; background-color: #1f1f1f; } .ant-dropdown-menu-item-selected:not(.ant-dropdown-menu-item-disabled) { color: #fff; background-color: #177ddc; } ================================================ FILE: src/vscode-windhawk-ui/apps/vscode-windhawk-ui/src/app/app.less ================================================ @import 'antd/dist/antd.dark.less'; @font-face { font-family: 'Oxanium'; src: local('Oxanium'), url(./fonts/Oxanium-Medium.ttf) format('truetype'); } :root { --app-max-width: 1200px; --app-background-color: var(--vscode-editor-background, #1e1e1e); // https://github.com/otakustay/react-diff-view/blob/f9e5f9f248f331598e5c9e7839fccb211efe43c2/site/components/DiffView/diff.global.less --diff-background-color: #1e1e1e; --diff-text-color: #fafafa; --diff-selection-background-color: #5a5f80; --diff-gutter-insert-background-color: #082525; --diff-gutter-delete-background-color: #2b1523; --diff-gutter-selected-background-color: #5a5f80; --diff-code-insert-background-color: #082525; --diff-code-delete-background-color: #2b1523; --diff-code-insert-edit-background-color: #00462f; --diff-code-delete-edit-background-color: #4e2436; --diff-code-selected-background-color: #5a5f80; --diff-omit-background-color: #101120; --diff-decoration-gutter-background-color: #222; --diff-decoration-gutter-color: #ababab; --diff-decoration-content-background-color: #222; --diff-decoration-content-color: #ababab; } body[data-content="sidebar"] { --app-background-color: var(--vscode-sideBar-background); } @body-background: var(--app-background-color); body.windhawk-no-pointer-events { pointer-events: none; .ant-modal-content { pointer-events: none; } .ant-select.ant-select-open, .windhawk-popup-content, .windhawk-popup-content-no-select { pointer-events: auto; } } .windhawk-popup-content-no-select { user-select: none; } // An ugly patch providing dark theme for select elements. .@{select-prefix-cls} { &-item { &-option { &-active:not(&-disabled) { color: @text-color-inverse; background-color: @menu-dark-bg; } &-selected:not(&-disabled) { color: @text-color-inverse; background-color: @primary-color; } } } } .rc-virtual-list-scrollbar-thumb { border-radius: 0 !important; background: rgba(121, 121, 121, 0.4) !important; } // An ugly patch providing dark theme for dropdown menu elements. .@{dropdown-prefix-cls} { &-menu { &-item { &-active:not(&-disabled) { color: @text-color-inverse; background-color: @menu-dark-bg; } &-selected:not(&-disabled) { color: @text-color-inverse; background-color: @primary-color; } } } } ================================================ FILE: src/vscode-windhawk-ui/apps/vscode-windhawk-ui/src/app/app.tsx ================================================ import { ConfigProvider } from 'antd'; import 'prism-themes/themes/prism-vsc-dark-plus.css'; import { useCallback, useEffect, useMemo, useState } from 'react'; import 'react-diff-view/style/index.css'; import { useTranslation } from 'react-i18next'; import './App.css'; import { AppUISettingsContext, AppUISettingsContextType, } from './appUISettings'; import { setLanguage } from './i18n'; import { mockAppUISettings, useMockData } from './panel/mockData'; import Panel from './panel/Panel'; import Sidebar from './sidebar/Sidebar'; import { useGetInitialAppSettings, useSetNewAppSettings } from './webviewIPC'; function WhenTranslationIsReady( props: React.PropsWithChildren> ) { const { ready } = useTranslation(); // https://stackoverflow.com/a/63898849 // eslint-disable-next-line react/jsx-no-useless-fragment return ready ? <>{props.children} : null; } function App() { const content = useMemo( () => document.querySelector('body')?.getAttribute('data-content') ?? (document.location.hash === '#/debug_sidebar' ? 'sidebar' : 'panel'), [] ); const [appUISettings, setAppUISettings] = useState(null); const [direction, setDirection] = useState<'ltr' | 'rtl'>('ltr'); const applyNewLanguage = useCallback((language?: string) => { setLanguage(language); const rtlLanguages = ['ar', 'he', 'fa', 'ur']; if (language && rtlLanguages.includes(language.split('-')[0])) { setDirection('rtl'); document.documentElement.setAttribute('dir', 'rtl'); } else { setDirection('ltr'); document.documentElement.setAttribute('dir', 'ltr'); } }, []); const { getInitialAppSettings } = useGetInitialAppSettings( useCallback((data) => { applyNewLanguage(data.appUISettings?.language); setAppUISettings(data.appUISettings || {}); }, [applyNewLanguage]) ); useEffect(() => { if (!useMockData) { getInitialAppSettings({}); } else { applyNewLanguage(mockAppUISettings?.language); setAppUISettings(mockAppUISettings || {}); } }, [applyNewLanguage, getInitialAppSettings]); useSetNewAppSettings( useCallback((data) => { applyNewLanguage(data.appUISettings?.language); setAppUISettings(data.appUISettings || {}); }, [applyNewLanguage]) ); if (!content || !appUISettings) { return null; } return ( {content === 'panel' ? ( ) : content === 'sidebar' ? ( ) : ( '' )} ); } export default App; ================================================ FILE: src/vscode-windhawk-ui/apps/vscode-windhawk-ui/src/app/appUISettings.ts ================================================ import React from 'react'; import { AppUISettings } from './webviewIPCMessages'; export type AppUISettingsContextType = Partial; export const AppUISettingsContext = React.createContext({}); ================================================ FILE: src/vscode-windhawk-ui/apps/vscode-windhawk-ui/src/app/components/EllipsisText.tsx ================================================ import { Typography } from 'antd'; import { TooltipPlacement } from 'antd/lib/tooltip'; import { useEffect, useRef, useState } from 'react'; interface Props extends React.PropsWithChildren { className?: string; style?: React.CSSProperties; tooltipPlacement?: TooltipPlacement; } /** * A text component that automatically shows a tooltip when truncated. * Uses ResizeObserver to recalculate ellipsis on width changes. * Automatically hides tooltip when resizing to prevent stale tooltip display. */ function EllipsisText(props: Props) { const containerRef = useRef(null); const [tooltipHide, setTooltipHide] = useState(false); const [ellipsisKey, setEllipsisKey] = useState(0); useEffect(() => { const element = containerRef.current; if (!element) { return; } const resizeObserver = new ResizeObserver(() => { // Prevent tooltip from being shown when ellipsis appears setTooltipHide(true); // Trigger ellipsis recalculation by changing the key setEllipsisKey((prev) => prev + 1); }); resizeObserver.observe(element); return () => { resizeObserver.disconnect(); }; }, []); return ( { if (visible) { setTooltipHide(false); } }, ... ( tooltipHide ? { open: false } : {} ) }, }} > {props.children} {/* Change key to force ellipsis recalculation on resize */} ); } export default EllipsisText; ================================================ FILE: src/vscode-windhawk-ui/apps/vscode-windhawk-ui/src/app/components/InputWithContextMenu.tsx ================================================ import { Dropdown, DropdownProps, Input, InputNumber, InputNumberProps, MenuProps, Popconfirm, PopconfirmProps, Select, SelectProps, } from 'antd'; import { InputProps, InputRef, TextAreaProps } from 'antd/lib/input'; import { TextAreaRef } from 'antd/lib/input/TextArea'; import { forwardRef, useCallback, useEffect, useImperativeHandle, useMemo, useRef } from 'react'; import { useTranslation } from 'react-i18next'; function useItems() { const { t } = useTranslation(); const items: MenuProps['items'] = useMemo( () => [ { label: t('general.cut'), key: 'cut', }, { label: t('general.copy'), key: 'copy', }, { label: t('general.paste'), key: 'paste', }, { type: 'divider', }, { label: t('general.selectAll'), key: 'selectAll', }, ], [t] ); return items; } function onClick( textArea: HTMLTextAreaElement | HTMLInputElement | null | undefined, key: string ) { if (textArea) { textArea.focus(); document.execCommand(key); } document.body.classList.remove('windhawk-no-pointer-events'); } function onOpenChange(open: boolean) { if (open) { document.body.classList.add('windhawk-no-pointer-events'); } else { document.body.classList.remove('windhawk-no-pointer-events'); } } const InputWithContextMenu = forwardRef( ({ children, ...rest }, ref) => { const items = useItems(); const internalRef = useRef(null); useImperativeHandle(ref, () => internalRef.current || ({} as InputRef)); const handleMenuClick = useCallback( (info: { key: string }) => onClick(internalRef.current?.input || null, info.key), [] ); useEffect(() => { return () => { document.body.classList.remove('windhawk-no-pointer-events'); }; }, []); return ( {children} ); } ); InputWithContextMenu.displayName = 'InputWithContextMenu'; const InputNumberWithContextMenu = forwardRef( ({ children, ...rest }, ref) => { const items = useItems(); const internalRef = useRef(null); useImperativeHandle(ref, () => internalRef.current || ({} as HTMLInputElement)); const handleMenuClick = useCallback( (info: { key: string }) => onClick(internalRef.current || null, info.key), [] ); useEffect(() => { return () => { document.body.classList.remove('windhawk-no-pointer-events'); }; }, []); return ( {children} ); } ); InputNumberWithContextMenu.displayName = 'InputNumberWithContextMenu'; const TextAreaWithContextMenu = forwardRef( ({ children, ...rest }, ref) => { const items = useItems(); const internalRef = useRef(null); useImperativeHandle(ref, () => internalRef.current || ({} as TextAreaRef)); const handleMenuClick = useCallback( (info: { key: string }) => onClick(internalRef.current?.resizableTextArea?.textArea || null, info.key), [] ); useEffect(() => { return () => { document.body.classList.remove('windhawk-no-pointer-events'); }; }, []); return ( {children} ); } ); TextAreaWithContextMenu.displayName = 'TextAreaWithContextMenu'; function SelectModal({ children, ...rest }: SelectProps) { const handleDropdownVisibleChange = useCallback( (open: boolean) => { onOpenChange(open); rest.onDropdownVisibleChange?.(open); }, [rest] ); return ( ); } function PopconfirmModal({ children, ...rest }: PopconfirmProps) { const handleOpenChange = useCallback( (open: boolean) => { onOpenChange(open); rest.onOpenChange?.(open); }, [rest] ); return ( {children} ); } function DropdownModal({ children, ...rest }: DropdownProps) { const handleOpenChange = useCallback( (open: boolean) => { onOpenChange(open); rest.onOpenChange?.(open); }, [rest] ); return ( {children} ); } function dropdownModalDismissed() { document.body.classList.remove('windhawk-no-pointer-events'); } export { InputWithContextMenu, InputNumberWithContextMenu, TextAreaWithContextMenu, SelectModal, PopconfirmModal, DropdownModal, dropdownModalDismissed, }; ================================================ FILE: src/vscode-windhawk-ui/apps/vscode-windhawk-ui/src/app/components/ReactMarkdownCustom.tsx ================================================ import type { Components } from 'react-markdown'; import ReactMarkdown from 'react-markdown'; import rehypeRaw from 'rehype-raw'; import rehypeSanitize from 'rehype-sanitize'; import rehypeSlug from 'rehype-slug'; import remarkGfm from 'remark-gfm'; import styled from 'styled-components'; import type { PluggableList } from 'unified'; import { sanitizeUrl } from '../utils'; const ReactMarkdownStyleWrapper = styled.div<{ $direction?: 'ltr' | 'rtl' }>` // Word-wrap long lines. overflow-wrap: break-word; ${props => props.$direction && ` direction: ${props.$direction}; text-align: ${props.$direction === 'rtl' ? 'right' : 'left'}; `} // Table style. // https://github.com/micromark/micromark-extension-gfm-table#css table { border-spacing: 0; border-collapse: collapse; display: block; margin-top: 0; margin-bottom: 16px; width: max-content; max-width: 100%; overflow: auto; } td, th { padding: 6px 13px; border: 1px solid #434343; } `; interface Props { markdown: string; components?: Components; allowHtml?: boolean; direction?: 'ltr' | 'rtl'; } function ReactMarkdownCustom({ markdown, components, allowHtml = false, direction }: Props) { // Custom link component that sanitizes URLs const defaultComponents: Components = { a: ({ node, href, children, ...props }) => { const sanitizedHref = sanitizeUrl(href); return {children}; } }; // Merge provided components with default components const mergedComponents = { ...defaultComponents, ...components }; // Minimal schema: only allow basic formatting tags const sanitizeSchema = { tagNames: [ // Headings 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', // Text formatting 'p', 'br', 'strong', 'b', 'em', 'i', // Lists 'ul', 'ol', 'li', // Blockquotes 'blockquote', // Code 'code', 'pre', // Links 'a' ], attributes: { a: ['href'] // Only href for links, no other attributes }, protocols: { href: ['http', 'https', 'mailto'] // Safe protocols only }, // Explicitly strip dangerous elements strip: ['script', 'style', 'iframe', 'object', 'embed', 'img', 'video', 'audio'] }; // CRITICAL: rehype-raw MUST come before rehype-sanitize const rehypePlugins: PluggableList = allowHtml ? [rehypeSlug, rehypeRaw, [rehypeSanitize, sanitizeSchema]] : [rehypeSlug]; const remarkPlugins: PluggableList = [remarkGfm]; return ( ); } export default ReactMarkdownCustom; ================================================ FILE: src/vscode-windhawk-ui/apps/vscode-windhawk-ui/src/app/i18n.ts ================================================ import i18n from 'i18next'; import Backend from 'i18next-http-backend'; import { initReactI18next } from 'react-i18next'; let initialized = false; const defaultLanguage = 'en'; function i18nInitialize(language: string) { i18n // load translation using http -> see /public/locales (i.e. https://github.com/i18next/react-i18next/tree/master/example/react/public/locales) // learn more: https://github.com/i18next/i18next-http-backend .use(Backend) // pass the i18n instance to react-i18next. .use(initReactI18next) // init i18next // for all options read: https://www.i18next.com/overview/configuration-options .init({ lng: language, fallbackLng: defaultLanguage, //debug: true, returnNull: false, returnEmptyString: false, interpolation: { escapeValue: false, // not needed for react as it escapes by default }, react: { useSuspense: false, }, backend: { // Use a relative load path. loadPath: './locales/{{lng}}/{{ns}}.json', }, }); } export function setLanguage(language?: string) { if (initialized) { i18n.changeLanguage(language || defaultLanguage); return; } i18nInitialize(language || defaultLanguage); initialized = true; } ================================================ FILE: src/vscode-windhawk-ui/apps/vscode-windhawk-ui/src/app/panel/About.tsx ================================================ import { Alert, Button } from 'antd'; import { useContext, useState } from 'react'; import { Trans, useTranslation } from 'react-i18next'; import styled from 'styled-components'; import { AppUISettingsContext } from '../appUISettings'; import { ChangelogModal } from './ChangelogModal'; import { UpdateModal } from './UpdateModal'; const AboutContainer = styled.div` display: flex; flex-direction: column; height: 100%; // Without this the centered content looks too low. padding-bottom: 10vh; `; const AboutContent = styled.div` margin: auto; text-align: center; `; const ContentSection = styled.div` margin-bottom: 1.5em; h1, h2, h3, h4, h5, h6 { margin-bottom: 0; } `; const UpdateNoticeDescription = styled.div` display: flex; flex-direction: column; row-gap: 8px; `; const ButtonGroup = styled.div` display: flex; gap: 8px; justify-content: center; `; function About() { const { t } = useTranslation(); const [changelogModalOpen, setChangelogModalOpen] = useState(false); const [updateModalOpen, setUpdateModalOpen] = useState(false); const { updateIsAvailable } = useContext(AppUISettingsContext); const currentVersion = ( process.env['REACT_APP_VERSION'] || 'unknown' ).replace(/^(\d+(?:\.\d+)+?)(\.0+)+$/, '$1'); return (

{t('about.title', { // version: currentVersion + ' ' + t('about.beta'), version: currentVersion, })}

{t('about.subtitle')}

website]} />

{updateIsAvailable && ( {t('about.update.title')}} description={
{t('about.update.subtitle')}
} type="info" />
)}

{t('about.links.title')}

{t('about.builtWith.title')}

VSCodium {' - '} {t('about.builtWith.vscodium')}
LLVM MinGW {' - '} {t('about.builtWith.llvmMingw')}
MinHook-Detours {' - '} {t('about.builtWith.minHook')}
{t('about.builtWith.others')}
setChangelogModalOpen(false)} /> setUpdateModalOpen(false)} />
); } export default About; ================================================ FILE: src/vscode-windhawk-ui/apps/vscode-windhawk-ui/src/app/panel/AppHeader.tsx ================================================ import { faCog, faHome, faInfo, faList, IconDefinition, } from '@fortawesome/free-solid-svg-icons'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { Badge, Button } from 'antd'; import { PresetStatusColorType } from 'antd/lib/_util/colors'; import { useContext } from 'react'; import { useTranslation } from 'react-i18next'; import { useLocation, useNavigate } from 'react-router-dom'; import styled from 'styled-components'; import { AppUISettingsContext } from '../appUISettings'; import logo from './assets/logo-white.svg'; const Header = styled.header` display: flex; align-items: center; flex-wrap: wrap; padding: 20px 20px 0; column-gap: 20px; margin: 0 auto; width: 100%; max-width: var(--app-max-width); `; const HeaderLogo = styled.div` cursor: pointer; margin-inline-end: auto; font-size: 40px; white-space: nowrap; font-family: Oxanium; user-select: none; `; const LogoImage = styled.img` height: 80px; margin-inline-end: 6px; `; const HeaderButtonsWrapper = styled.div` display: flex; flex-wrap: wrap; gap: 10px; margin: 12px 0; `; const HeaderIcon = styled(FontAwesomeIcon)` margin-inline-end: 8px; `; type HeaderButton = { text: string; route: string; icon: IconDefinition; badge?: { status: PresetStatusColorType; title?: string; }; }; function AppHeader() { const { t } = useTranslation(); const navigate = useNavigate(); const location = useLocation(); const { loggingEnabled, updateIsAvailable } = useContext(AppUISettingsContext); const buttons: HeaderButton[] = [ { text: t('appHeader.home'), route: '/', icon: faHome, }, { text: t('appHeader.explore'), route: '/mods-browser', icon: faList, }, { text: t('appHeader.settings'), route: '/settings', icon: faCog, badge: loggingEnabled ? { status: 'warning', title: t('general.loggingEnabled'), } : undefined, }, { text: t('appHeader.about'), route: '/about', icon: faInfo, badge: updateIsAvailable ? { status: 'error', title: t('about.update.title'), } : undefined, }, ]; return (
navigate('/')}> Windhawk {buttons.map(({ text, route, icon, badge }) => ( ))}
); } export default AppHeader; ================================================ FILE: src/vscode-windhawk-ui/apps/vscode-windhawk-ui/src/app/panel/ChangelogModal.tsx ================================================ import { ConfigProvider, Modal, Result, Spin } from 'antd'; import { useEffect, useRef, useState } from 'react'; import { useTranslation } from 'react-i18next'; import styled from 'styled-components'; import ReactMarkdownCustom from '../components/ReactMarkdownCustom'; import { fetchText } from '../swrHelpers'; const CHANGELOG_URL = 'https://ramensoftware.com/downloads/windhawk_setup.exe?version&changelog'; const ModalContent = styled.div` max-height: 60vh; overflow-y: auto; padding: 16px 0; `; const LoadingContainer = styled.div` display: flex; justify-content: center; align-items: center; padding: 40px; `; interface Props { open: boolean; onClose: () => void; } export function ChangelogModal(props: Props) { const { t } = useTranslation(); const [changelog, setChangelog] = useState(''); const [loading, setLoading] = useState(false); const [hasError, setHasError] = useState(false); const fetchStatusRef = useRef<'idle' | 'loading' | 'success' | 'error'>('idle'); useEffect(() => { // Fetch when modal opens if we haven't successfully fetched yet // On error, allow retry when modal is reopened (not immediate retry) if (props.open && fetchStatusRef.current !== 'success' && fetchStatusRef.current !== 'loading') { fetchStatusRef.current = 'loading'; setLoading(true); setHasError(false); fetchText(CHANGELOG_URL) .then((textWithNull) => { const text = textWithNull.split('\0', 2)[1] || ''; setChangelog(text); fetchStatusRef.current = 'success'; setLoading(false); }) .catch((err) => { console.error('Failed to fetch changelog:', err); setHasError(true); fetchStatusRef.current = 'error'; setLoading(false); }); } }, [props.open]); return ( {loading && ( )} {hasError && ( )} {changelog && !loading && !hasError && ( )} ); } ================================================ FILE: src/vscode-windhawk-ui/apps/vscode-windhawk-ui/src/app/panel/CreateNewModButton.tsx ================================================ import { faPen } from '@fortawesome/free-solid-svg-icons'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { Button } from 'antd'; import { useTranslation } from 'react-i18next'; import styled from 'styled-components'; import { createNewMod } from '../webviewIPC'; import DevModeAction from './DevModeAction'; const ButtonContainer = styled.div` position: fixed; bottom: 0; inset-inline-start: 0; inset-inline-end: 0; margin: 0 auto; width: 100%; max-width: var(--app-max-width); z-index: 100; /* Monaco editor uses two-digit z-index values */ `; const CreateButton = styled(Button)` position: absolute; inset-inline-end: 32px; bottom: 20px; background-color: var(--app-background-color) !important; box-shadow: 0 3px 6px rgb(100 100 100 / 16%), 0 1px 2px rgb(100 100 100 / 23%); `; const CreateButtonIcon = styled(FontAwesomeIcon)` margin-inline-end: 8px; `; function CreateNewModButton() { const { t } = useTranslation(); return ( createNewMod()} renderButton={(onClick) => ( {t('createNewModButton.title')} )} /> ); } export default CreateNewModButton; ================================================ FILE: src/vscode-windhawk-ui/apps/vscode-windhawk-ui/src/app/panel/DevModeAction.tsx ================================================ import { Checkbox } from 'antd'; import { TooltipPlacement } from 'antd/lib/tooltip'; import React, { JSX, useContext, useState } from 'react'; import { useTranslation } from 'react-i18next'; import styled from 'styled-components'; import { AppUISettingsContext } from '../appUISettings'; import { PopconfirmModal } from '../components/InputWithContextMenu'; import { useUpdateAppSettings } from '../webviewIPC'; const PopconfirmTitleContent = styled.div` display: flex; flex-direction: column; row-gap: 8px; max-width: 300px; `; interface Props { disabled?: boolean; popconfirmPlacement?: TooltipPlacement; onClick: () => void; renderButton: (onClick?: () => void) => JSX.Element; } function DevModeAction(props: React.PropsWithChildren) { const { t } = useTranslation(); const { devModeOptOut, devModeUsedAtLeastOnce } = useContext(AppUISettingsContext); const [optOutChecked, setOptOutChecked] = useState(false); const { updateAppSettings } = useUpdateAppSettings(() => undefined); if (devModeOptOut) { return null; } return (
{t('devModeAction.message')}
setOptOutChecked(e.target.checked)} > {t('devModeAction.hideOptionsCheckbox')} } okText={ optOutChecked ? t('devModeAction.hideOptionsButton') : t('devModeAction.beginCodingButton') } cancelText={t('devModeAction.cancelButton')} onConfirm={() => { if (optOutChecked) { updateAppSettings({ appSettings: { devModeOptOut: true, }, }); } else { updateAppSettings({ appSettings: { devModeUsedAtLeastOnce: true, }, }); props.onClick(); } }} onOpenChange={(open) => open && setOptOutChecked(false)} > {props.renderButton( !devModeUsedAtLeastOnce ? undefined : () => props.onClick() )}
); } export default DevModeAction; ================================================ FILE: src/vscode-windhawk-ui/apps/vscode-windhawk-ui/src/app/panel/ModCard.tsx ================================================ import { faUser } from '@fortawesome/free-solid-svg-icons'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { Badge, Button, Card, Divider, Rate, Switch, Tooltip } from 'antd'; import { useTranslation } from 'react-i18next'; import styled, { css } from 'styled-components'; import EllipsisText from '../components/EllipsisText'; import { PopconfirmModal } from '../components/InputWithContextMenu'; import { ModMetadata, RepositoryDetails } from '../webviewIPCMessages'; import localModIcon from './assets/local-mod-icon.svg'; import ModMetadataLine from './ModMetadataLine'; const ModCardWrapper = styled.div` // Fill whole height. > .ant-ribbon-wrapper { height: 100%; } `; const ModCardRibbon = styled(Badge.Ribbon) <{ $hidden: boolean }>` ${({ $hidden }) => $hidden && css` display: none; `} `; const ModCardWrapperInner = styled(Card)` // Fill whole height and stick buttons to the bottom. height: 100%; > .ant-card-body { height: 100%; display: flex; flex-direction: column; > .ant-card-meta { flex: 1; } } `; const ModCardTitleContainer = styled.div` display: flex; `; const ModCardTitle = styled(EllipsisText)` flex: 1; `; // Used to prevent from the title to overlap with the ribbon. const ModCardTitleRibbonContent = styled.span` position: static; margin-inline-end: -16px; font-weight: normal; visibility: hidden; `; const ModLocalIcon = styled.img` height: 24px; margin-inline-start: 4px; cursor: help; `; const ModCardActionsContainer = styled.div` display: flex; align-items: center; margin-top: 20px; text-align: end; > :not(:last-child) { margin-inline-end: 10px; } > :last-child { margin-inline-start: auto; } `; const ModRate = styled(Rate)` font-size: 14px; pointer-events: none; > .ant-rate-star { margin-inline-end: 2px; } `; const RatingBreakdownTooltip = styled.div` display: grid; grid-template-columns: auto 1fr auto; gap: 8px; align-items: center; min-width: 234px; // Max tooltip width `; const BreakdownLine = styled.div` display: contents; `; const BreakdownStars = styled.span` display: flex; `; const BreakdownRate = styled(Rate)` font-size: 12px; pointer-events: none; > .ant-rate-star { margin-inline-end: 2px; } `; const BreakdownProgressContainer = styled.div` height: 8px; background-color: rgba(23, 18, 18, 0.1); border-radius: 4px; `; const BreakdownProgressBar = styled.div<{ $percentage: number }>` height: 100%; width: ${(props) => props.$percentage}%; background-color: #fadb14; border-radius: 4px; animation: progressBarFill 0.3s ease; @keyframes progressBarFill { from { width: 0%; } } `; const BreakdownCount = styled.span` color: rgba(255, 255, 255, 0.85); text-align: end; font-size: 12px; white-space: nowrap; `; interface Props { ribbonText?: string; title: string; isLocal?: boolean; description?: string; modMetadata?: ModMetadata; repositoryDetails?: RepositoryDetails; buttons: { text: React.ReactNode; confirmText?: string; confirmOkText?: string; confirmCancelText?: string; confirmIsDanger?: boolean; onClick: () => void; badge?: { tooltip?: string; }; }[]; switch?: { title?: string; checked?: boolean; disabled?: boolean; onChange: (checked: boolean) => void; }; } function ModCard(props: Props) { const { t } = useTranslation(); // Derive stats from repositoryDetails if available const stats = props.repositoryDetails ? { users: props.repositoryDetails.users, rating: props.repositoryDetails.rating, ratingBreakdown: props.repositoryDetails.ratingBreakdown, } : null; const renderRatingTooltip = () => { if (!stats) { return t('mod.notRated'); } // Calculate total users for percentage const totalUsers = stats.ratingBreakdown.reduce( (sum, count) => sum + count, 0 ); if (totalUsers === 0) { return t('mod.notRated'); } return ( {[5, 4, 3, 2, 1].map((stars) => { const count = stats.ratingBreakdown[stars - 1] ?? 0; const percentage = (count / totalUsers) * 100; return ( {t('mod.users', { count, formattedCount: count.toLocaleString(), })} ); })} ); }; return ( {props.title} {props.isLocal && ( )} {props.ribbonText && ( // Used to prevent from the title to overlap with the ribbon. {props.ribbonText} )} {props.modMetadata && ( )} } description={props.description || {t('mod.noDescription')}} /> {props.buttons.map((button, i) => { const buttonElement = button.confirmText ? ( button.onClick()} > ) : ( ); if (button.badge) { return ( {buttonElement} ); } return buttonElement; })} {props.switch && ( props.switch?.onChange(checked)} /> )} {stats && (
{' '} {t('mod.users', { count: stats.users, formattedCount: stats.users.toLocaleString(), })}
)}
); } export default ModCard; ================================================ FILE: src/vscode-windhawk-ui/apps/vscode-windhawk-ui/src/app/panel/ModDetails.tsx ================================================ import { Badge, Button, Card, Radio, Result, Spin, Tooltip } from 'antd'; import { useCallback, useEffect, useMemo, useRef, useState } from 'react'; import { useTranslation } from 'react-i18next'; import styled from 'styled-components'; import { useGetModSourceData, useGetRepositoryModSourceData, } from '../webviewIPC'; import { ModConfig, ModMetadata, RepositoryDetails, InitialSettings } from '../webviewIPCMessages'; import ModDetailsAdvanced from './ModDetailsAdvanced'; import ModDetailsChangelog from './ModDetailsChangelog'; import ModDetailsHeader, { ModStatus } from './ModDetailsHeader'; import ModDetailsReadme from './ModDetailsReadme'; import ModDetailsSettings from './ModDetailsSettings'; import ModDetailsSource from './ModDetailsSource'; import ModDetailsSourceDiff from './ModDetailsSourceDiff'; import { VersionSelectorModal } from './VersionSelectorModal'; import { mockInstalledModSourceData, mockModVersionSource } from './mockData'; const ModDetailsContainer = styled.div` flex: 1; padding-top: 20px; `; const ModDetailsCard = styled(Card)` min-height: 100%; border-bottom: none; border-bottom-left-radius: 0; border-bottom-right-radius: 0; `; const ModVersionRadioGroup = styled(Radio.Group)` font-weight: normal; margin-bottom: 8px; `; const ProgressSpin = styled(Spin)` display: block; margin-inline-start: auto; margin-inline-end: auto; font-size: 32px; `; const NoDataMessage = styled.div` color: rgba(255, 255, 255, 0.45); font-style: italic; `; type InstalledModDetails = { metadata: ModMetadata | null; config: ModConfig | null; userRating?: number; }; type RepositoryModDetails = { metadata?: ModMetadata; details?: RepositoryDetails; }; type ModSourceData = { source: string | null; metadata: ModMetadata | null; readme: string | null; initialSettings: InitialSettings | null; }; type TabKey = 'details' | 'settings' | 'code' | 'changelog' | 'advanced' | 'changes'; type ViewMode = 'installed' | 'repository' | 'custom'; interface ModVersionSelectorProps { // Version state currentView: ViewMode; selectedCustomVersion: string | null; // Version info (null if not available) installed: { version?: string } | null; repository: ( { status: 'loading' } | { status: 'loaded'; version?: string } | { status: 'failed' } | null ); // Callbacks onViewChange: (value: Exclude) => void; onOpenVersionModal: () => void; } function ModVersionSelector(props: ModVersionSelectorProps) { const { t } = useTranslation(); const { currentView, selectedCustomVersion, installed, repository, onViewChange, onOpenVersionModal, } = props; if (!installed && !selectedCustomVersion) { return null; } if (!repository) { return null; } return ( { // Don't allow switching to 'custom' value, it will be set after // selecting a version in the modal. if (e.target.value !== 'custom') { onViewChange(e.target.value); } }} > {installed && ( {t('modDetails.header.installedVersion')} {installed.version && `: ${installed.version}`} )} {t('modDetails.header.latestVersion')} {repository.status === 'loading' ? ': ' + t('modDetails.header.loading') : repository.status === 'failed' ? ': ' + t('modDetails.header.loadingFailed') : repository.status === 'loaded' && repository.version ? `: ${repository.version}` : ''} {selectedCustomVersion ? t('modDetails.header.selectedVersion', { version: selectedCustomVersion }) : t('modDetails.header.otherVersions')} ); } interface ModDetailsTabContentProps { // Tab state modId: string; isLocalMod: boolean; currentView: ViewMode; activeTab: TabKey; // Source data modSourceData: ModSourceData | null; // Additional source data for changes tab installedModSourceData: ModSourceData | null; selectedModSourceData: ModSourceData | null; installedVersionIsLatest: boolean; // Settings tab navigation canNavigateAwayRef: React.MutableRefObject<(() => Promise) | null>; // Retry handler onRetryLoad: () => void; } function ModDetailsTabContent(props: ModDetailsTabContentProps) { const { t } = useTranslation(); const { modId, isLocalMod, currentView, activeTab, modSourceData, installedModSourceData, selectedModSourceData, installedVersionIsLatest, canNavigateAwayRef, onRetryLoad, } = props; const isLoading = ( !modSourceData || (activeTab === 'changes' && ( !installedModSourceData || !selectedModSourceData )) ); if (isLoading) { const shouldShowLoading = ( currentView === 'repository' || currentView === 'custom' || activeTab === 'changes'); if (shouldShowLoading) { return ; } return null; } const isLoadingFailed = ( ( currentView === 'repository' || currentView === 'custom' || activeTab === 'changes' ) && !selectedModSourceData?.source ); if (isLoadingFailed) { return ( {t('general.tryAgain')} , ]} /> ); } if (activeTab === 'details') { return modSourceData.readme ? ( ) : ( {t('modDetails.details.noData')} ); } if (activeTab === 'settings') { return modSourceData.initialSettings ? ( { canNavigateAwayRef.current = callback; }} /> ) : ( {t('modDetails.settings.noData')} ); } if (activeTab === 'code') { return modSourceData.source ? ( ) : ( {t('modDetails.code.noData')} ); } if (activeTab === 'changelog') { return ( } modId={modId} /> ); } if (activeTab === 'advanced') { return ; } if (activeTab === 'changes') { const installedModSource = installedModSourceData?.source ?? null; const selectedModSource = selectedModSourceData?.source ?? null; if (installedModSource && selectedModSource) { return installedVersionIsLatest ? ( {t('modDetails.changes.noData')} ) : ( ); } return {t('modDetails.code.noData')}; } return null; } interface Props { modId: string; installedModDetails?: InstalledModDetails; repositoryModDetails?: RepositoryModDetails; loadRepositoryData?: boolean; goBack: () => void; installMod?: (modSource: string) => void; updateMod?: (modSource: string, disabled: boolean) => void; forkModFromSource?: (modSource: string) => void; compileMod: () => void; enableMod: (enable: boolean) => void; editMod: () => void; forkMod: () => void; deleteMod: () => void; updateModRating: (newRating: number) => void; } function ModDetails(props: Props) { const { t } = useTranslation(); const { modId, installedModDetails, repositoryModDetails, loadRepositoryData, } = props; const isLocalMod = modId.startsWith('local@'); const [installedModSourceData, setInstalledModSourceData] = useState(null); const [repositoryModSourceData, setRepositoryModSourceData] = useState(null); const [selectedCustomVersion, setSelectedCustomVersion] = useState(null); const [versionTimestamps, setVersionTimestamps] = useState>({}); const [customVersionSourceData, setCustomVersionSourceData] = useState(null); const [isVersionModalOpen, setIsVersionModalOpen] = useState(false); const { getModSourceData } = useGetModSourceData( useCallback( (data) => { if (data.modId === modId) { setInstalledModSourceData(data.data); } }, [modId] ) ); useEffect(() => { setInstalledModSourceData(mockInstalledModSourceData); if (installedModDetails?.metadata) { getModSourceData({ modId }); } }, [modId, installedModDetails?.metadata, getModSourceData]); const { getRepositoryModSourceData } = useGetRepositoryModSourceData( useCallback( (data) => { if (data.modId === modId && (data.version ?? null) === selectedCustomVersion) { if (data.version) { setCustomVersionSourceData(data.data); } else { setRepositoryModSourceData(data.data); } } }, [modId, selectedCustomVersion] ) ); useEffect(() => { setRepositoryModSourceData(null); if (repositoryModDetails || loadRepositoryData) { getRepositoryModSourceData({ modId }); } }, [ getRepositoryModSourceData, loadRepositoryData, modId, repositoryModDetails, ]); const [selectedModDetails, setSelectedModDetails] = useState< Exclude | null >(null); useEffect(() => { if ( !(installedModDetails && (repositoryModDetails || loadRepositoryData)) ) { // Only one type can be selected, reset selection. setSelectedModDetails(null); } }, [installedModDetails, repositoryModDetails, loadRepositoryData]); const modDetailsToShow: ViewMode = selectedCustomVersion ? 'custom' : selectedModDetails || (installedModDetails ? 'installed' : 'repository'); const [activeTab, setActiveTab] = useState('details'); // Track if settings can navigate away const canNavigateAwayRef = useRef<(() => Promise) | null>(null); const handleTabChange = useCallback(async (key: string) => { // Check if we can navigate away from settings if (canNavigateAwayRef.current) { const canNavigate = await canNavigateAwayRef.current(); if (!canNavigate) { return; } } setActiveTab(key as TabKey); }, []); const handleOpenVersionModal = useCallback(() => { setIsVersionModalOpen(true); }, []); const handleVersionSelect = useCallback((version: string, timestamps: Record) => { setSelectedCustomVersion(version); setVersionTimestamps(timestamps); setIsVersionModalOpen(false); // Fetch the source for the selected version. if (mockModVersionSource) { setCustomVersionSourceData(mockModVersionSource(version)); } else { setCustomVersionSourceData(null); getRepositoryModSourceData({ modId, version }); } }, [getRepositoryModSourceData, modId]); const handleClearCustomVersion = useCallback(() => { setSelectedCustomVersion(null); setVersionTimestamps({}); setCustomVersionSourceData(null); }, []); const tabList: Array<{ key: TabKey; tab: React.ReactNode }> = [ { key: 'details', tab: t('modDetails.details.title'), }, ]; if (modDetailsToShow === 'installed' && installedModDetails?.config) { tabList.push({ key: 'settings', tab: t('modDetails.settings.title'), }); } tabList.push({ key: 'code', tab: t('modDetails.code.title'), }); if (!isLocalMod) { tabList.push({ key: 'changelog', tab: t('modDetails.changelog.title'), }); } if (modDetailsToShow === 'installed') { const hasLogging = installedModDetails?.config?.loggingEnabled || installedModDetails?.config?.debugLoggingEnabled; tabList.push({ key: 'advanced', tab: hasLogging ? ( <> {t('modDetails.advanced.title')} {' '} ) : t('modDetails.advanced.title'), }); } if (installedModDetails && (repositoryModDetails || loadRepositoryData)) { tabList.push({ key: 'changes', tab: t('modDetails.changes.title'), }); } const availableActiveTab = tabList.find((x) => x.key === activeTab) ? activeTab : 'details'; // Clear the navigation callback when not on settings tab useEffect(() => { if (availableActiveTab !== 'settings') { canNavigateAwayRef.current = null; } }, [availableActiveTab]); let installedModMetadata: ModMetadata = {}; if (installedModSourceData?.metadata) { installedModMetadata = installedModSourceData.metadata; } else if (installedModDetails) { installedModMetadata = installedModDetails.metadata || {}; } let repositoryModMetadata: ModMetadata = {}; if (repositoryModSourceData?.metadata) { repositoryModMetadata = repositoryModSourceData.metadata; } else if (repositoryModDetails?.metadata) { repositoryModMetadata = repositoryModDetails.metadata; } let modMetadata: ModMetadata = {}; let modSourceData: ModSourceData | null = null; if (modDetailsToShow === 'custom') { modMetadata = customVersionSourceData?.metadata || {}; modSourceData = customVersionSourceData; } else if (modDetailsToShow === 'installed') { modMetadata = installedModMetadata; modSourceData = installedModSourceData; } else if (modDetailsToShow === 'repository') { modMetadata = repositoryModMetadata; modSourceData = repositoryModSourceData; } const installedModSource = installedModSourceData?.source ?? null; const repositoryModSource = repositoryModSourceData?.source ?? null; const selectedModSourceData = modDetailsToShow === 'custom' ? customVersionSourceData : repositoryModSourceData; const selectedModSource = selectedModSourceData?.source ?? null; const installedVersionIsLatest = useMemo(() => { return !!( selectedModSource && installedModSource && selectedModSource === installedModSource ); }, [selectedModSource, installedModSource]); // Determine if the selected custom version is a downgrade const isDowngrade = useMemo(() => { if (!selectedCustomVersion || !installedModMetadata.version) { return false; } const selectedTimestamp = versionTimestamps[selectedCustomVersion]; const currentTimestamp = versionTimestamps[installedModMetadata.version]; return selectedTimestamp !== undefined && currentTimestamp !== undefined && selectedTimestamp < currentTimestamp; }, [selectedCustomVersion, installedModMetadata.version, versionTimestamps]); let modStatus: ModStatus = 'not-installed'; if (modDetailsToShow === 'installed' && installedModDetails) { if (!installedModDetails.config) { modStatus = 'installed-not-compiled'; } else if (!installedModDetails.config.disabled) { modStatus = 'enabled'; } else { modStatus = 'disabled'; } } return ( { setSelectedModDetails(value); // Clear custom version when switching back to // installed/latest. handleClearCustomVersion(); }} onOpenVersionModal={handleOpenVersionModal} /> } modId={modId} modMetadata={modMetadata} modConfig={ (modDetailsToShow === 'installed' && installedModDetails?.config) || undefined} modStatus={modStatus} updateAvailable={ !!( installedModDetails && (repositoryModDetails || loadRepositoryData) ) } installedVersionIsLatest={installedVersionIsLatest} isDowngrade={isDowngrade} userRating={installedModDetails?.userRating} repositoryDetails={ (modDetailsToShow === 'repository' && repositoryModDetails?.details) || undefined} callbacks={{ goBack: props.goBack, installMod: props.installMod && selectedModSource ? () => props.installMod?.(selectedModSource) : undefined, updateMod: props.updateMod && selectedModSource ? () => props.updateMod?.( selectedModSource, modStatus === 'disabled' ) : undefined, forkModFromSource: props.forkModFromSource && selectedModSource ? () => props.forkModFromSource?.(selectedModSource) : undefined, compileMod: props.compileMod, enableMod: props.enableMod, editMod: props.editMod, forkMod: props.forkMod, deleteMod: props.deleteMod, updateModRating: props.updateModRating, onOpenVersionModal: handleOpenVersionModal, }} /> } tabList={tabList} activeTabKey={availableActiveTab} onTabChange={handleTabChange} > { if (selectedCustomVersion) { getRepositoryModSourceData({ modId, version: selectedCustomVersion, }); } else if (repositoryModDetails || loadRepositoryData) { getRepositoryModSourceData({ modId }); } }} /> setIsVersionModalOpen(false)} /> ); } export default ModDetails; ================================================ FILE: src/vscode-windhawk-ui/apps/vscode-windhawk-ui/src/app/panel/ModDetailsAdvanced.tsx ================================================ import { Alert, Button, Dropdown, List, message, Select, Space, Switch } from 'antd'; import { useCallback, useEffect, useState } from 'react'; import { Trans, useTranslation } from 'react-i18next'; import styled from 'styled-components'; import { SelectModal, TextAreaWithContextMenu } from '../components/InputWithContextMenu'; import { showAdvancedDebugLogOutput, useGetModConfig, useGetModSettings, useSetModSettings, useUpdateModConfig, } from '../webviewIPC'; const SettingsListItemMeta = styled(List.Item.Meta)` .ant-list-item-meta { margin-bottom: 8px; } .ant-list-item-meta-title { margin-bottom: 0; } `; const SettingsSelect = styled(SelectModal)` width: 200px; `; const SpaceWithWidth = styled(Space)` width: 100%; max-width: 600px; `; function engineArrayToProcessList(processArray: string[]) { return processArray.join('\n'); } function engineProcessListToArray(processList: string) { return processList .split('\n') .map((x) => x.replace(/["/<>|]/g, '').trim()) .filter((x) => x); } interface Props { modId: string; } function ModDetailsAdvanced({ modId }: Props) { const { t } = useTranslation(); const [debugLogging, setDebugLogging] = useState(); const [modSettingsUI, setModSettingsUI] = useState(); const [modSettingsUIModified, setModSettingsUIModified] = useState(false); const [customInclude, setCustomInclude] = useState(); const [customIncludeModified, setCustomIncludeModified] = useState(false); const [customExclude, setCustomExclude] = useState(); const [customExcludeModified, setCustomExcludeModified] = useState(false); const [includeExcludeCustomOnly, setIncludeExcludeCustomOnly] = useState(); const [patternsMatchCriticalSystemProcesses, setPatternsMatchCriticalSystemProcesses] = useState(); const { getModConfig } = useGetModConfig( useCallback((data) => { if (data.config?.debugLoggingEnabled) { setDebugLogging(2); } else if (data.config?.loggingEnabled) { setDebugLogging(1); } else { setDebugLogging(0); } setCustomInclude(engineArrayToProcessList(data.config?.includeCustom ?? [])); setCustomExclude(engineArrayToProcessList(data.config?.excludeCustom ?? [])); setIncludeExcludeCustomOnly( data.config?.includeExcludeCustomOnly ?? false ); setPatternsMatchCriticalSystemProcesses( data.config?.patternsMatchCriticalSystemProcesses ?? false ); }, []) ); const { getModSettings } = useGetModSettings<{ formatted?: boolean }>( useCallback((data, context) => { setModSettingsUI( JSON.stringify(data.settings, null, context?.formatted ? 2 : undefined) ); }, []) ); const { setModSettings } = useSetModSettings( useCallback((data) => { if (data.succeeded) { setModSettingsUIModified(false); } }, []) ); const { updateModConfig } = useUpdateModConfig<{ callback?: () => void }>( useCallback((data, context) => { if (data.succeeded) { context?.callback?.(); } }, []) ); useEffect(() => { getModConfig({ modId }); getModSettings({ modId }); }, [getModConfig, getModSettings, modId]); if ( modSettingsUI === undefined || debugLogging === undefined || customInclude === undefined || customExclude === undefined || includeExcludeCustomOnly === undefined || patternsMatchCriticalSystemProcesses === undefined ) { return null; } return ( { const numValue = typeof value === 'number' ? value : 0; setDebugLogging(numValue); updateModConfig({ modId, config: { loggingEnabled: numValue === 1, debugLoggingEnabled: numValue === 2, }, }); }} dropdownMatchSelectWidth={false} > {t('modDetails.advanced.debugLogging.none')} {t('modDetails.advanced.debugLogging.modLogs')} {t('modDetails.advanced.debugLogging.detailedLogs')} { setModSettingsUI(e.target.value); setModSettingsUIModified(true); }} /> { getModSettings( { modId }, { formatted: e.key === 'formatted' } ); }, }} onClick={() => { getModSettings({ modId }); }} > {t('modDetails.advanced.modSettings.loadButton')}
{ setCustomInclude(e.target.value); setCustomIncludeModified(true); }} /> {customInclude.match(/["/<>|]/) && ( |', })} type="warning" showIcon /> )}
{ setCustomExclude(e.target.value); setCustomExcludeModified(true); }} /> {customExclude.match(/["/<>|]/) && ( |', })} type="warning" showIcon /> )}
{ setIncludeExcludeCustomOnly(checked); updateModConfig({ modId, config: { includeExcludeCustomOnly: checked, }, }); }} /> , wiki, ]} /> } /> { setPatternsMatchCriticalSystemProcesses(checked); updateModConfig({ modId, config: { patternsMatchCriticalSystemProcesses: checked, }, }); }} />
); } export default ModDetailsAdvanced; ================================================ FILE: src/vscode-windhawk-ui/apps/vscode-windhawk-ui/src/app/panel/ModDetailsChangelog.tsx ================================================ import { ConfigProvider } from 'antd'; import { Trans, useTranslation } from 'react-i18next'; import styled from 'styled-components'; import useSWR from 'swr'; import ReactMarkdownCustom from '../components/ReactMarkdownCustom'; import { fetchText } from '../swrHelpers'; const ErrorMessage = styled.div` color: rgba(255, 255, 255, 0.45); font-style: italic; `; interface Props { modId: string; loadingNode: React.ReactElement; } function ModDetailsChangelog({ modId, loadingNode }: Props) { const { t } = useTranslation(); const url = `https://mods.windhawk.net/changelogs/${modId}.md`; const { data, error, isLoading } = useSWR(url, fetchText); if (error) { const githubUrl = `https://github.com/ramensoftware/windhawk-mods/blob/pages/changelogs/${modId}.md`; return ( GitHub]} /> ); } if (isLoading) { return loadingNode; } return ( ); } export default ModDetailsChangelog; ================================================ FILE: src/vscode-windhawk-ui/apps/vscode-windhawk-ui/src/app/panel/ModDetailsHeader.tsx ================================================ import { faGithubAlt } from '@fortawesome/free-brands-svg-icons'; import { faArrowLeft, faArrowRight, faHeart, faHome, faUser, } from '@fortawesome/free-solid-svg-icons'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { Alert, Button, Card, ConfigProvider, Dropdown, Modal, Rate, Tooltip } from 'antd'; import { useContext, useState } from 'react'; import { Trans, useTranslation } from 'react-i18next'; import styled from 'styled-components'; import EllipsisText from '../components/EllipsisText'; import { PopconfirmModal } from '../components/InputWithContextMenu'; import { sanitizeUrl } from '../utils'; import { ModConfig, ModMetadata, RepositoryDetails } from '../webviewIPCMessages'; import DevModeAction from './DevModeAction'; import ModMetadataLine from './ModMetadataLine'; const TextAsIconWrapper = styled.span` font-size: 18px; line-height: 18px; user-select: none; `; const ModDetailsHeaderWrapper = styled.div` display: flex; margin-bottom: 4px; > :first-child { flex-shrink: 0; margin-inline-end: 12px; // Center vertically with text: margin-top: -8px; } // https://stackoverflow.com/q/26465745 .ant-card-meta { min-width: 0; } `; const CardTitleWrapper = styled.div` padding-bottom: 4px; `; const CardTitleFirstLine = styled.div` display: flex; flex-wrap: wrap; align-items: center; column-gap: 8px; > * { text-overflow: ellipsis; overflow: hidden; } > :not(:first-child) { font-size: 14px; font-weight: normal; } `; const CardTitleModId = styled.div` border-radius: 2px; background: #444; padding: 0 4px; `; const CardTitleDescription = styled(EllipsisText)` display: block !important; color: rgba(255, 255, 255, 0.45); font-size: 14px; font-weight: normal; `; const ModRate = styled(Rate)` line-height: 0.7; `; const HeartIcon = styled(FontAwesomeIcon)` color: #ff4d4f; margin-inline-end: 4px; `; const CardTitleButtons = styled.div` display: flex; flex-wrap: wrap; gap: 8px; margin-top: 8px; // Fixes a button alignment bug. > .ant-tooltip-disabled-compatible-wrapper, > .ant-popover-disabled-compatible-wrapper { font-size: 0; } `; const ModInstallationAlert = styled(Alert)` line-height: 1.2; `; const ModInstallationModalContent = styled.div` display: flex; flex-direction: column; row-gap: 24px; `; const ModInstallationDetails = styled.div` display: grid; grid-template-columns: 20px auto; align-items: center; row-gap: 4px; `; const ModInstallationDetailsVerified = styled.span` text-decoration: underline dotted; cursor: help; `; export type ModStatus = | 'not-installed' | 'installed-not-compiled' | 'disabled' | 'enabled'; function VerifiedLabel() { const { t } = useTranslation(); return ( ]} /> } placement="bottom" > {t('installModal.verified')} ); } function ModInstallationDetailsGrid(props: { modMetadata: ModMetadata }) { const { t } = useTranslation(); const { modMetadata } = props; return ( {modMetadata.author && ( <>
{t('installModal.modAuthor')}: {modMetadata.author}
)} {modMetadata.homepage && ( <>
{t('installModal.homepage')}:{' '} {modMetadata.homepage}
)} {modMetadata.github && ( <> )} {modMetadata.twitter && ( <> 𝕏 )}
); } interface Props { topNode?: React.ReactNode; modId: string; modMetadata: ModMetadata; modConfig?: ModConfig; modStatus: ModStatus; updateAvailable: boolean; installedVersionIsLatest: boolean; isDowngrade: boolean; userRating?: number; repositoryDetails?: RepositoryDetails; callbacks: { goBack: () => void; installMod?: () => void; updateMod?: () => void; forkModFromSource?: () => void; compileMod: () => void; enableMod: (enable: boolean) => void; editMod: () => void; forkMod: () => void; deleteMod: () => void; updateModRating: (newRating: number) => void; onOpenVersionModal?: () => void; }; } function ModDetailsHeader(props: Props) { const { t } = useTranslation(); const { modId, modMetadata, modConfig, modStatus, callbacks } = props; const { direction } = useContext(ConfigProvider.ConfigContext); let displayModId = props.modId; let isLocalMod = false; if (modId.startsWith('local@')) { displayModId = modId.slice('local@'.length); isLocalMod = true; } const displayModName = modMetadata.name || displayModId; const [isInstallModalOpen, setIsInstallModalOpen] = useState(false); return ( ) : modStatus === 'enabled' ? ( ) : modStatus === 'disabled' ? ( ) : ( '' )} {modStatus !== 'not-installed' && isLocalMod && ( callbacks.editMod()} renderButton={(onClick) => ( )} /> )} {modStatus !== 'not-installed' ? ( <> callbacks.forkMod()} renderButton={(onClick) => ( )} /> callbacks.deleteMod()} > ) : ( callbacks.forkModFromSource?.()} renderButton={(onClick) => ( )} /> )} {modMetadata.donateUrl && ( )} } /> { callbacks.installMod?.(); setIsInstallModalOpen(false); }} onCancel={() => { setIsInstallModalOpen(false); }} okText={t('installModal.acceptButton')} okButtonProps={{ disabled: !callbacks.installMod, }} cancelText={t('installModal.cancelButton')} > {t('installModal.warningTitle')}} description={t('installModal.warningDescription')} type="warning" showIcon /> ); } export default ModDetailsHeader; ================================================ FILE: src/vscode-windhawk-ui/apps/vscode-windhawk-ui/src/app/panel/ModDetailsReadme.tsx ================================================ import { ConfigProvider } from 'antd'; import type { Components } from 'react-markdown'; import ReactMarkdownCustom from '../components/ReactMarkdownCustom'; interface Props { markdown: string; isLocalMod?: boolean; } function ModDetailsReadme({ markdown, isLocalMod }: Props) { // Only use custom components for non-local mods to transform image URLs. const customComponents: Components | undefined = isLocalMod ? undefined : { img: ({ node, src, alt, ...props }) => { let transformedSrc = src; // Transform certain image URLs to go through our image proxy. This // ensures that the original images are available even if they're removed // from the original hosting site. Also, Imgur is blocked in the UK, so // this makes Imgur images accessible there. if (src) { const shouldTransform = src.startsWith('https://i.imgur.com/') || (src.startsWith('https://raw.githubusercontent.com/') && !src.startsWith('https://raw.githubusercontent.com/ramensoftware/')); if (shouldTransform) { const path = src.slice('https://'.length); transformedSrc = `https://mods.windhawk.net/images/${path}`; } } return {alt}; } }; return ( ); } export default ModDetailsReadme; ================================================ FILE: src/vscode-windhawk-ui/apps/vscode-windhawk-ui/src/app/panel/ModDetailsSettings.spec.ts ================================================ /** * Tests for YamlSchemaValidator and YamlConverter * * These tests ensure: * 1. Round-trip conversion (settings -> YAML -> settings) works correctly * 2. Schema validation properly detects invalid keys and type mismatches * 3. Complex nested structures are handled correctly */ import { TextDecoder, TextEncoder } from 'util'; // Mock dependencies that are not needed for testing jest.mock('monaco-editor', () => ({})); jest.mock('@monaco-editor/react', () => ({ loader: { config: jest.fn() }, })); jest.mock('react-i18next', () => ({ useTranslation: () => ({ t: (key: string) => key }), })); jest.mock('react-router-dom', () => ({ useBlocker: () => ({ state: 'unblocked', proceed: jest.fn(), reset: jest.fn() }), })); jest.mock('../webviewIPC', () => ({ useGetModSettings: jest.fn(), useSetModSettings: jest.fn(), })); // eslint-disable-next-line @typescript-eslint/no-explicit-any const globalAny = global as any; if (!globalAny.TextEncoder) { globalAny.TextEncoder = TextEncoder; } if (!globalAny.TextDecoder) { globalAny.TextDecoder = TextDecoder; } // eslint-disable-next-line import/first import { exportedForTesting, typesForTesting, } from './ModDetailsSettings'; // eslint-disable-next-line import/first import * as yaml from 'js-yaml'; // eslint-disable-next-line import/first import i18next from 'i18next'; const { YamlSchemaValidator, YamlConverter, } = exportedForTesting; type ModSettings = typesForTesting["ModSettings"]; type InitialSettings = typesForTesting["InitialSettings"] // Mock translation function for tests const mockT = ((key: string, params?: Record): string => { if (key === 'modDetails.settings.yamlInvalid') return 'Invalid YAML structure'; if (key === 'modDetails.settings.yamlInvalidKey') return `Invalid key: ${params?.['key']}`; if (key === 'modDetails.settings.yamlTypeMismatch') { return `Type mismatch for ${params?.['key']}: expected ${params?.['expected']}, got ${params?.['actual']}`; } if (key === 'modDetails.settings.yamlParseError') return `Parse error: ${params?.['error']}`; return key; }) as typeof i18next.t; // Helper function to parse YAML text into an object for comparison const parseYaml = (yamlText: string): unknown => { if (!yamlText) return {}; return yaml.load(yamlText); }; // ============================================================================= // TESTS // ============================================================================= describe('YamlConverter and YamlSchemaValidator', () => { // ===== Simple Types Tests ===== describe('Simple primitive types', () => { const initialSettings: InitialSettings = [ { key: 'enabled', value: true }, { key: 'count', value: 42 }, { key: 'name', value: 'test' }, ]; it('should convert flat settings to YAML and back', () => { const flatSettings: ModSettings = { enabled: 1, count: 100, name: 'mytest', }; const yamlText = YamlConverter.toYaml(flatSettings, initialSettings); expect(yamlText).toBeTruthy(); const validator = new YamlSchemaValidator(initialSettings); const result = YamlConverter.fromYaml(yamlText, validator, mockT); expect(result.error).toBeNull(); expect(result.settings).toEqual(flatSettings); }); it('should handle empty values correctly', () => { const flatSettings: ModSettings = { enabled: 0, count: 0, name: '', }; const yamlText = YamlConverter.toYaml(flatSettings, initialSettings); const parsed = parseYaml(yamlText); // Empty values are kept in objects, only array ends are trimmed expect(parsed).toEqual({ enabled: 0, count: 0, name: '', }); }); it('should detect invalid keys', () => { const validator = new YamlSchemaValidator(initialSettings); const yamlText = 'invalid_key: 123'; const result = YamlConverter.fromYaml(yamlText, validator, mockT); expect(result.error).toContain('Invalid key: invalid_key'); expect(result.settings).toBeNull(); }); it('should detect type mismatches', () => { const validator = new YamlSchemaValidator(initialSettings); const yamlText = 'count: "not a number"'; const result = YamlConverter.fromYaml(yamlText, validator, mockT); expect(result.error).toContain('Type mismatch'); expect(result.settings).toBeNull(); }); it('should coerce flat settings to schema primitive types when generating YAML', () => { const flatSettings: ModSettings = { enabled: '1', count: '123', name: 'value', }; const nested = YamlConverter.flatToNested(flatSettings, initialSettings); expect(nested['enabled']).toBe(1); const countValue = nested['count']; expect(typeof countValue).toBe('number'); expect(countValue).toBe(123); expect(nested['name']).toBe('value'); }); }); // ===== Primitive Arrays Tests ===== describe('Primitive arrays', () => { const initialSettings: InitialSettings = [ { key: 'numbers', value: [1, 2, 3] }, { key: 'strings', value: ['a', 'b', 'c'] }, ]; it('should handle number arrays', () => { const flatSettings: ModSettings = { 'numbers[0]': 10, 'numbers[1]': 20, 'numbers[2]': 30, }; const yamlText = YamlConverter.toYaml(flatSettings, initialSettings); const validator = new YamlSchemaValidator(initialSettings); const result = YamlConverter.fromYaml(yamlText, validator, mockT); expect(result.error).toBeNull(); // Schema defines both numbers and strings arrays // strings gets default first entry since it's not in flatSettings expect(result.settings).toEqual({ ...flatSettings, 'strings[0]': '', // Default first entry for array in schema }); }); it('should handle string arrays', () => { const flatSettings: ModSettings = { 'strings[0]': 'hello', 'strings[1]': 'world', }; const yamlText = YamlConverter.toYaml(flatSettings, initialSettings); const validator = new YamlSchemaValidator(initialSettings); const result = YamlConverter.fromYaml(yamlText, validator, mockT); expect(result.error).toBeNull(); // Schema defines both numbers and strings arrays // numbers gets default first entry since it's not in flatSettings expect(result.settings).toEqual({ 'numbers[0]': 0, // Default first entry for array in schema ...flatSettings, }); }); it('should fill leading gaps in number arrays', () => { const flatSettings: ModSettings = { 'numbers[3]': 7, }; const yamlText = YamlConverter.toYaml(flatSettings, initialSettings); const parsed = parseYaml(yamlText); expect(parsed).toEqual({ numbers: [0, 0, 0, 7], strings: [''], }); const validator = new YamlSchemaValidator(initialSettings); const result = YamlConverter.fromYaml(yamlText, validator, mockT); expect(result.error).toBeNull(); expect(result.settings).toEqual({ 'numbers[0]': 0, 'numbers[1]': 0, 'numbers[2]': 0, 'numbers[3]': 7, 'strings[0]': '', }); }); it('should detect wrong type in number array', () => { const validator = new YamlSchemaValidator(initialSettings); const yamlText = 'numbers:\n - 1\n - "wrong"\n - 3'; const result = YamlConverter.fromYaml(yamlText, validator, mockT); expect(result.error).toContain('Type mismatch'); expect(result.settings).toBeNull(); }); }); // ===== Nested Objects Tests ===== describe('Nested objects', () => { const initialSettings: InitialSettings = [ { key: 'database', value: [ { key: 'host', value: 'localhost' }, { key: 'port', value: 5432 }, { key: 'enabled', value: true }, ], }, ]; it('should handle nested object settings', () => { const flatSettings: ModSettings = { 'database.host': 'example.com', 'database.port': 3306, 'database.enabled': 1, }; const yamlText = YamlConverter.toYaml(flatSettings, initialSettings); const validator = new YamlSchemaValidator(initialSettings); const result = YamlConverter.fromYaml(yamlText, validator, mockT); expect(result.error).toBeNull(); expect(result.settings).toEqual(flatSettings); }); it('should detect invalid nested keys', () => { const validator = new YamlSchemaValidator(initialSettings); const yamlText = 'database:\n host: test\n invalid: 123'; const result = YamlConverter.fromYaml(yamlText, validator, mockT); expect(result.error).toContain('Invalid key: database.invalid'); expect(result.settings).toBeNull(); }); it('should respect schema order when filling defaults', () => { const flatSettings: ModSettings = { 'database.port': 3306, }; const yamlText = YamlConverter.toYaml(flatSettings, initialSettings); const lines = yamlText.split('\n'); const hostIndex = lines.findIndex(l => l.includes('host:')); const portIndex = lines.findIndex(l => l.includes('port:')); const enabledIndex = lines.findIndex(l => l.includes('enabled:')); expect(hostIndex).toBeGreaterThan(-1); expect(portIndex).toBeGreaterThan(-1); expect(enabledIndex).toBeGreaterThan(-1); expect(hostIndex).toBeLessThan(portIndex); expect(portIndex).toBeLessThan(enabledIndex); const validator = new YamlSchemaValidator(initialSettings); const result = YamlConverter.fromYaml(yamlText, validator, mockT); expect(result.error).toBeNull(); expect(result.settings).toEqual({ 'database.host': '', 'database.port': 3306, 'database.enabled': 0, }); }); it('should maintain key order from schema', () => { const flatSettings: ModSettings = { 'database.enabled': 1, 'database.host': 'test', 'database.port': 8080, }; const yamlText = YamlConverter.toYaml(flatSettings, initialSettings); const parsed = parseYaml(yamlText); // Verify the structure expect(parsed).toEqual({ database: { host: 'test', port: 8080, enabled: 1, }, }); // Keys should appear in schema order: host, port, enabled const lines = yamlText.split('\n'); const hostIndex = lines.findIndex(l => l.includes('host:')); const portIndex = lines.findIndex(l => l.includes('port:')); const enabledIndex = lines.findIndex(l => l.includes('enabled:')); expect(hostIndex).toBeLessThan(portIndex); expect(portIndex).toBeLessThan(enabledIndex); }); }); // ===== Array of Objects Tests ===== describe('Array of objects', () => { const initialSettings: InitialSettings = [ { key: 'servers', value: [ [ { key: 'name', value: 'server1' }, { key: 'port', value: 8080 }, ], ], }, ]; it('should handle array of objects', () => { const flatSettings: ModSettings = { 'servers[0].name': 'web-server', 'servers[0].port': 80, 'servers[1].name': 'db-server', 'servers[1].port': 5432, }; const yamlText = YamlConverter.toYaml(flatSettings, initialSettings); const validator = new YamlSchemaValidator(initialSettings); const result = YamlConverter.fromYaml(yamlText, validator, mockT); expect(result.error).toBeNull(); expect(result.settings).toEqual(flatSettings); }); it('should detect invalid keys in array elements', () => { const validator = new YamlSchemaValidator(initialSettings); const yamlText = 'servers:\n - name: test\n port: 80\n invalid: 123'; const result = YamlConverter.fromYaml(yamlText, validator, mockT); expect(result.error).toContain('Invalid key: servers.invalid'); expect(result.settings).toBeNull(); }); }); // ===== Deeply Nested Structures Tests ===== describe('Deeply nested structures', () => { const initialSettings: InitialSettings = [ { key: 'config', value: [ { key: 'profiles', value: [ [ { key: 'name', value: 'default' }, { key: 'settings', value: [ { key: 'theme', value: 'dark' }, { key: 'fontSize', value: 14 }, ], }, ], ], }, ], }, ]; it('should handle deeply nested structures', () => { const flatSettings: ModSettings = { 'config.profiles[0].name': 'production', 'config.profiles[0].settings.theme': 'light', 'config.profiles[0].settings.fontSize': 16, 'config.profiles[1].name': 'development', 'config.profiles[1].settings.theme': 'dark', 'config.profiles[1].settings.fontSize': 12, }; const yamlText = YamlConverter.toYaml(flatSettings, initialSettings); const validator = new YamlSchemaValidator(initialSettings); const result = YamlConverter.fromYaml(yamlText, validator, mockT); expect(result.error).toBeNull(); expect(result.settings).toEqual(flatSettings); }); it('should detect type errors in deeply nested values', () => { const validator = new YamlSchemaValidator(initialSettings); const yamlText = `config: profiles: - name: test settings: theme: light fontSize: "not a number"`; const result = YamlConverter.fromYaml(yamlText, validator, mockT); expect(result.error).toContain('Type mismatch'); expect(result.settings).toBeNull(); }); }); // ===== Nested Arrays Tests ===== describe('Nested arrays (arrays of objects containing arrays)', () => { describe('Arrays containing primitive arrays', () => { const initialSettings: InitialSettings = [ { key: 'groups', value: [ [ { key: 'name', value: 'team1' }, { key: 'tags', value: ['tag1', 'tag2'] }, ], ], }, ]; it('should handle array of objects with primitive arrays', () => { const flatSettings: ModSettings = { 'groups[0].name': 'frontend', 'groups[0].tags[0]': 'react', 'groups[0].tags[1]': 'typescript', 'groups[1].name': 'backend', 'groups[1].tags[0]': 'node', 'groups[1].tags[1]': 'express', }; const yamlText = YamlConverter.toYaml(flatSettings, initialSettings); const parsed = parseYaml(yamlText); expect(parsed).toEqual({ groups: [ { name: 'frontend', tags: ['react', 'typescript'] }, { name: 'backend', tags: ['node', 'express'] }, ], }); const validator = new YamlSchemaValidator(initialSettings); const result = YamlConverter.fromYaml(yamlText, validator, mockT); expect(result.error).toBeNull(); expect(result.settings).toEqual(flatSettings); }); it('should fill missing array indices in nested primitive arrays', () => { const flatSettings: ModSettings = { 'groups[0].name': 'team', 'groups[0].tags[0]': 'first', 'groups[0].tags[2]': 'third', // Sparse array - missing index 1 }; const yamlText = YamlConverter.toYaml(flatSettings, initialSettings); const parsed = parseYaml(yamlText); expect(parsed).toEqual({ groups: [ { name: 'team', tags: ['first', '', 'third'], // Index 1 filled with default }, ], }); const validator = new YamlSchemaValidator(initialSettings); const result = YamlConverter.fromYaml(yamlText, validator, mockT); expect(result.error).toBeNull(); expect(result.settings).toEqual({ 'groups[0].name': 'team', 'groups[0].tags[0]': 'first', 'groups[0].tags[1]': '', // Filled with default 'groups[0].tags[2]': 'third', }); }); it('should add missing nested array when parent object exists', () => { const flatSettings: ModSettings = { 'groups[0].name': 'team', // groups[0].tags is missing entirely }; const yamlText = YamlConverter.toYaml(flatSettings, initialSettings); const parsed = parseYaml(yamlText); expect(parsed).toEqual({ groups: [ { name: 'team', tags: [''], // Default first entry added }, ], }); const validator = new YamlSchemaValidator(initialSettings); const result = YamlConverter.fromYaml(yamlText, validator, mockT); expect(result.error).toBeNull(); expect(result.settings).toEqual({ 'groups[0].name': 'team', 'groups[0].tags[0]': '', // Default added }); }); }); describe('Arrays containing object arrays', () => { const initialSettings: InitialSettings = [ { key: 'departments', value: [ [ { key: 'name', value: 'Engineering' }, { key: 'teams', value: [ [ { key: 'teamName', value: 'Team A' }, { key: 'size', value: 5 }, ], ], }, ], ], }, ]; it('should handle array of objects containing object arrays', () => { const flatSettings: ModSettings = { 'departments[0].name': 'Engineering', 'departments[0].teams[0].teamName': 'Frontend', 'departments[0].teams[0].size': 8, 'departments[0].teams[1].teamName': 'Backend', 'departments[0].teams[1].size': 6, 'departments[1].name': 'Sales', 'departments[1].teams[0].teamName': 'Enterprise', 'departments[1].teams[0].size': 4, }; const yamlText = YamlConverter.toYaml(flatSettings, initialSettings); const parsed = parseYaml(yamlText); expect(parsed).toEqual({ departments: [ { name: 'Engineering', teams: [ { teamName: 'Frontend', size: 8 }, { teamName: 'Backend', size: 6 }, ], }, { name: 'Sales', teams: [ { teamName: 'Enterprise', size: 4 }, ], }, ], }); const validator = new YamlSchemaValidator(initialSettings); const result = YamlConverter.fromYaml(yamlText, validator, mockT); expect(result.error).toBeNull(); expect(result.settings).toEqual(flatSettings); }); it('should fill sparse arrays in nested object arrays', () => { const flatSettings: ModSettings = { 'departments[0].name': 'Engineering', 'departments[0].teams[0].teamName': 'Frontend', 'departments[0].teams[0].size': 8, 'departments[0].teams[2].teamName': 'DevOps', // Sparse - missing index 1 'departments[0].teams[2].size': 3, }; const yamlText = YamlConverter.toYaml(flatSettings, initialSettings); const parsed = parseYaml(yamlText); expect(parsed).toEqual({ departments: [ { name: 'Engineering', teams: [ { teamName: 'Frontend', size: 8 }, { teamName: '', size: 0 }, // Index 1 filled with defaults { teamName: 'DevOps', size: 3 }, ], }, ], }); const validator = new YamlSchemaValidator(initialSettings); const result = YamlConverter.fromYaml(yamlText, validator, mockT); expect(result.error).toBeNull(); expect(result.settings).toEqual({ 'departments[0].name': 'Engineering', 'departments[0].teams[0].teamName': 'Frontend', 'departments[0].teams[0].size': 8, 'departments[0].teams[1].teamName': '', // Filled 'departments[0].teams[1].size': 0, // Filled 'departments[0].teams[2].teamName': 'DevOps', 'departments[0].teams[2].size': 3, }); }); it('should fill missing properties in nested object array elements', () => { const flatSettings: ModSettings = { 'departments[0].name': 'Engineering', 'departments[0].teams[0].teamName': 'Frontend', // departments[0].teams[0].size is missing 'departments[0].teams[1].size': 6, // departments[0].teams[1].teamName is missing }; const yamlText = YamlConverter.toYaml(flatSettings, initialSettings); const parsed = parseYaml(yamlText); expect(parsed).toEqual({ departments: [ { name: 'Engineering', teams: [ { teamName: 'Frontend', size: 0 }, // size filled with default { teamName: '', size: 6 }, // teamName filled with default ], }, ], }); const validator = new YamlSchemaValidator(initialSettings); const result = YamlConverter.fromYaml(yamlText, validator, mockT); expect(result.error).toBeNull(); expect(result.settings).toEqual({ 'departments[0].name': 'Engineering', 'departments[0].teams[0].teamName': 'Frontend', 'departments[0].teams[0].size': 0, // Filled 'departments[0].teams[1].teamName': '', // Filled 'departments[0].teams[1].size': 6, }); }); it('should add missing nested object array when parent exists', () => { const flatSettings: ModSettings = { 'departments[0].name': 'Engineering', // departments[0].teams is completely missing }; const yamlText = YamlConverter.toYaml(flatSettings, initialSettings); const parsed = parseYaml(yamlText); expect(parsed).toEqual({ departments: [ { name: 'Engineering', teams: [ { teamName: '', size: 0 }, // Default first entry ], }, ], }); const validator = new YamlSchemaValidator(initialSettings); const result = YamlConverter.fromYaml(yamlText, validator, mockT); expect(result.error).toBeNull(); expect(result.settings).toEqual({ 'departments[0].name': 'Engineering', 'departments[0].teams[0].teamName': '', // Default 'departments[0].teams[0].size': 0, // Default }); }); it('should fill leading gaps in nested object arrays', () => { const flatSettings: ModSettings = { 'departments[2].name': 'Support', 'departments[2].teams[0].teamName': 'Tier1', 'departments[2].teams[0].size': 5, }; const yamlText = YamlConverter.toYaml(flatSettings, initialSettings); const parsed = parseYaml(yamlText); expect(parsed).toEqual({ departments: [ { name: '', teams: [{ teamName: '', size: 0 }] }, { name: '', teams: [{ teamName: '', size: 0 }] }, { name: 'Support', teams: [{ teamName: 'Tier1', size: 5 }] }, ], }); const validator = new YamlSchemaValidator(initialSettings); const result = YamlConverter.fromYaml(yamlText, validator, mockT); expect(result.error).toBeNull(); expect(result.settings).toEqual({ 'departments[0].name': '', 'departments[0].teams[0].teamName': '', 'departments[0].teams[0].size': 0, 'departments[1].name': '', 'departments[1].teams[0].teamName': '', 'departments[1].teams[0].size': 0, 'departments[2].name': 'Support', 'departments[2].teams[0].teamName': 'Tier1', 'departments[2].teams[0].size': 5, }); }); }); describe('Triple-nested arrays', () => { const initialSettings: InitialSettings = [ { key: 'companies', value: [ [ { key: 'companyName', value: 'Acme Inc' }, { key: 'divisions', value: [ [ { key: 'divisionName', value: 'North' }, { key: 'projects', value: [ [ { key: 'projectName', value: 'Project X' }, { key: 'budget', value: 1000 }, ], ], }, ], ], }, ], ], }, ]; it('should handle triple-nested arrays', () => { const flatSettings: ModSettings = { 'companies[0].companyName': 'TechCorp', 'companies[0].divisions[0].divisionName': 'West', 'companies[0].divisions[0].projects[0].projectName': 'Alpha', 'companies[0].divisions[0].projects[0].budget': 5000, 'companies[0].divisions[0].projects[1].projectName': 'Beta', 'companies[0].divisions[0].projects[1].budget': 3000, }; const yamlText = YamlConverter.toYaml(flatSettings, initialSettings); const parsed = parseYaml(yamlText); expect(parsed).toEqual({ companies: [ { companyName: 'TechCorp', divisions: [ { divisionName: 'West', projects: [ { projectName: 'Alpha', budget: 5000 }, { projectName: 'Beta', budget: 3000 }, ], }, ], }, ], }); const validator = new YamlSchemaValidator(initialSettings); const result = YamlConverter.fromYaml(yamlText, validator, mockT); expect(result.error).toBeNull(); expect(result.settings).toEqual(flatSettings); }); it('should fill sparse arrays at all nesting levels', () => { const flatSettings: ModSettings = { 'companies[0].companyName': 'TechCorp', 'companies[0].divisions[0].divisionName': 'West', 'companies[0].divisions[0].projects[0].projectName': 'Alpha', 'companies[0].divisions[0].projects[0].budget': 5000, 'companies[0].divisions[0].projects[2].projectName': 'Gamma', // Sparse 'companies[0].divisions[0].projects[2].budget': 2000, 'companies[0].divisions[2].divisionName': 'South', // Sparse at division level 'companies[0].divisions[2].projects[0].projectName': 'Delta', 'companies[0].divisions[2].projects[0].budget': 1000, }; const yamlText = YamlConverter.toYaml(flatSettings, initialSettings); const parsed = parseYaml(yamlText); expect(parsed).toEqual({ companies: [ { companyName: 'TechCorp', divisions: [ { divisionName: 'West', projects: [ { projectName: 'Alpha', budget: 5000 }, { projectName: '', budget: 0 }, // Sparse index 1 filled { projectName: 'Gamma', budget: 2000 }, ], }, { divisionName: '', // Sparse division index 1 filled projects: [ { projectName: '', budget: 0 }, ], }, { divisionName: 'South', projects: [ { projectName: 'Delta', budget: 1000 }, ], }, ], }, ], }); const validator = new YamlSchemaValidator(initialSettings); const result = YamlConverter.fromYaml(yamlText, validator, mockT); expect(result.error).toBeNull(); expect(result.settings).toEqual({ 'companies[0].companyName': 'TechCorp', 'companies[0].divisions[0].divisionName': 'West', 'companies[0].divisions[0].projects[0].projectName': 'Alpha', 'companies[0].divisions[0].projects[0].budget': 5000, 'companies[0].divisions[0].projects[1].projectName': '', // Filled 'companies[0].divisions[0].projects[1].budget': 0, // Filled 'companies[0].divisions[0].projects[2].projectName': 'Gamma', 'companies[0].divisions[0].projects[2].budget': 2000, 'companies[0].divisions[1].divisionName': '', // Filled 'companies[0].divisions[1].projects[0].projectName': '', // Filled 'companies[0].divisions[1].projects[0].budget': 0, // Filled 'companies[0].divisions[2].divisionName': 'South', 'companies[0].divisions[2].projects[0].projectName': 'Delta', 'companies[0].divisions[2].projects[0].budget': 1000, }); }); }); }); // ===== Array of Arrays Tests ===== // Note: Arrays of primitive arrays (number[][] or string[][]) are not supported by InitialSettings. // Only arrays of objects (InitialSettings[]) are supported for nested arrays. // ===== Complex Mixed Types Tests ===== describe('Complex mixed types', () => { const initialSettings: InitialSettings = [ { key: 'enabled', value: true }, { key: 'tags', value: ['tag1', 'tag2'] }, { key: 'users', value: [ [ { key: 'name', value: 'user' }, { key: 'roles', value: ['admin', 'user'] }, { key: 'permissions', value: [ [ { key: 'resource', value: 'file' }, { key: 'actions', value: ['read', 'write'] }, ], ], }, ], ], }, ]; it('should handle complex mixed type structures', () => { const flatSettings: ModSettings = { enabled: 1, 'tags[0]': 'important', 'tags[1]': 'reviewed', 'users[0].name': 'alice', 'users[0].roles[0]': 'admin', 'users[0].roles[1]': 'developer', 'users[0].permissions[0].resource': 'database', 'users[0].permissions[0].actions[0]': 'read', 'users[0].permissions[0].actions[1]': 'write', 'users[0].permissions[1].resource': 'api', 'users[0].permissions[1].actions[0]': 'execute', }; const yamlText = YamlConverter.toYaml(flatSettings, initialSettings); const validator = new YamlSchemaValidator(initialSettings); const result = YamlConverter.fromYaml(yamlText, validator, mockT); expect(result.error).toBeNull(); expect(result.settings).toEqual(flatSettings); }); }); // ===== Edge Cases Tests ===== describe('Edge cases', () => { it('should handle empty settings', () => { const initialSettings: InitialSettings = []; const flatSettings: ModSettings = {}; const yamlText = YamlConverter.toYaml(flatSettings, initialSettings); expect(yamlText).toBe(''); const validator = new YamlSchemaValidator(initialSettings); const result = YamlConverter.fromYaml('', validator, mockT); expect(result.error).toBeNull(); expect(result.settings).toEqual({}); }); it('should handle settings with only empty values', () => { const initialSettings: InitialSettings = [ { key: 'a', value: 0 }, { key: 'b', value: '' }, ]; const flatSettings: ModSettings = { a: 0, b: '' }; const yamlText = YamlConverter.toYaml(flatSettings, initialSettings); const parsed = parseYaml(yamlText); // Empty values are kept in objects expect(parsed).toEqual({ a: 0, b: '', }); }); it('should reject invalid YAML', () => { const initialSettings: InitialSettings = [{ key: 'test', value: 1 }]; const validator = new YamlSchemaValidator(initialSettings); const result = YamlConverter.fromYaml('invalid: [unclosed', validator, mockT); expect(result.error).toContain('Parse error'); expect(result.settings).toBeNull(); }); it('should reject non-object YAML root', () => { const initialSettings: InitialSettings = [{ key: 'test', value: 1 }]; const validator = new YamlSchemaValidator(initialSettings); const result = YamlConverter.fromYaml('- item1\n- item2', validator, mockT); expect(result.error).toContain('Invalid YAML structure'); expect(result.settings).toBeNull(); }); it('should handle sparse arrays correctly', () => { const initialSettings: InitialSettings = [ { key: 'items', value: [1, 2, 3] }, ]; const flatSettings: ModSettings = { 'items[0]': 1, 'items[2]': 3, 'items[4]': 5, }; const yamlText = YamlConverter.toYaml(flatSettings, initialSettings); const validator = new YamlSchemaValidator(initialSettings); const result = YamlConverter.fromYaml(yamlText, validator, mockT); expect(result.error).toBeNull(); // Sparse array indices are filled with default values (0 for numbers) expect(result.settings).toEqual({ 'items[0]': 1, 'items[1]': 0, // Filled with default 'items[2]': 3, 'items[3]': 0, // Filled with default 'items[4]': 5, }); }); it('should preserve order with extra keys not in schema', () => { const initialSettings: InitialSettings = [ { key: 'a', value: 1 }, { key: 'b', value: 2 }, ]; const flatSettings: ModSettings = { a: 10, z: 999, // Extra key not in schema b: 20, }; const yamlText = YamlConverter.toYaml(flatSettings, initialSettings); const parsed = parseYaml(yamlText); // Verify the structure expect(parsed).toEqual({ a: 10, b: 20, z: 999, }); // Schema keys (a, b) should come first in order const lines = yamlText.split('\n'); const aIndex = lines.findIndex(l => l.startsWith('a:')); const bIndex = lines.findIndex(l => l.startsWith('b:')); const zIndex = lines.findIndex(l => l.startsWith('z:')); expect(aIndex).toBeLessThan(bIndex); expect(bIndex).toBeLessThan(zIndex); }); it('should use natural sort for extra keys with numbers', () => { const initialSettings: InitialSettings = [ { key: 'name', value: 'test' }, ]; // Extra keys with numbers - natural sort should order them correctly const flatSettings: ModSettings = { name: 'main', 'item10': 10, 'item2': 2, 'item1': 1, 'item20': 20, 'item3': 3, }; const yamlText = YamlConverter.toYaml(flatSettings, initialSettings); const parsed = parseYaml(yamlText); // Verify the structure expect(parsed).toEqual({ name: 'main', item1: 1, item2: 2, item3: 3, item10: 10, item20: 20, }); // Verify order in YAML: name (schema key) first, then extra keys in natural order const lines = yamlText.split('\n'); const nameIndex = lines.findIndex(l => l.startsWith('name:')); const item1Index = lines.findIndex(l => l.startsWith('item1:')); const item2Index = lines.findIndex(l => l.startsWith('item2:')); const item3Index = lines.findIndex(l => l.startsWith('item3:')); const item10Index = lines.findIndex(l => l.startsWith('item10:')); const item20Index = lines.findIndex(l => l.startsWith('item20:')); // Schema key first expect(nameIndex).toBeLessThan(item1Index); // Extra keys in natural order (not lexicographic: item1, item10, item2, item20, item3) expect(item1Index).toBeLessThan(item2Index); expect(item2Index).toBeLessThan(item3Index); expect(item3Index).toBeLessThan(item10Index); expect(item10Index).toBeLessThan(item20Index); }); it('should reject null values', () => { const initialSettings: InitialSettings = [ { key: 'name', value: 'test' }, { key: 'count', value: 42 }, ]; const validator = new YamlSchemaValidator(initialSettings); const yamlText = 'name: null\ncount: 100'; const result = YamlConverter.fromYaml(yamlText, validator, mockT); expect(result.error).not.toBeNull(); expect(result.error).toContain('Type mismatch'); expect(result.settings).toBeNull(); }); it('should reject boolean arrays', () => { const initialSettings: InitialSettings = [ { key: 'flags', value: [1, 2, 3] }, ]; const validator = new YamlSchemaValidator(initialSettings); const yamlText = 'flags:\n - true\n - false\n - true'; const result = YamlConverter.fromYaml(yamlText, validator, mockT); expect(result.error).not.toBeNull(); expect(result.error).toContain('Type mismatch'); expect(result.settings).toBeNull(); }); it('should reject nested null values', () => { const initialSettings: InitialSettings = [ { key: 'database', value: [ { key: 'host', value: 'localhost' }, { key: 'port', value: 5432 }, ], }, ]; const validator = new YamlSchemaValidator(initialSettings); const yamlText = 'database:\n host: example.com\n port: null'; const result = YamlConverter.fromYaml(yamlText, validator, mockT); expect(result.error).not.toBeNull(); expect(result.error).toContain('Type mismatch'); expect(result.settings).toBeNull(); }); }); // ===== Real-world Complex Example ===== describe('Real-world example: VS Code-like settings', () => { const initialSettings: InitialSettings = [ { key: 'workbench', value: [ { key: 'colorTheme', value: 'dark' }, ], }, { key: 'editor', value: [ { key: 'fontSize', value: 14 }, { key: 'lineHeight', value: 20 }, ], }, { key: 'files', value: [ { key: 'associations', value: [ [ { key: 'pattern', value: '*.config' }, { key: 'language', value: 'json' }, ], ], }, ], }, { key: 'search', value: [ { key: 'exclude', value: ['**/node_modules', '**/dist'], }, ], }, ]; it('should handle real-world settings round-trip', () => { const flatSettings: ModSettings = { 'workbench.colorTheme': 'light', 'editor.fontSize': 16, 'editor.lineHeight': 24, 'files.associations[0].pattern': '*.tsx', 'files.associations[0].language': 'typescriptreact', 'files.associations[1].pattern': '*.md', 'files.associations[1].language': 'markdown', 'search.exclude[0]': '**/build', 'search.exclude[1]': '**/.git', }; const yamlText = YamlConverter.toYaml(flatSettings, initialSettings); const parsed = parseYaml(yamlText); // Verify the structure expect(parsed).toEqual({ workbench: { colorTheme: 'light', }, editor: { fontSize: 16, lineHeight: 24, }, files: { associations: [ { pattern: '*.tsx', language: 'typescriptreact' }, { pattern: '*.md', language: 'markdown' }, ], }, search: { exclude: ['**/build', '**/.git'], }, }); const validator = new YamlSchemaValidator(initialSettings); const result = YamlConverter.fromYaml(yamlText, validator, mockT); expect(result.error).toBeNull(); expect(result.settings).toEqual(flatSettings); }); }); // ===== Array Ordering Tests ===== describe('Array element ordering', () => { const initialSettings: InitialSettings = [ { key: 'items', value: [ [ { key: 'x', value: 1 }, { key: 'y', value: 2 }, { key: 'z', value: 3 }, ], ], }, ]; it('should maintain consistent order for array elements', () => { const flatSettings: ModSettings = { 'items[1].z': 30, 'items[0].y': 20, 'items[1].x': 10, 'items[0].z': 3, 'items[1].y': 2, 'items[0].x': 1, }; const yamlText = YamlConverter.toYaml(flatSettings, initialSettings); const parsed = parseYaml(yamlText); // Verify the structure expect(parsed).toEqual({ items: [ { x: 1, y: 20, z: 3 }, { x: 10, y: 2, z: 30 }, ], }); const validator = new YamlSchemaValidator(initialSettings); const result = YamlConverter.fromYaml(yamlText, validator, mockT); expect(result.error).toBeNull(); expect(result.settings).toEqual(flatSettings); // Verify ordering in YAML - the keys should appear in schema order (x, y, z) // Keys may be quoted ('y':) or unquoted (x:) const xPos = Math.max(yamlText.indexOf('x:'), yamlText.indexOf("'x':")); const yPos = Math.max(yamlText.indexOf('y:'), yamlText.indexOf("'y':")); const zPos = Math.max(yamlText.indexOf('z:'), yamlText.indexOf("'z':")); // All keys should exist and be in order expect(xPos).toBeGreaterThanOrEqual(0); expect(yPos).toBeGreaterThanOrEqual(0); expect(zPos).toBeGreaterThanOrEqual(0); expect(xPos).toBeLessThan(yPos); expect(yPos).toBeLessThan(zPos); }); it('should properly sort two-digit array indexes', () => { const initialSettings: InitialSettings = [ { key: 'values', value: [1, 2, 3], }, ]; // Create flat settings with sequential indexes 0-11 to get two-digit indexes in result const flatSettings: ModSettings = { 'values[0]': 0, 'values[1]': 10, 'values[2]': 20, 'values[3]': 30, 'values[4]': 40, 'values[5]': 50, 'values[6]': 60, 'values[7]': 70, 'values[8]': 80, 'values[9]': 90, 'values[10]': 100, 'values[11]': 110, }; const yamlText = YamlConverter.toYaml(flatSettings, initialSettings); const parsed = parseYaml(yamlText); // Verify the array is properly ordered with all values including two-digit indexes expect(parsed).toEqual({ values: [0, 10, 20, 30, 40, 50, 60, 70, 80, 90, 100, 110], }); // Verify round-trip maintains all indexes including two-digit ones const validator = new YamlSchemaValidator(initialSettings); const result = YamlConverter.fromYaml(yamlText, validator, mockT); expect(result.error).toBeNull(); expect(result.settings).toEqual(flatSettings); // Verify the YAML has indexes in correct numeric order const lines = yamlText.split('\n'); const indexLines = lines .map((line, idx) => ({ line, idx })) .filter(({ line }) => line.match(/^\s*-\s+\d+$/)) .map(({ line, idx }) => ({ value: parseInt(line.trim().slice(2)), lineIdx: idx })); // Check that line indexes are in ascending order (which means array indexes are too) for (let i = 1; i < indexLines.length; i++) { expect(indexLines[i].lineIdx).toBeGreaterThan(indexLines[i - 1].lineIdx); expect(indexLines[i].value).toBeGreaterThan(indexLines[i - 1].value); } }); it('should preserve order when array starts at non-zero index', () => { const initialSettings: InitialSettings = [ { key: 'w', value: 1 }, { key: 'x', value: [ [ { key: 'a', value: 'test' }, { key: 'b', value: 42 }, ], ], }, { key: 'y', value: [10, 20] }, { key: 'z', value: 2 }, ]; // Array starts at index [1] instead of [0] const flatSettings: ModSettings = { w: 100, 'x[1].a': 'hello', 'x[1].b': 50, 'x[2].a': 'world', 'y[1]': 200, 'y[2]': 300, z: 400, }; const yamlText = YamlConverter.toYaml(flatSettings, initialSettings); const parsed = parseYaml(yamlText); // Verify structure (sparse arrays get filled with defaults) expect(parsed).toEqual({ w: 100, x: [ { a: '', b: 0 }, // Default values for missing x[0] { a: 'hello', b: 50 }, { a: 'world', b: 0 }, // Default value for missing b in x[2] ], y: [0, 200, 300], // Default value for missing y[0] z: 400, }); // Verify order in YAML: w, x, y, z (schema order preserved even with non-zero array start) const wPos = yamlText.indexOf('w:'); const xPos = Math.max(yamlText.indexOf('x:'), yamlText.indexOf("'x':")); const yPos = Math.max(yamlText.indexOf('y:'), yamlText.indexOf("'y':")); const zPos = yamlText.indexOf('z:'); expect(wPos).toBeGreaterThan(-1); expect(xPos).toBeGreaterThan(-1); expect(yPos).toBeGreaterThan(-1); expect(zPos).toBeGreaterThan(-1); // Schema order should be preserved: w, then x, then y, then z expect(wPos).toBeLessThan(xPos); expect(xPos).toBeLessThan(yPos); expect(yPos).toBeLessThan(zPos); }); }); // ===== Conflicting Key Types Tests ===== describe('Conflicting key types (array vs object)', () => { it('should handle mixed array/object notation by treating object path as invalid', () => { // Schema defines 'profiles' as an array of objects const initialSettings: InitialSettings = [ { key: 'config', value: [ { key: 'profiles', value: [ [ { key: 'name', value: 'default' }, ], ], }, ], }, ]; // Flat settings mixing array and object access const flatSettings: ModSettings = { 'config.profiles[0].name': 'production', 'config.profiles.theme': 'light', // Invalid - profiles is an array }; const yamlText = YamlConverter.toYaml(flatSettings, initialSettings); const parsed = parseYaml(yamlText); // toYaml only includes valid keys based on schema // config.profiles.theme is not in schema, so it won't be in YAML expect(parsed).toEqual({ config: { profiles: [ { name: 'production' }, ], }, }); const validator = new YamlSchemaValidator(initialSettings); const result = YamlConverter.fromYaml(yamlText, validator, mockT); // YAML only has valid structure, so validation passes expect(result.error).toBeNull(); expect(result.settings).not.toBeNull(); }); it('should handle mixed array/object notation by treating object path as invalid', () => { // Schema defines 'profiles' as an array of objects const initialSettings: InitialSettings = [ { key: 'config', value: [ { key: 'profiles', value: [ [ { key: 'name', value: 'default' }, ], ], }, ], }, ]; // Flat settings mixing array and object access const flatSettings: ModSettings = { 'config.profiles.theme': 'light', // Invalid - profiles is an array }; const yamlText = YamlConverter.toYaml(flatSettings, initialSettings); const parsed = parseYaml(yamlText); // toYaml only includes valid keys based on schema // config.profiles.theme is not in schema, so it won't be in YAML // But arrays in schema get default first entry expect(parsed).toEqual({ config: { profiles: [ { name: '' }, // Default first entry ], }, }); const validator = new YamlSchemaValidator(initialSettings); const result = YamlConverter.fromYaml(yamlText, validator, mockT); // YAML only has valid structure, so validation passes expect(result.error).toBeNull(); expect(result.settings).not.toBeNull(); }); it('should handle mixed object/array notation by treating array path as invalid', () => { // Schema defines 'settings' as an object (not an array) const initialSettings: InitialSettings = [ { key: 'config', value: [ { key: 'settings', value: [ { key: 'theme', value: 'dark' }, { key: 'fontSize', value: 14 }, ], }, ], }, ]; // Flat settings mixing object and array access const flatSettings: ModSettings = { 'config.settings.theme': 'light', 'config.settings[0].name': 'invalid', // Invalid - settings is an object, not an array }; const yamlText = YamlConverter.toYaml(flatSettings, initialSettings); const parsed = parseYaml(yamlText); // toYaml now filters out keys that don't match schema structure // config.settings[0].name is filtered because settings is an object in schema // Schema keys get default values (0 for fontSize since it's not in flatSettings) expect(parsed).toEqual({ config: { settings: { theme: 'light', fontSize: 0, // Default value for schema key not in flatSettings }, }, }); const validator = new YamlSchemaValidator(initialSettings); const result = YamlConverter.fromYaml(yamlText, validator, mockT); // YAML only has valid structure, so validation passes // Default values are included for all schema keys expect(result.error).toBeNull(); expect(result.settings).toEqual({ 'config.settings.theme': 'light', 'config.settings.fontSize': 0, // Default value added }); }); }); // ===== Numeric Keys Tests ===== describe('Numeric keys', () => { it('should handle numeric property keys at root level', () => { const initialSettings: InitialSettings = [ { key: 'name', value: 'test' }, { key: '100', value: 42 }, { key: 'enabled', value: true }, ]; const flatSettings: ModSettings = { name: 'myname', '100': 999, enabled: 1, }; const yamlText = YamlConverter.toYaml(flatSettings, initialSettings); const parsed = parseYaml(yamlText); // Verify the structure (numeric keys work at root level) expect(parsed).toEqual({ name: 'myname', 100: 999, enabled: 1, }); // Note: JavaScript automatically sorts numeric keys first, so schema order cannot be preserved // for numeric keys. The YAML will have '100' first, then 'name', then 'enabled'. // This is a known limitation due to JavaScript's object key ordering rules. // Verify round-trip works correctly (order doesn't affect functionality) const validator = new YamlSchemaValidator(initialSettings); const result = YamlConverter.fromYaml(yamlText, validator, mockT); expect(result.error).toBeNull(); expect(result.settings).toEqual(flatSettings); }); it('should handle numeric keys in nested objects', () => { const initialSettings: InitialSettings = [ { key: 'config', value: [ { key: 'name', value: 'test' }, { key: '42', value: 100 }, { key: 'count', value: 5 }, ], }, ]; const flatSettings: ModSettings = { 'config.name': 'myconfig', 'config.42': 888, 'config.count': 10, }; const yamlText = YamlConverter.toYaml(flatSettings, initialSettings); const parsed = parseYaml(yamlText); // Verify the structure - numeric property keys work in nested objects expect(parsed).toEqual({ config: { name: 'myconfig', 42: 888, count: 10, }, }); // Verify round-trip works correctly const validator = new YamlSchemaValidator(initialSettings); const result = YamlConverter.fromYaml(yamlText, validator, mockT); expect(result.error).toBeNull(); expect(result.settings).toEqual(flatSettings); }); it('should handle keys with numeric prefixes', () => { const initialSettings: InitialSettings = [ { key: 'config', value: [ { key: 'name', value: 'test' }, { key: 'port80', value: 100 }, { key: 'count', value: 5 }, ], }, ]; const flatSettings: ModSettings = { 'config.name': 'myconfig', 'config.port80': 888, 'config.count': 10, }; const yamlText = YamlConverter.toYaml(flatSettings, initialSettings); const parsed = parseYaml(yamlText); // Verify the structure - keys with numeric suffixes work fine expect(parsed).toEqual({ config: { name: 'myconfig', port80: 888, count: 10, }, }); // Verify round-trip works correctly const validator = new YamlSchemaValidator(initialSettings); const result = YamlConverter.fromYaml(yamlText, validator, mockT); expect(result.error).toBeNull(); expect(result.settings).toEqual(flatSettings); }); }); describe('Empty value handling (removeEmptyValues)', () => { describe('Primitive empty values', () => { it('should keep empty strings in objects', () => { const initialSettings: InitialSettings = [ { key: 'name', value: 'test' }, { key: 'description', value: 'desc' }, ]; const flatSettings: ModSettings = { 'name': 'John', 'description': '', // Empty string kept in objects }; const yamlText = YamlConverter.toYaml(flatSettings, initialSettings); const parsed = parseYaml(yamlText); expect(parsed).toEqual({ name: 'John', description: '', // Empty strings are kept }); }); it('should keep zero values in objects', () => { const initialSettings: InitialSettings = [ { key: 'count', value: 10 }, { key: 'size', value: 20 }, ]; const flatSettings: ModSettings = { 'count': 5, 'size': 0, // Zero kept in objects }; const yamlText = YamlConverter.toYaml(flatSettings, initialSettings); const parsed = parseYaml(yamlText); expect(parsed).toEqual({ count: 5, size: 0, // Zeros are kept }); }); it('should keep non-empty values', () => { const initialSettings: InitialSettings = [ { key: 'name', value: 'test' }, { key: 'count', value: 10 }, ]; const flatSettings: ModSettings = { 'name': 'John', 'count': 42, }; const yamlText = YamlConverter.toYaml(flatSettings, initialSettings); const parsed = parseYaml(yamlText); expect(parsed).toEqual({ name: 'John', count: 42, }); }); }); describe('Arrays with empty values', () => { it('should remove trailing empty values from primitive arrays', () => { const initialSettings: InitialSettings = [ { key: 'numbers', value: [1, 2, 3] }, ]; const flatSettings: ModSettings = { 'numbers[0]': 5, 'numbers[1]': 10, 'numbers[2]': 0, // Trailing zero should be removed 'numbers[3]': 0, // Trailing zero should be removed }; const yamlText = YamlConverter.toYaml(flatSettings, initialSettings); const parsed = parseYaml(yamlText); expect(parsed).toEqual({ numbers: [5, 10], }); }); it('should keep empty values in the middle of arrays', () => { const initialSettings: InitialSettings = [ { key: 'numbers', value: [1, 2, 3] }, ]; const flatSettings: ModSettings = { 'numbers[0]': 5, 'numbers[1]': 0, // Zero in middle should be kept 'numbers[2]': 10, }; const yamlText = YamlConverter.toYaml(flatSettings, initialSettings); const parsed = parseYaml(yamlText); expect(parsed).toEqual({ numbers: [5, 0, 10], }); }); it('should remove trailing empty strings from string arrays', () => { const initialSettings: InitialSettings = [ { key: 'tags', value: ['a', 'b'] }, ]; const flatSettings: ModSettings = { 'tags[0]': 'foo', 'tags[1]': 'bar', 'tags[2]': '', // Trailing empty string 'tags[3]': '', // Trailing empty string }; const yamlText = YamlConverter.toYaml(flatSettings, initialSettings); const parsed = parseYaml(yamlText); expect(parsed).toEqual({ tags: ['foo', 'bar'], }); }); it('should keep empty strings in the middle of arrays', () => { const initialSettings: InitialSettings = [ { key: 'tags', value: ['a', 'b'] }, ]; const flatSettings: ModSettings = { 'tags[0]': 'foo', 'tags[1]': '', // Empty in middle 'tags[2]': 'bar', }; const yamlText = YamlConverter.toYaml(flatSettings, initialSettings); const parsed = parseYaml(yamlText); expect(parsed).toEqual({ tags: ['foo', '', 'bar'], }); }); }); describe('Objects with empty values', () => { it('should keep empty properties in nested objects', () => { const initialSettings: InitialSettings = [ { key: 'person', value: [ { key: 'name', value: 'test' }, { key: 'age', value: 0 }, { key: 'email', value: '' }, ], }, ]; const flatSettings: ModSettings = { 'person.name': 'John', 'person.age': 0, // Kept in objects 'person.email': '', // Kept in objects }; const yamlText = YamlConverter.toYaml(flatSettings, initialSettings); const parsed = parseYaml(yamlText); expect(parsed).toEqual({ person: { name: 'John', age: 0, email: '', }, }); }); it('should keep nested objects even if they only contain empty values', () => { const initialSettings: InitialSettings = [ { key: 'config', value: [ { key: 'name', value: 'test' }, { key: 'settings', value: [ { key: 'enabled', value: false }, ], }, ], }, ]; const flatSettings: ModSettings = { 'config.name': 'test', 'config.settings.enabled': 0, // Empty value kept in object }; const yamlText = YamlConverter.toYaml(flatSettings, initialSettings); const parsed = parseYaml(yamlText); expect(parsed).toEqual({ config: { name: 'test', settings: { enabled: 0, // Empty values kept in objects }, }, }); }); it('should keep nested objects with at least one non-empty value', () => { const initialSettings: InitialSettings = [ { key: 'config', value: [ { key: 'settings', value: [ { key: 'enabled', value: false }, { key: 'count', value: 0 }, ], }, ], }, ]; const flatSettings: ModSettings = { 'config.settings.enabled': 1, 'config.settings.count': 0, // Kept in objects }; const yamlText = YamlConverter.toYaml(flatSettings, initialSettings); const parsed = parseYaml(yamlText); expect(parsed).toEqual({ config: { settings: { enabled: 1, count: 0, // Empty values kept }, }, }); }); }); describe('Arrays of objects with empty values', () => { it('should keep empty properties in objects in arrays', () => { const initialSettings: InitialSettings = [ { key: 'users', value: [ [ { key: 'name', value: 'test' }, { key: 'email', value: '' }, ], ], }, ]; const flatSettings: ModSettings = { 'users[0].name': 'Alice', 'users[0].email': '', // Kept in objects 'users[1].name': 'Bob', 'users[1].email': 'bob@test.com', }; const yamlText = YamlConverter.toYaml(flatSettings, initialSettings); const parsed = parseYaml(yamlText); expect(parsed).toEqual({ users: [ { name: 'Alice', email: '' }, { name: 'Bob', email: 'bob@test.com' }, ], }); }); it('should remove trailing empty objects from arrays', () => { const initialSettings: InitialSettings = [ { key: 'items', value: [ [ { key: 'name', value: 'test' }, { key: 'count', value: 0 }, ], ], }, ]; const flatSettings: ModSettings = { 'items[0].name': 'Item1', 'items[0].count': 5, 'items[1].name': '', // Empty object (all properties empty) 'items[1].count': 0, // Empty object (all properties empty) }; const yamlText = YamlConverter.toYaml(flatSettings, initialSettings); const parsed = parseYaml(yamlText); expect(parsed).toEqual({ items: [ { name: 'Item1', count: 5 }, // Empty object removed from trailing position ], }); }); it('should keep empty objects in the middle of arrays', () => { const initialSettings: InitialSettings = [ { key: 'items', value: [ [ { key: 'name', value: 'test' }, ], ], }, ]; const flatSettings: ModSettings = { 'items[0].name': 'Item1', 'items[1].name': '', // Empty object in middle 'items[2].name': 'Item3', }; const yamlText = YamlConverter.toYaml(flatSettings, initialSettings); const parsed = parseYaml(yamlText); // Empty object in middle is kept with its properties expect(parsed).toEqual({ items: [ { name: 'Item1' }, { name: '' }, // Empty value kept in object { name: 'Item3' }, ], }); }); }); describe('Deeply nested structures with empty values', () => { it('should keep deeply nested empty values in objects', () => { const initialSettings: InitialSettings = [ { key: 'root', value: [ { key: 'level1', value: [ { key: 'level2', value: [ { key: 'value1', value: 'test' }, { key: 'value2', value: '' }, ], }, ], }, ], }, ]; const flatSettings: ModSettings = { 'root.level1.level2.value1': 'data', 'root.level1.level2.value2': '', // Deep empty value }; const yamlText = YamlConverter.toYaml(flatSettings, initialSettings); const parsed = parseYaml(yamlText); expect(parsed).toEqual({ root: { level1: { level2: { value1: 'data', value2: '', // Empty values kept in objects }, }, }, }); }); it('should keep nested objects even if they contain only empty values', () => { const initialSettings: InitialSettings = [ { key: 'config', value: [ { key: 'name', value: 'test' }, { key: 'nested', value: [ { key: 'item', value: '' }, ], }, ], }, ]; const flatSettings: ModSettings = { 'config.name': 'MyConfig', 'config.nested.item': '', // Only value in nested is empty }; const yamlText = YamlConverter.toYaml(flatSettings, initialSettings); const parsed = parseYaml(yamlText); expect(parsed).toEqual({ config: { name: 'MyConfig', nested: { item: '', // Empty values kept in objects }, }, }); }); it('should handle arrays nested in objects nested in arrays', () => { const initialSettings: InitialSettings = [ { key: 'groups', value: [ [ { key: 'name', value: 'test' }, { key: 'items', value: ['a', 'b'] }, ], ], }, ]; const flatSettings: ModSettings = { 'groups[0].name': 'Group1', 'groups[0].items[0]': 'item1', 'groups[0].items[1]': '', // Trailing empty 'groups[0].items[2]': '', // Trailing empty }; const yamlText = YamlConverter.toYaml(flatSettings, initialSettings); const parsed = parseYaml(yamlText); expect(parsed).toEqual({ groups: [ { name: 'Group1', items: ['item1'], // Trailing empty values removed }, ], }); }); }); describe('Edge cases', () => { it('should keep completely empty structures in objects', () => { const initialSettings: InitialSettings = [ { key: 'name', value: '' }, { key: 'count', value: 0 }, ]; const flatSettings: ModSettings = { 'name': '', 'count': 0, }; const yamlText = YamlConverter.toYaml(flatSettings, initialSettings); const parsed = parseYaml(yamlText); // Empty values are kept in objects expect(parsed).toEqual({ name: '', count: 0, }); }); it('should remove array with all trailing empty values (keep first)', () => { const initialSettings: InitialSettings = [ { key: 'numbers', value: [1, 2, 3] }, ]; const flatSettings: ModSettings = { 'numbers[0]': 0, 'numbers[1]': 0, 'numbers[2]': 0, }; const yamlText = YamlConverter.toYaml(flatSettings, initialSettings); const parsed = parseYaml(yamlText); // Array keeps first element only (skips index 0 when trimming) expect(parsed).toEqual({ numbers: [0], }); }); it('should handle mixed empty and non-empty in complex structure', () => { const initialSettings: InitialSettings = [ { key: 'data', value: [ { key: 'active', value: true }, { key: 'items', value: ['a', 'b'] }, { key: 'config', value: [ { key: 'enabled', value: false }, { key: 'count', value: 0 }, ], }, ], }, ]; const flatSettings: ModSettings = { 'data.active': 1, 'data.items[0]': 'first', 'data.items[1]': '', // Trailing 'data.config.enabled': 0, // Empty 'data.config.count': 0, // Empty }; const yamlText = YamlConverter.toYaml(flatSettings, initialSettings); const parsed = parseYaml(yamlText); expect(parsed).toEqual({ data: { active: 1, items: ['first'], // Trailing empty removed from array config: { // Empty values kept in objects enabled: 0, count: 0, }, }, }); }); }); }); }); ================================================ FILE: src/vscode-windhawk-ui/apps/vscode-windhawk-ui/src/app/panel/ModDetailsSettings.tsx ================================================ import { faCaretDown } from '@fortawesome/free-solid-svg-icons'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import Editor, { loader } from '@monaco-editor/react'; import { Button, Card, ConfigProvider, List, message, Modal, Select, Switch } from 'antd'; import * as yaml from 'js-yaml'; import * as monaco from 'monaco-editor/esm/vs/editor/editor.api'; import { useCallback, useEffect, useMemo, useRef, useState } from 'react'; import { useTranslation } from 'react-i18next'; import { useBlocker } from 'react-router-dom'; import styled from 'styled-components'; import { useEventListener } from 'usehooks-ts'; import { DropdownModal, dropdownModalDismissed, InputNumberWithContextMenu, InputWithContextMenu, SelectModal } from '../components/InputWithContextMenu'; import { useGetModSettings, useSetModSettings } from '../webviewIPC'; import { InitialSettings, InitialSettingItem, InitialSettingsValue, InitialSettingsArrayValue, } from '../webviewIPCMessages'; import { mockModSettings } from './mockData'; // Configure Monaco Editor to use local npm package instead of CDN. loader.config({ monaco }); const SettingsWrapper = styled.div` // If an object list (with split={false}) is nested inside an array list (without split={false}), // the array list's CSS is applied to the object list's CSS, forcing the split style. // This CSS rule explicitly removes the split from object lists. .ant-list:not(.ant-list-split) > div > div > ul > li.ant-list-item { border-bottom: none; } padding-top: 12px; padding-bottom: 12px; `; const SettingInputNumber = styled(InputNumberWithContextMenu)` width: 100%; max-width: 130px; // Remove default VSCode focus highlighting color. input:focus { outline: none !important; } `; const SettingSelect = styled(SelectModal)` width: 100%; `; const SettingsCard = styled(Card)` width: 100%; `; const ArraySettingsItemWrapper = styled.div` display: flex; gap: 12px; `; const ArraySettingsDropdownOptionsButton = styled(Button)` padding-inline-start: 10px; padding-inline-end: 10px; `; const SettingsListItem = styled(List.Item)` &:first-child { padding-top: 0; } &:last-child { padding-bottom: 0; } `; const SettingsListItemMeta = styled(List.Item.Meta)` .ant-list-item-meta { margin-bottom: 8px; } .ant-list-item-meta-title { margin-bottom: 0; } .ant-list-item-meta-description { white-space: pre-line; } `; const SaveSettingsCard = styled(Card)` position: sticky; top: 0; z-index: 1; margin-inline-start: -12px; margin-inline-end: -12px; margin-top: -12px; `; const ActionButtonsWrapper = styled.div` display: flex; gap: 12px; `; const YamlEditorWrapper = styled.div` direction: ltr; margin-top: 12px; `; const YamlErrorContent = styled.div` display: inline-block; text-align: start; font-family: 'Consolas', 'Monaco', 'Courier New', monospace; white-space: pre-wrap; `; type ModSettings = Record; type NestedValue = string | number | NestedSettings | (string | number | NestedSettings)[]; interface NestedSettings { [key: string]: NestedValue; } type InitialSettingItemExtra = { options?: Record[]; }; enum SettingType { Boolean = 'boolean', Number = 'number', String = 'string', NestedObject = 'nested-object', NumberArray = 'number-array', StringArray = 'string-array', ObjectArray = 'object-array', } type BooleanDescriptor = { kind: SettingType.Boolean; value: boolean; defaultValue: number; }; type NumberDescriptor = { kind: SettingType.Number; value: number; defaultValue: number; }; type StringDescriptor = { kind: SettingType.String; value: string; defaultValue: string; }; type NestedDescriptor = { kind: SettingType.NestedObject; value: InitialSettings; children: InitialSettings; }; type NumberArrayDescriptor = { kind: SettingType.NumberArray; value: number[]; defaultValue: number; }; type StringArrayDescriptor = { kind: SettingType.StringArray; value: string[]; defaultValue: string; }; type ObjectArrayDescriptor = { kind: SettingType.ObjectArray; value: InitialSettings[]; children: InitialSettings; }; type SettingDescriptor = | BooleanDescriptor | NumberDescriptor | StringDescriptor | NestedDescriptor | NumberArrayDescriptor | StringArrayDescriptor | ObjectArrayDescriptor; function isInitialSettingItem(value: unknown): value is InitialSettingItem { if (typeof value !== 'object' || value === null) { return false; } const record = value as Record; return typeof record['key'] === 'string' && 'value' in record; } function isInitialSettingsArray(value: unknown): value is InitialSettings { return Array.isArray(value) && value.every(isInitialSettingItem); } function isInitialSettingsCollection(value: unknown[]): value is InitialSettings[] { return value.every(isInitialSettingsArray); } function isNumberArrayValue(value: unknown[]): value is number[] { return value.every(item => typeof item === 'number'); } function isStringArrayValue(value: unknown[]): value is string[] { return value.every(item => typeof item === 'string'); } function describeSetting(value: InitialSettingsValue): SettingDescriptor { if (typeof value === 'boolean') { return { kind: SettingType.Boolean, value, defaultValue: 0 }; } if (typeof value === 'number') { return { kind: SettingType.Number, value, defaultValue: 0 }; } if (typeof value === 'string') { return { kind: SettingType.String, value, defaultValue: '' }; } if (!Array.isArray(value) || value.length === 0) { throw new Error('Initial settings arrays must contain at least one template entry.'); } const arrayValue: unknown[] = value; if (isInitialSettingsCollection(arrayValue)) { const [first] = arrayValue; if (first.length === 0) { throw new Error('Invalid object array schema definition.'); } return { kind: SettingType.ObjectArray, value: arrayValue, children: first }; } if (isInitialSettingsArray(arrayValue)) { return { kind: SettingType.NestedObject, value: arrayValue, children: arrayValue }; } if (isNumberArrayValue(arrayValue)) { return { kind: SettingType.NumberArray, value: arrayValue, defaultValue: 0 }; } if (isStringArrayValue(arrayValue)) { return { kind: SettingType.StringArray, value: arrayValue, defaultValue: '' }; } throw new Error(`Unknown setting type for value: ${JSON.stringify(value)}`); } // ============================================================================ // Utility Functions // ============================================================================ function parseIntLax(value?: string | number | null) { const result = parseInt((value ?? 0).toString(), 10); return Number.isNaN(result) ? 0 : result; } /** * Formats a YAML error message for display in Ant Design message component. * Handles multiline error messages by rendering each line separately. */ function formatYamlError(error: string): React.ReactNode { const lines = error.split('\n'); return ( {lines.map((line, index) => ( {line} {index < lines.length - 1 &&
}
))}
); } // ============================================================================ // YAML Schema Validation // ============================================================================ interface TypeMismatchError { key: string; expected: string; actual: string; } /** * Helper to check if a value is a plain object (not array, not null) */ function isPlainObject(value: unknown): value is Record { return typeof value === 'object' && value !== null && !Array.isArray(value); } function toNestedSettings(value: unknown): NestedSettings { return isPlainObject(value) ? (value as NestedSettings) : {}; } /** * Natural sort comparator for strings with numbers. * Compares strings such that "item2" comes before "item10". */ function naturalSort(a: string, b: string): number { return a.localeCompare(b, undefined, { numeric: true, sensitivity: 'base' }); } class YamlSchemaValidator { private validKeys: Set; private typeSchema: Map; constructor(initialSettings: InitialSettings) { this.validKeys = this.buildValidKeys(initialSettings); this.typeSchema = this.buildTypeSchema(initialSettings); } private buildValidKeys(settings: InitialSettings, prefix = ''): Set { const keys = new Set(); for (const item of settings) { const key = prefix ? `${prefix}.${item.key}` : item.key; keys.add(key); const descriptor = describeSetting(item.value); if (descriptor.kind === SettingType.NestedObject || descriptor.kind === SettingType.ObjectArray) { const nestedKeys = this.buildValidKeys(descriptor.children, key); nestedKeys.forEach(nestedKey => keys.add(nestedKey)); } } return keys; } private buildTypeSchema(settings: InitialSettings, prefix = ''): Map { const schema = new Map(); for (const item of settings) { const key = prefix ? `${prefix}.${item.key}` : item.key; const descriptor = describeSetting(item.value); switch (descriptor.kind) { case SettingType.Boolean: case SettingType.Number: schema.set(key, 'number'); break; case SettingType.String: schema.set(key, 'string'); break; case SettingType.NestedObject: case SettingType.ObjectArray: { const nestedSchema = this.buildTypeSchema(descriptor.children, key); nestedSchema.forEach((type, nestedKey) => schema.set(nestedKey, type)); break; } case SettingType.NumberArray: schema.set(key, 'number[]'); break; case SettingType.StringArray: schema.set(key, 'string[]'); break; } } return schema; } validateKeys(nested: NestedSettings, prefix = ''): string | null { for (const [key, value] of Object.entries(nested)) { const fullKey = prefix ? `${prefix}.${key}` : key; // Check validity for this key first if (!this.validKeys.has(fullKey)) { return fullKey; } // Then recurse into nested structures if (Array.isArray(value)) { for (const item of value) { if (isPlainObject(item)) { const invalidKey = this.validateKeys(item, fullKey); if (invalidKey) { return invalidKey; } } } } else if (isPlainObject(value)) { const invalidKey = this.validateKeys(value, fullKey); if (invalidKey) { return invalidKey; } } } return null; } validateTypes(nested: NestedSettings, prefix = ''): TypeMismatchError | null { for (const [key, value] of Object.entries(nested)) { const fullKey = prefix ? `${prefix}.${key}` : key; const expectedType = this.typeSchema.get(fullKey); if (expectedType) { const error = this.validateValue(fullKey, value, expectedType); if (error) return error; } else { // Even if key is not in schema, recurse into nested structures if (Array.isArray(value)) { for (const item of value) { if (isPlainObject(item)) { const error = this.validateTypes(item, fullKey); if (error) return error; } } } else if (isPlainObject(value)) { const error = this.validateTypes(value, fullKey); if (error) return error; } } } return null; } private validateValue( fullKey: string, value: NestedValue, expectedType: string ): TypeMismatchError | null { const actualType = this.getActualType(value); // Handle array types if (expectedType.endsWith('[]')) { if (!Array.isArray(value)) { return { key: fullKey, expected: 'array', actual: actualType }; } return this.validateArrayElements(fullKey, value, expectedType); } // Handle primitive types if (expectedType !== actualType) { return { key: fullKey, expected: expectedType, actual: actualType }; } // Handle nested objects if (isPlainObject(value)) { return this.validateTypes(value, fullKey); } return null; } private getActualType(value: NestedValue): string { if (Array.isArray(value)) return 'array'; return typeof value; } private validateArrayElements( fullKey: string, array: NestedValue[], expectedType: string ): TypeMismatchError | null { const elementType = expectedType.replace('[]', ''); for (let i = 0; i < array.length; i++) { const item = array[i]; const itemKey = `${fullKey}[${i}]`; const actualType = this.getActualType(item); if (elementType === 'object') { if (!isPlainObject(item)) { return { key: itemKey, expected: 'object', actual: actualType }; } const typeError = this.validateTypes(item, fullKey); if (typeError) return typeError; } else if (elementType !== actualType) { return { key: itemKey, expected: elementType, actual: actualType }; } } return null; } } // ============================================================================ // YAML Conversion Utilities // ============================================================================ class YamlConverter { static flatToNested(flatSettings: ModSettings, initialSettings: InitialSettings): NestedSettings { const nested: NestedSettings = {}; const keysToProcess = Object.keys(flatSettings); // Filter keys to only include those that match the schema structure const validKeys = keysToProcess.filter(key => this.keyMatchesSchemaStructure(key, initialSettings)); for (const key of validKeys) { this.setNestedValue(nested, key, flatSettings[key]); } return this.normalizeWithSchema(nested, initialSettings); } /** * Check if a key path matches the schema structure. * Returns false if: * - Key uses array notation [index] where schema defines an object * - Key uses object notation .property where schema defines an array */ private static keyMatchesSchemaStructure(key: string, initialSettings: InitialSettings): boolean { const parts = this.parseKeyPath(key); let currentSettings = initialSettings; for (let i = 0; i < parts.length; i++) { const { part, index } = parts[i]; // Find the setting that matches this part const setting = currentSettings.find(s => s.key === part); if (!setting) { // Key not in schema - let validation handle it return true; } const descriptor = describeSetting(setting.value); const isArrayPart = index !== undefined; const expectsArray = descriptor.kind === SettingType.NumberArray || descriptor.kind === SettingType.StringArray || descriptor.kind === SettingType.ObjectArray; if (expectsArray !== isArrayPart) { return false; } switch (descriptor.kind) { case SettingType.ObjectArray: case SettingType.NestedObject: currentSettings = descriptor.children; break; default: return true; } } return true; } private static setNestedValue(nested: NestedSettings, key: string, value: string | number): void { const parts = this.parseKeyPath(key); let current = nested; // Navigate through all parts, creating structure as needed for (let i = 0; i < parts.length; i++) { const part = parts[i]; const isLastPart = i === parts.length - 1; if (part.index !== undefined) { // Navigate to array by property name current[part.part] ??= []; const currentArray = current[part.part] as NestedValue[]; // Set value or navigate to array element if (isLastPart) { currentArray[part.index] = value; } else { currentArray[part.index] ??= {}; current = currentArray[part.index] as NestedSettings; } } else { // Set value or navigate to property if (isLastPart) { current[part.part] = value; } else { current[part.part] ??= {}; current = current[part.part] as NestedSettings; } } } } /** * Parse a key path and track whether each part is from bracket notation. * Returns array of {part, index} objects. index is optional. * Example: "config.x" -> [{part: 'config'}, {part: 'x'}] * Example: "config.42" -> [{part: 'config'}, {part: '42'}] * Example: "config[42]" -> [{part: 'config', index: 42}] */ private static parseKeyPath(key: string): Array<{ part: string; index?: number }> { const parts: Array<{ part: string; index?: number }> = []; let remaining = key; while (remaining) { // Match property name with optional array index: word or word[123] const match = remaining.match(/^([^.[]+)(?:\[(\d+)\])?\.?(.*)/); if (!match) { break; } const part: { part: string; index?: number } = { part: match[1] }; if (match[2] !== undefined) { part.index = parseInt(match[2], 10); } parts.push(part); remaining = match[3]; } return parts; } /** * Combines provided values with schema metadata: orders keys, applies * defaults, and coerces to schema types. */ private static normalizeWithSchema(target: NestedSettings, schema: InitialSettings): NestedSettings { const ordered: NestedSettings = {}; const remainingKeys = new Set(Object.keys(target)); for (const item of schema) { const { key } = item; const descriptor = describeSetting(item.value); const existingValue = target[key]; switch (descriptor.kind) { case SettingType.Boolean: case SettingType.Number: case SettingType.String: ordered[key] = this.normalizePrimitiveValue(existingValue, descriptor); break; case SettingType.NestedObject: ordered[key] = this.normalizeNestedObject(existingValue, descriptor.children); break; case SettingType.ObjectArray: ordered[key] = this.normalizeObjectArray(existingValue, descriptor.children); break; case SettingType.NumberArray: ordered[key] = this.normalizePrimitiveArray(existingValue, descriptor.defaultValue, this.isNumberValue); break; case SettingType.StringArray: ordered[key] = this.normalizePrimitiveArray(existingValue, descriptor.defaultValue, this.isStringValue); break; } remainingKeys.delete(key); } if (remainingKeys.size > 0) { const extras = Array.from(remainingKeys).sort(naturalSort); for (const key of extras) { ordered[key] = target[key]; } } return ordered; } private static highestDefinedIndex(array: unknown[]): number { for (let i = array.length - 1; i >= 0; i--) { if (array[i] !== undefined) { return i; } } return -1; } private static normalizeNestedObject(value: NestedValue | undefined, schema: InitialSettings): NestedSettings { return this.normalizeWithSchema(toNestedSettings(value), schema); } private static normalizeObjectArray(value: NestedValue | undefined, schema: InitialSettings): NestedSettings[] { const existingArray = Array.isArray(value) ? value : []; const highestIndex = Math.max(this.highestDefinedIndex(existingArray), 0); const result: NestedSettings[] = []; for (let index = 0; index <= highestIndex; index += 1) { result[index] = this.normalizeWithSchema(toNestedSettings(existingArray[index]), schema); } return result; } private static normalizePrimitiveArray( value: NestedValue | undefined, defaultValue: T, guard: (candidate: unknown) => candidate is T ): T[] { const existingArray = Array.isArray(value) ? value : []; const highestIndex = Math.max(this.highestDefinedIndex(existingArray), 0); const result: T[] = []; for (let index = 0; index <= highestIndex; index += 1) { const candidate = existingArray[index]; result[index] = guard(candidate) ? candidate : defaultValue; } return result; } private static isNumberValue(value: unknown): value is number { return typeof value === 'number'; } private static isStringValue(value: unknown): value is string { return typeof value === 'string'; } private static normalizePrimitiveValue( value: NestedValue | undefined, descriptor: BooleanDescriptor | NumberDescriptor | StringDescriptor ): string | number { if (descriptor.kind === SettingType.Boolean) { return this.normalizeBooleanValue(value, descriptor.defaultValue); } if (descriptor.kind === SettingType.Number) { return this.normalizeNumberValue(value, descriptor.defaultValue); } return this.normalizeStringValue(value, descriptor.defaultValue); } private static normalizeBooleanValue( value: NestedValue | undefined, defaultValue: number ): number { if (value === undefined) { return defaultValue; } if (typeof value === 'number') { return value ? 1 : 0; } if (typeof value === 'string') { return parseIntLax(value) ? 1 : 0; } return defaultValue; } private static normalizeNumberValue( value: NestedValue | undefined, defaultValue: number ): number { if (value === undefined) { return defaultValue; } if (typeof value === 'number') { return value; } if (typeof value === 'string') { return parseIntLax(value); } return defaultValue; } private static normalizeStringValue( value: NestedValue | undefined, defaultValue: string ): string { if (value === undefined) { return defaultValue; } if (typeof value === 'string') { return value; } if (typeof value === 'number') { return value.toString(); } return defaultValue; } static nestedToFlat(nested: NestedValue, prefix = ''): ModSettings { const flat: ModSettings = {}; if (Array.isArray(nested)) { nested.forEach((item, index) => { const key = `${prefix}[${index}]`; Object.assign(flat, isPlainObject(item) ? this.nestedToFlat(item, key) : { [key]: item } ); }); } else { for (const [key, value] of Object.entries(nested)) { const fullKey = prefix ? `${prefix}.${key}` : key; if (Array.isArray(value)) { value.forEach((item, index) => { const arrayKey = `${fullKey}[${index}]`; Object.assign(flat, isPlainObject(item) ? this.nestedToFlat(item as NestedSettings, arrayKey) : { [arrayKey]: item } ); }); } else if (isPlainObject(value)) { Object.assign(flat, this.nestedToFlat(value as NestedSettings, fullKey)); } else { flat[fullKey] = value; } } } return flat; } static removeEmptyValues(value: NestedValue): NestedValue { if (Array.isArray(value)) { return this.cleanArray(value); } if (isPlainObject(value)) { return this.cleanObject(value); } return value; } private static cleanArray(array: (string | number | NestedSettings)[]): (string | number | NestedSettings)[] { // Compact a possibly sparse array const compacted = Object.values(array); // Find the last non-empty index, but skip the first element let lastNonEmpty = 0; for (let i = compacted.length - 1; i >= 1; i--) { const value = compacted[i]; if (!this.isEmptyValue(value)) { lastNonEmpty = i; break; } } // Trim to last non-empty element, but never remove all elements const trimmed = compacted.slice(0, lastNonEmpty + 1); // Clean nested objects const cleaned = trimmed. map(value => { if (isPlainObject(value)) { return this.cleanObject(value); } return value; }); return cleaned; } private static cleanObject(obj: NestedSettings): NestedSettings { return Object.fromEntries( Object.entries(obj) .map(([key, val]) => [key, this.removeEmptyValues(val)]) ); } private static isEmptyValue(value: NestedValue): boolean { if (Array.isArray(value)) { return value.every(v => this.isEmptyValue(v)); } if (isPlainObject(value)) { return Object.values(value).every(v => this.isEmptyValue(v)); } return value === '' || value === 0; } static toYaml(settings: ModSettings, initialSettings: InitialSettings): string { try { const nested = this.flatToNested(settings, initialSettings); const cleaned = this.removeEmptyValues(nested); const yamlText = yaml.dump(cleaned, { indent: 2, lineWidth: -1, noRefs: true, sortKeys: false, }); return yamlText.trim() === '{}' ? '' : yamlText; } catch (error) { console.error('Error converting settings to YAML:', error); return ''; } } static fromYaml( yamlString: string, validator: YamlSchemaValidator, t: ReturnType['t'], ): { settings: ModSettings | null; error: string | null } { if (!yamlString.trim()) { return { settings: {}, error: null }; } try { const parsed = yaml.load(yamlString); // Validate structure if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) { return { settings: null, error: t('modDetails.settings.yamlInvalid') }; } // Validate keys const invalidKey = validator.validateKeys(parsed as NestedSettings); if (invalidKey) { return { settings: null, error: t('modDetails.settings.yamlInvalidKey', { key: invalidKey }) }; } // Validate types const typeError = validator.validateTypes(parsed as NestedSettings); if (typeError) { return { settings: null, error: t('modDetails.settings.yamlTypeMismatch', { key: typeError.key, expected: typeError.expected, actual: typeError.actual }) }; } return { settings: this.nestedToFlat(parsed as NestedSettings), error: null }; } catch (error) { return { settings: null, error: t('modDetails.settings.yamlParseError', { error: error instanceof Error ? error.message : String(error) }) }; } } } // ============================================================================ // Component Definitions // ============================================================================ interface BooleanSettingProps { checked: boolean; onChange: (checked: boolean) => void; } function BooleanSetting({ checked, onChange }: BooleanSettingProps) { return ; } interface StringSettingProps { value: string; sampleValue: string; onChange: (newValue: string) => void; } function StringSetting({ value, sampleValue, onChange }: StringSettingProps) { const { t } = useTranslation(); return ( onChange(e.target.value)} /> ); } interface SelectSettingProps { value: string; selectItems: { value: string; label: string; }[]; onChange: (newValue: string) => void; } function SelectSetting({ value, selectItems, onChange }: SelectSettingProps) { let maxWidth = undefined; const canvas = document.createElement('canvas'); const ctx = canvas.getContext('2d'); if (ctx) { ctx.font = '14px "Segoe UI"'; if (selectItems.every((item) => ctx.measureText(item.label).width <= 350)) { maxWidth = '400px'; } } return (
onChange(newValue as string)} > {selectItems.map((item) => ( {item.label} ))}
); } interface NumberSettingProps { value?: number; onChange: (newValue: number) => void; } function NumberSetting({ value, onChange }: NumberSettingProps) { return ( onChange(parseIntLax(newValue))} /> ); } interface SettingsTreeProps { modSettings: ModSettings; onSettingChanged: (key: string, newValue: string | number) => void; arrayItemMaxIndex: Record; onRemoveArrayItem: (key: string, index: number) => void; onNewArrayItem: (key: string, index: number) => void; } interface SingleSettingProps { settingsTreeProps: SettingsTreeProps; initialSettingsValue: InitialSettingsValue; initialSettingItemExtra?: InitialSettingItemExtra; settingKey: string; } function SingleSetting({ settingsTreeProps, initialSettingsValue, initialSettingItemExtra, settingKey, }: SingleSettingProps) { const { modSettings, onSettingChanged } = settingsTreeProps; const descriptor = describeSetting(initialSettingsValue); switch (descriptor.kind) { case SettingType.Boolean: return ( onSettingChanged(settingKey, checked ? 1 : 0)} /> ); case SettingType.Number: return ( onSettingChanged(settingKey, newValue)} /> ); case SettingType.String: if (initialSettingItemExtra?.options) { return ( { const [value, label] = Object.entries(option)[0]; return { value, label }; })} onChange={(newValue) => onSettingChanged(settingKey, newValue)} /> ); } return ( onSettingChanged(settingKey, newValue)} /> ); case SettingType.NumberArray: case SettingType.StringArray: case SettingType.ObjectArray: return ( ); case SettingType.NestedObject: return ( ); } } interface ArraySettingsProps { settingsTreeProps: SettingsTreeProps; initialSettingsItems: InitialSettingsArrayValue; initialSettingItemExtra?: InitialSettingItemExtra; keyPrefix: string; } function ArraySettings({ settingsTreeProps, initialSettingsItems, initialSettingItemExtra, keyPrefix, }: ArraySettingsProps) { const { t } = useTranslation(); const { modSettings, arrayItemMaxIndex, onRemoveArrayItem, onNewArrayItem } = settingsTreeProps; const maxSettingsArrayIndex = Object.keys(modSettings).reduce( (maxIndex, key) => { if (key.startsWith(keyPrefix + '[')) { const match = key.slice((keyPrefix + '[').length).match(/^(\d+)\]/); if (match) { return Math.max(maxIndex, parseIntLax(match[1])); } } return maxIndex; }, -1 ); const maxArrayIndex = Math.max( maxSettingsArrayIndex, arrayItemMaxIndex[keyPrefix] ?? 0 ); const indexValues = [...Array(maxArrayIndex + 1).keys(), -1]; const defaultValue = initialSettingsItems[0]; return ( (
{index === -1 ? ( ) : ( { dropdownModalDismissed(); onRemoveArrayItem(keyPrefix, index) }, }, ], }} trigger={['click']} > )}
)} /> ); } interface ObjectSettingsProps { settingsTreeProps: SettingsTreeProps; initialSettings: InitialSettings; keyPrefix?: string; } function ObjectSettings({ settingsTreeProps, initialSettings, keyPrefix = '', }: ObjectSettingsProps) { return ( ( )} /> ); } interface YamlEditorProps { yamlText: string; onYamlTextChange: (value: string) => void; } function YamlEditor({ yamlText, onYamlTextChange }: YamlEditorProps) { const [editorCalcHeight, setEditorCalcHeight] = useState('0'); return ( { onYamlTextChange(value || ''); }} onMount={(editor, monacoInstance) => { // Calculate height based on position const rect = editor.getDomNode()?.getBoundingClientRect(); if (!rect) { return; } const topOffset = rect.top; const bottomOffset = 24; // Bottom padding const totalOffset = topOffset + bottomOffset; setEditorCalcHeight(`calc(100vh - ${totalOffset}px)`); // Fix clipboard operations in Electron/webview context Add copy // action (Ctrl+C) editor.addAction({ id: 'editor.action.clipboardCopyActionWithExecCommand', label: 'Copy', keybindings: [monacoInstance.KeyMod.CtrlCmd | monacoInstance.KeyCode.KeyC], contextMenuGroupId: '9_cutcopypaste', contextMenuOrder: 1, run: (ed) => { const selection = ed.getSelection(); const model = ed.getModel(); if (!selection || !model) return; if (selection.isEmpty()) { // No selection - copy the entire current line including newline const lineNumber = selection.startLineNumber; // Select the line including the newline character const lineRange = new monacoInstance.Range( lineNumber, 1, lineNumber + 1, 1 ); ed.setSelection(lineRange); document.execCommand('copy'); // Restore cursor position ed.setSelection(selection); } else { // Has selection - copy selected text document.execCommand('copy'); } } }); // Add cut action (Ctrl+X) editor.addAction({ id: 'editor.action.clipboardCutActionWithExecCommand', label: 'Cut', keybindings: [monacoInstance.KeyMod.CtrlCmd | monacoInstance.KeyCode.KeyX], contextMenuGroupId: '9_cutcopypaste', contextMenuOrder: 0, run: (ed) => { const selection = ed.getSelection(); const model = ed.getModel(); if (!selection || !model) return; if (selection.isEmpty()) { // No selection - cut the entire current line including newline const lineNumber = selection.startLineNumber; // Select the entire line including newline const lineRange = new monacoInstance.Range( lineNumber, 1, lineNumber + 1, 1 ); ed.setSelection(lineRange); document.execCommand('copy'); // Delete the entire line including newline ed.executeEdits('cut', [{ range: lineRange, text: '', forceMoveMarkers: true }]); } else { // Has selection - cut selected text document.execCommand('copy'); ed.executeEdits('cut', [{ range: selection, text: '', forceMoveMarkers: true }]); } } }); // Add paste action (Ctrl+V) editor.addAction({ id: 'editor.action.clipboardPasteActionWithExecCommand', label: 'Paste', keybindings: [monacoInstance.KeyMod.CtrlCmd | monacoInstance.KeyCode.KeyV], contextMenuGroupId: '9_cutcopypaste', contextMenuOrder: 2, run: async (ed) => { try { // Try modern clipboard API first if (navigator.clipboard && navigator.clipboard.readText) { const text = await navigator.clipboard.readText(); if (text) { const selection = ed.getSelection(); if (selection) { ed.executeEdits('paste', [{ range: selection, text: text, forceMoveMarkers: true }]); } } } else { // Fallback to execCommand document.execCommand('paste'); } } catch (err) { console.error('Paste failed:', err); } } }); // Add paste action for Shift+Insert editor.addAction({ id: 'editor.action.clipboardPasteActionWithShiftInsert', label: 'Paste', keybindings: [monacoInstance.KeyMod.Shift | monacoInstance.KeyCode.Insert], run: async (ed) => { try { if (navigator.clipboard && navigator.clipboard.readText) { const text = await navigator.clipboard.readText(); if (text) { const selection = ed.getSelection(); if (selection) { ed.executeEdits('paste', [{ range: selection, text: text, forceMoveMarkers: true }]); } } } else { document.execCommand('paste'); } } catch (err) { console.error('Paste failed:', err); } } }); // Hide the default clipboard actions that don't work in Electron. // We need to remove them from the context menu. // https://github.com/microsoft/monaco-editor/issues/1280#issuecomment-2099873176 const removableIds = [ 'editor.action.clipboardCopyAction', 'editor.action.clipboardCutAction', 'editor.action.clipboardPasteAction' ]; // eslint-disable-next-line @typescript-eslint/no-explicit-any const contextmenu = editor.getContribution('editor.contrib.contextmenu') as any; if (contextmenu && contextmenu._getMenuActions) { const realMethod = contextmenu._getMenuActions; // eslint-disable-next-line @typescript-eslint/no-explicit-any contextmenu._getMenuActions = function () { // eslint-disable-next-line prefer-rest-params const items = realMethod.apply(contextmenu, arguments); // eslint-disable-next-line @typescript-eslint/no-explicit-any return items.filter(function (item: any) { return !removableIds.includes(item.id); }); }; } }} options={{ detectIndentation: false, tabSize: 2, insertSpaces: true, minimap: { enabled: false }, }} theme="vs-dark" /> ); } interface Props { modId: string; initialSettings: InitialSettings; onCanNavigateAwayChange?: (canNavigateAway: () => Promise) => void; } function ModDetailsSettings({ modId, initialSettings, onCanNavigateAwayChange }: Props) { const { t } = useTranslation(); const [modSettingsUI, setModSettingsUI] = useState(mockModSettings); const [settingsChanged, setSettingsChanged] = useState(false); const [isYamlMode, setIsYamlMode] = useState(() => { const stored = localStorage.getItem('settingsYamlMode'); return stored === 'true'; }); const [yamlText, setYamlText] = useState(''); const [yamlWasEdited, setYamlWasEdited] = useState(false); // Track if a confirmation modal is already open const isModalOpenRef = useRef(false); // Helper function to show confirmation modal for unsaved changes const showUnsavedChangesConfirmation = useCallback((): Promise => { // Prevent multiple modals from opening if (isModalOpenRef.current) { return Promise.resolve(false); } isModalOpenRef.current = true; return new Promise((resolve) => { Modal.confirm({ title: t('modDetails.settings.unsavedChangesTitle'), content: t('modDetails.settings.unsavedChangesMessage'), okText: t('modDetails.settings.unsavedChangesLeave'), cancelText: t('modDetails.settings.unsavedChangesStay'), onOk: () => { isModalOpenRef.current = false; resolve(true); }, onCancel: () => { isModalOpenRef.current = false; resolve(false); }, closable: true, maskClosable: true, }); }); }, [t]); // Block navigation when there are unsaved changes const blocker = useBlocker(({ currentLocation, nextLocation }) => { return settingsChanged && currentLocation.pathname !== nextLocation.pathname; }); // Show confirmation modal when navigation is blocked useEffect(() => { if (blocker.state === 'blocked') { showUnsavedChangesConfirmation().then((canLeave) => { if (canLeave) { blocker.proceed(); } else { blocker.reset(); } }); } }, [blocker, showUnsavedChangesConfirmation]); // Provide a callback for parent component to check if navigation is allowed useEffect(() => { const canNavigateAway = (): Promise => { if (!settingsChanged) { return Promise.resolve(true); } return showUnsavedChangesConfirmation(); }; onCanNavigateAwayChange?.(canNavigateAway); }, [settingsChanged, showUnsavedChangesConfirmation, onCanNavigateAwayChange]); const { getModSettings } = useGetModSettings( useCallback( (data) => { if (data.modId === modId) { setModSettingsUI(data.settings); } }, [modId] ) ); const { setModSettings } = useSetModSettings( useCallback( (data) => { if (data.modId === modId && data.succeeded) { setSettingsChanged(false); } }, [modId] ) ); // Initialize YAML validator with schema const yamlValidator = useMemo( () => new YamlSchemaValidator(initialSettings), [initialSettings] ); // YAML conversion handlers const settingsToYaml = useCallback( (settings: ModSettings): string => YamlConverter.toYaml(settings, initialSettings), [initialSettings] ); const yamlToSettings = useCallback( (yamlString: string) => YamlConverter.fromYaml(yamlString, yamlValidator, t), [yamlValidator, t] ); // Sync YAML text only when switching to YAML mode or on initial load if // already in YAML mode. Don't sync when settings change to preserve user's // YAML formatting. const prevIsYamlMode = useRef(null); useEffect(() => { if (!modSettingsUI) { return; } if (isYamlMode && !prevIsYamlMode.current && modSettingsUI) { setYamlText(settingsToYaml(modSettingsUI)); } prevIsYamlMode.current = isYamlMode; }, [isYamlMode, modSettingsUI, settingsToYaml]); // Handle mode toggle const handleModeToggle = useCallback(() => { if (isYamlMode) { // Switching from YAML to UI mode if (yamlWasEdited) { // YAML was edited - validate and parse it const { settings, error } = yamlToSettings(yamlText); if (error || !settings) { message.error(formatYamlError(error || 'Unknown error')); return; } setModSettingsUI(settings); } // If YAML was never edited, keep existing modSettingsUI setArrayItemMaxIndex({}); setIsYamlMode(false); setYamlText(''); setYamlWasEdited(false); localStorage.setItem('settingsYamlMode', 'false'); } else { // Switching from UI to YAML mode setIsYamlMode(true); setYamlWasEdited(false); localStorage.setItem('settingsYamlMode', 'true'); } }, [isYamlMode, yamlWasEdited, yamlToSettings, yamlText]); const handleSave = useCallback(() => { if (!settingsChanged) { return; } let settingsToSave = modSettingsUI; // If in YAML mode, validate and parse before saving if (isYamlMode) { const { settings, error } = yamlToSettings(yamlText); if (error || !settings) { message.error(formatYamlError(error || 'Unknown error')); return; } settingsToSave = settings; } if (settingsToSave) { setModSettings({ modId, settings: settingsToSave, }); } }, [settingsChanged, modSettingsUI, isYamlMode, yamlText, yamlToSettings, modId, setModSettings]); useEffect(() => { getModSettings({ modId }); }, [getModSettings, modId]); useEventListener( 'keydown', useCallback( (e: KeyboardEvent) => { if (e.key === 's' && e.ctrlKey) { e.preventDefault(); handleSave(); } }, [handleSave] ) ); const [arrayItemMaxIndex, setArrayItemMaxIndex] = useState< Record >({}); const onRemoveArrayItem = useCallback( (key: string, index: number) => { const indexFromKey = (targetKey: string) => { if (targetKey.startsWith(key + '[')) { const match = targetKey.slice((key + '[').length).match(/^(\d+)\]/); if (match) { return parseIntLax(match[1]); } } return null; }; const decreaseKeyIndex = (targetKey: string) => { if (targetKey.startsWith(key + '[')) { const match = targetKey .slice((key + '[').length) .match(/^(\d+)(\].*$)/); if (match) { const targetKeyIndex = parseIntLax(match[1]); if (targetKeyIndex > index) { return key + '[' + (targetKeyIndex - 1).toString() + match[2]; } } } return targetKey; }; setModSettingsUI( Object.fromEntries( Object.entries(modSettingsUI ?? {}) .filter(([iterKey, iterValue]) => { return indexFromKey(iterKey) !== index; }) .map(([iterKey, iterValue]) => { return [decreaseKeyIndex(iterKey), iterValue]; }) ) ); setArrayItemMaxIndex( Object.fromEntries( Object.entries(arrayItemMaxIndex) .filter(([iterKey, iterValue]) => { return indexFromKey(iterKey) !== index; }) .map(([iterKey, iterValue]) => { return iterKey === key ? [iterKey, Math.max(iterValue - 1, 0)] : [decreaseKeyIndex(iterKey), iterValue]; }) ) ); setSettingsChanged(true); }, [modSettingsUI, arrayItemMaxIndex] ); if (modSettingsUI === null) { return null; } return (
{ e.preventDefault(); handleSave(); }} > {isYamlMode ? ( { setYamlText(value); setSettingsChanged(true); setYamlWasEdited(true); }} /> ) : ( { setModSettingsUI({ ...modSettingsUI, [key]: newValue, }); setSettingsChanged(true); }, arrayItemMaxIndex: arrayItemMaxIndex, onRemoveArrayItem, onNewArrayItem: (key, index) => { setArrayItemMaxIndex({ ...arrayItemMaxIndex, [key]: index, }); setSettingsChanged(true); }, }} initialSettings={initialSettings} /> )} ); } export default ModDetailsSettings; // Types exported for testing only export type typesForTesting = { ModSettings: ModSettings; NestedValue: NestedValue; NestedSettings: NestedSettings; InitialSettings: InitialSettings; InitialSettingItem: InitialSettingItem; InitialSettingItemExtra: InitialSettingItemExtra; InitialSettingsValue: InitialSettingsValue; InitialSettingsArrayValue: InitialSettingsArrayValue; TypeMismatchError: TypeMismatchError; } // Exported for testing only export const exportedForTesting = { // Types SettingType, // Helper functions isPlainObject, naturalSort, // Classes YamlSchemaValidator, YamlConverter, }; ================================================ FILE: src/vscode-windhawk-ui/apps/vscode-windhawk-ui/src/app/panel/ModDetailsSource.tsx ================================================ import { ConfigProvider, Switch } from 'antd'; import 'prism-themes/themes/prism-vsc-dark-plus.css'; import Prism from 'prismjs'; import 'prismjs/components/prism-c'; import 'prismjs/components/prism-cpp'; import { useMemo, useState } from 'react'; import { useTranslation } from 'react-i18next'; import styled from 'styled-components'; import { DropdownModal, dropdownModalDismissed } from '../components/InputWithContextMenu'; const SyntaxHighlighterWrapper = styled.div` direction: ltr; pre { font-size: 13px; line-height: 1.5; background-color: #1e1e1e; padding: 12px; border-radius: 2px; overflow: auto; } code { color: #d4d4d4; background-color: transparent; tab-size: 4; } `; const ConfigurationWrapper = styled.div` margin-bottom: 20px; > span { vertical-align: middle; } > button { margin-inline-start: 10px; } `; function collapseSource(source: string) { return source .replace( /^(\/\/[ \t]+==WindhawkModReadme==[ \t]*$\s*\/\*)(\s*[\s\S]+?\s*)(\*\/\s*^\/\/[ \t]+==\/WindhawkModReadme==[ \t]*)$/m, (match, p1, p2, p3) => { if ((p2 as string).includes('*/')) { return p1 + p2 + p3; } return p1 + '...' + p3; } ) .replace( /^(\/\/[ \t]+==WindhawkModSettings==[ \t]*$\s*\/\*)(\s*[\s\S]+?\s*)(\*\/\s*^\/\/[ \t]+==\/WindhawkModSettings==[ \t]*)$/m, (match, p1, p2, p3) => { if ((p2 as string).includes('*/')) { return p1 + p2 + p3; } return p1 + '...' + p3; } ); } // https://stackoverflow.com/a/30810322 function fallbackCopyTextToClipboard(text: string) { const textArea = document.createElement('textarea'); textArea.value = text; // Avoid scrolling to bottom. textArea.style.top = '0'; textArea.style.insetInlineStart = '0'; textArea.style.position = 'fixed'; document.body.appendChild(textArea); textArea.focus(); textArea.select(); try { const successful = document.execCommand('copy'); const msg = successful ? 'successful' : 'unsuccessful'; console.log('Copying text command was ' + msg); } catch (err) { console.error('Oops, unable to copy', err); } document.body.removeChild(textArea); } interface Props { source: string; } function ModDetailsSource({ source }: Props) { const { t } = useTranslation(); const [isCollapsed, setIsCollapsed] = useState(true); const collapsedSource = useMemo(() => collapseSource(source), [source]); const currentSource = isCollapsed ? collapsedSource : source; const highlightedHtml = useMemo(() => { return Prism.highlight(currentSource, Prism.languages['cpp'], 'cpp'); }, [currentSource]); return ( {t('modDetails.code.collapseExtra')} setIsCollapsed(checked)} /> { dropdownModalDismissed(); // navigator.clipboard.writeText is forbidden in VSCode webviews. const selection = window.getSelection(); if (selection && selection.type === 'Range') { document.execCommand('copy'); } else { fallbackCopyTextToClipboard(source); } }, }, ], }} trigger={['contextMenu']} >
            
          
); } export default ModDetailsSource; ================================================ FILE: src/vscode-windhawk-ui/apps/vscode-windhawk-ui/src/app/panel/ModDetailsSourceDiff.tsx ================================================ // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-nocheck: ignore TS errors due to lack of types for react-diff-view and refractor import { faArrowsAltV, faLongArrowAltDown, faLongArrowAltUp, } from '@fortawesome/free-solid-svg-icons'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { Button, ConfigProvider, Switch } from 'antd'; import Prism from 'prismjs'; import 'prismjs/components/prism-c'; import 'prismjs/components/prism-cpp'; import { useCallback, useMemo, useState } from 'react'; import { Decoration, Diff, getCollapsedLinesCountBetween, Hunk, markEdits, parseDiff, tokenize, useMinCollapsedLines, useSourceExpansion, } from 'react-diff-view'; import { useTranslation } from 'react-i18next'; import styled from 'styled-components'; import { diffLines, formatLines } from 'unidiff'; const ConfigurationWrapper = styled.div` margin-bottom: 20px; > span { vertical-align: middle; } > button { margin-inline-start: 10px; } `; const DiffWrapper = styled.div` direction: ltr; `; const UnfoldButton = styled(Button)` width: 100%; border-radius: 0; `; // https://github.com/otakustay/react-diff-view/blob/f9e5f9f248f331598e5c9e7839fccb211efe43c2/site/components/DiffView/Unfold.js const ICON_TYPE_MAPPING = { up: faLongArrowAltUp, down: faLongArrowAltDown, none: faArrowsAltV, }; const Unfold = ({ start, end, direction, onExpand, ...props }) => { const { t } = useTranslation(); const expand = useCallback( () => onExpand(start, end), [onExpand, start, end] ); const iconType = ICON_TYPE_MAPPING[direction]; const lines = end - start; return (  {t('modDetails.changes.expandLines', { count: lines })} ); }; // https://github.com/otakustay/react-diff-view/blob/f9e5f9f248f331598e5c9e7839fccb211efe43c2/site/components/DiffView/UnfoldCollapsed.js const UnfoldCollapsed = ({ previousHunk, currentHunk, linesCount, onExpand, }) => { if (!currentHunk) { const nextStart = previousHunk.oldStart + previousHunk.oldLines; const collapsedLines = linesCount - nextStart + 1; if (collapsedLines <= 0) { return null; } return ( <> {collapsedLines > 10 && ( )} ); } const collapsedLines = getCollapsedLinesCountBetween( previousHunk, currentHunk ); if (!previousHunk) { if (!collapsedLines) { return null; } const start = Math.max(currentHunk.oldStart - 10, 1); return ( <> {collapsedLines > 10 && ( )} ); } const collapsedStart = previousHunk.oldStart + previousHunk.oldLines; const collapsedEnd = currentHunk.oldStart; if (collapsedLines < 10) { return ( ); } return ( <> ); }; // HAST (Hypertext Abstract Syntax Tree) node types // HAST format: https://github.com/syntax-tree/hast interface HastText { type: 'text'; value: string; } interface HastElement { type: 'element'; tagName: string; properties: { className: string[]; }; children: HastNode[]; } type HastNode = HastText | HastElement; // Convert Prism tokens to HAST (Hypertext Abstract Syntax Tree) format const prismTokensToHast = (tokens: (string | Prism.Token)[]): HastNode[] => { const result: HastNode[] = []; for (const token of tokens) { if (typeof token === 'string') { result.push({ type: 'text', value: token }); } else if (token instanceof Prism.Token) { const className = Array.isArray(token.type) ? token.type.map((t) => `token ${t}`) : [`token`, token.type].filter(Boolean); // Handle nested tokens (token.content can be string or Token array) let children: HastNode[]; if (typeof token.content === 'string') { children = [{ type: 'text', value: token.content }]; } else if (Array.isArray(token.content)) { children = prismTokensToHast(token.content); } else { children = [{ type: 'text', value: String(token.content) }]; } result.push({ type: 'element', tagName: 'span', properties: { className }, children, }); } } return result; }; // https://codesandbox.io/s/react-diff-view-mark-edits-demo-8ndcl const diffTokenize = (hunks, oldSource) => { if (!hunks) { return undefined; } // Create a refractor-compatible adapter for Prism const prismAdapter = { highlight: (code: string, language: string) => { const grammar = Prism.languages[language]; if (!grammar) { return [{ type: 'text', value: code }]; } const tokens = Prism.tokenize(code, grammar); return prismTokensToHast(tokens); }, }; const options = { highlight: true, language: 'cpp', refractor: prismAdapter, oldSource, enhancers: [markEdits(hunks, { type: 'block' })], }; try { return tokenize(hunks, options); } catch { return undefined; } }; interface Props { oldSource: string; newSource: string; } function ModDetailsSource(props: Props) { const { t } = useTranslation(); const { oldSource, newSource } = props; const [splitView, setSplitView] = useState(true); const { type, hunks } = useMemo(() => { const diffText = formatLines(diffLines(oldSource, newSource), { context: 3, }); const [{ type, hunks }] = parseDiff(diffText, { nearbySequences: 'zip' }); return { type, hunks }; }, [newSource, oldSource]); // https://github.com/otakustay/react-diff-view/blob/b9213164497211ef45393e5a57ed5866a5f27b2e/site/components/DiffView/index.js const [hunksWithSourceExpanded, expandRange] = useSourceExpansion( hunks, oldSource ); const hunksWithMinLinesCollapsed = useMinCollapsedLines( 0, hunksWithSourceExpanded, oldSource ); const linesCount = oldSource ? oldSource.split('\n').length : 0; const tokens = diffTokenize(hunksWithMinLinesCollapsed, oldSource); const renderHunk = (children, hunk, i, hunks) => { const previousElement = children[children.length - 1]; const decorationElement = oldSource ? ( ) : ( {null} {hunk.content} ); children.push(decorationElement); const hunkElement = ; children.push(hunkElement); if (i === hunks.length - 1 && oldSource) { const unfoldTailElement = ( ); children.push(unfoldTailElement); } return children; }; return ( {t('modDetails.changes.splitView')} setSplitView(checked)} /> {(hunks) => hunks.reduce(renderHunk, [])} ); } export default ModDetailsSource; ================================================ FILE: src/vscode-windhawk-ui/apps/vscode-windhawk-ui/src/app/panel/ModMetadataLine.tsx ================================================ import { faGithubAlt } from '@fortawesome/free-brands-svg-icons'; import { faBullhorn, faCrosshairs, faHome, faUser, IconDefinition, } from '@fortawesome/free-solid-svg-icons'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { Badge, Button, ConfigProvider, Divider, Tooltip, Typography } from 'antd'; import { useCallback, useContext, useEffect, useLayoutEffect, useMemo, useRef, useState } from 'react'; import { useTranslation } from 'react-i18next'; import styled from 'styled-components'; import { sanitizeUrl } from '../utils'; import { ModMetadata, RepositoryDetails } from '../webviewIPCMessages'; type TranslationFunction = ReturnType['t']; const MetadataLineWrapper = styled.div<{ $singleLine?: boolean }>` display: flex; flex-wrap: ${({ $singleLine }) => ($singleLine ? 'nowrap' : 'wrap')}; margin-top: 4px; margin-bottom: 2px; `; const MetadataItemWrapper = styled.div<{ $width?: number; $singleLine?: boolean }>` font-size: 14px; font-weight: normal; overflow: hidden; ${({ $singleLine }) => $singleLine && ` // Don't shrink automatically; widths are managed manually. flex-shrink: 0; `} ${({ $width }) => $width !== undefined && ` width: ${$width}px; `} `; const MetadataLineIcon = styled(FontAwesomeIcon)` margin-inline-end: 3px; `; const TextAsIconWrapper = styled.span` font-size: 18px; line-height: 18px; user-select: none; `; const VersionTooltipHeader = styled.div` text-align: center; `; const VersionTooltipGrid = styled.div` display: grid; grid-template-columns: auto auto; gap: 4px 8px; margin-top: 8px; `; const VersionTooltipLabel = styled.div` text-align: end; `; const TooltipProcessList = styled.ul` margin: 4px 0; padding-inline-start: 20px; `; const TooltipSection = styled.div<{ $hasMarginTop?: boolean }>` ${({ $hasMarginTop }) => $hasMarginTop && 'margin-top: 8px;'} `; const DisabledProcessItem = styled.span` text-decoration: line-through; opacity: 0.5; `; const CustomProcessItem = styled.span` color: #388ed3; `; const TooltipNote = styled.div` margin-top: 12px; padding-top: 8px; border-top: 1px solid rgba(255, 255, 255, 0.2); `; const TooltipNoteList = styled.ul` margin: 0; padding-inline-start: 20px; color: #388ed3; `; const TooltipNoteText = styled.div` color: rgba(255, 255, 255, 0.65); font-size: 12px; `; interface MetadataItem { key: string; icon: IconDefinition; text: string; tooltip: string | React.ReactNode; showBadge?: boolean; } interface CustomProcesses { include: string[]; exclude: string[]; includeExcludeCustomOnly: boolean; patternsMatchCriticalSystemProcesses: boolean; }; function createVersionItem( version: string, t: TranslationFunction, repositoryDetails?: RepositoryDetails ): MetadataItem { let tooltip: React.ReactNode = t('modDetails.header.modVersion'); if (repositoryDetails) { const updatedDate = new Date(repositoryDetails.updated); const formatDate = (date: Date) => { return date.toLocaleDateString('en-US', { year: 'numeric', month: 'short', day: 'numeric', }); }; tooltip = ( <> {t('modDetails.header.modVersion')} {t('modDetails.header.lastUpdated')}:
{formatDate(updatedDate)}
); } return { key: 'version', icon: faBullhorn, text: version, tooltip, }; } function createAuthorTooltip( modMetadata: ModMetadata, t: TranslationFunction ): React.ReactNode { return ( <>
{t('modDetails.header.modAuthor.title')}
{(modMetadata.homepage || modMetadata.github || modMetadata.twitter) && (
{modMetadata.homepage && (
)} ); } function createAuthorItem( author: string, modMetadata: ModMetadata, t: TranslationFunction ): MetadataItem { return { key: 'author', icon: faUser, text: author, tooltip: createAuthorTooltip(modMetadata, t), }; } function createProcessesItem( modMetadata: ModMetadata, t: TranslationFunction, customProcesses?: CustomProcesses ): MetadataItem { const include = modMetadata.include || []; const exclude = modMetadata.exclude || []; let text: string; if (include.length === 0) { text = ''; } else if (include.length === 1 && exclude.length === 0) { if (include[0] === '*') { text = t('modDetails.header.processes.all'); } else { text = include[0]; } } else { if (include.length === 1 && include[0] === '*') { text = t('modDetails.header.processes.allBut', { list: exclude.join(', '), }); } else if (exclude.length > 0) { text = t('modDetails.header.processes.except', { included: include.join(', '), excluded: exclude.join(', '), }); } else { text = include.join(', '); } } const includeCustom = customProcesses?.include || []; const excludeCustom = customProcesses?.exclude || []; const isCustomOnly = customProcesses?.includeExcludeCustomOnly ?? false; const patternsMatchCriticalSystemProcesses = customProcesses?.patternsMatchCriticalSystemProcesses ?? false; const hasCustomLists = includeCustom.length > 0 || excludeCustom.length > 0 || isCustomOnly; const tooltip = ( <> {t('modDetails.header.processes.tooltip.targets')} {include.map((process, i) => { return (
  • {isCustomOnly ? ( {process} ) : ( process )}
  • ); })} {includeCustom.map((process, i) => { return (
  • {process}
  • ); })}
    {(exclude.length > 0 || excludeCustom.length > 0) && ( <> {t('modDetails.header.processes.tooltip.excluded')} {exclude.map((process, i) => { return (
  • {isCustomOnly ? ( {process} ) : ( process )}
  • ); })}
    {excludeCustom.map((process, i) => { return (
  • {process}
  • ); })}
    )} {(hasCustomLists || patternsMatchCriticalSystemProcesses) && ( {hasCustomLists && (
  • {t('modDetails.header.processes.tooltip.customListsNote')}
  • )} {patternsMatchCriticalSystemProcesses && (
  • {t('modDetails.header.processes.tooltip.patternsMatchCriticalSystemProcessesNote')}
  • )}
    )} ); return { key: 'processes', icon: faCrosshairs, text, tooltip, showBadge: hasCustomLists || patternsMatchCriticalSystemProcesses, }; } function buildMetadataItems( modMetadata: ModMetadata, t: TranslationFunction, customProcesses?: CustomProcesses, repositoryDetails?: RepositoryDetails ): MetadataItem[] { const items: MetadataItem[] = []; if (modMetadata.version) { items.push(createVersionItem(modMetadata.version, t, repositoryDetails)); } if (modMetadata.author) { items.push(createAuthorItem(modMetadata.author, modMetadata, t)); } if ((modMetadata?.include || []).length > 0 || (customProcesses?.include || []).length > 0) { items.push(createProcessesItem(modMetadata, t, customProcesses)); } return items; } // Width constraints for single-line mode const PROCESSES_MIN_WIDTH = 50; interface ItemWidths { [key: string]: number | undefined; } /** * Calculates constrained widths for metadata items based on priority: * 1. Version: capped at half of container width, never shrinks * 2. Processes: shrinks first, down to PROCESSES_MIN_WIDTH * 3. Author: shrinks last, gets remaining space */ function calculateItemWidths( containerWidth: number, naturalWidths: Record ): ItemWidths { const totalNaturalWidth = Object.values(naturalWidths).reduce( (sum, width) => sum + width, 0); // If everything fits naturally, no constraints needed if (totalNaturalWidth <= containerWidth) { return {}; } const versionNatural = naturalWidths['version'] || 0; const authorNatural = naturalWidths['author'] || 0; const processesNatural = naturalWidths['processes'] || 0; // Version: capped at max, never shrinks below natural (up to cap) const versionWidth = Math.min(versionNatural, containerWidth / 2); let remainingWidth = containerWidth - versionWidth; // Processes: shrinks first, down to minimum const processesWidth = Math.max( PROCESSES_MIN_WIDTH, Math.min(processesNatural, remainingWidth - authorNatural) ); remainingWidth -= processesWidth; // Author: gets remaining space (may shrink significantly) const authorWidth = Math.max(0, remainingWidth); return { 'version': versionWidth, 'author': authorWidth, 'processes': processesWidth, }; } interface Props { modMetadata: ModMetadata; singleLine?: boolean; customProcesses?: CustomProcesses; repositoryDetails?: RepositoryDetails; } function ModMetadataLine(props: Props) { const { t } = useTranslation(); const { modMetadata, singleLine, customProcesses, repositoryDetails } = props; const { direction } = useContext(ConfigProvider.ConfigContext); const metadataItems = useMemo( () => buildMetadataItems( modMetadata, t, customProcesses, repositoryDetails ), [modMetadata, t, customProcesses, repositoryDetails] ); const containerRef = useRef(null); const itemRefs = useRef<{ [key: string]: HTMLDivElement | null }>({}); const textRefs = useRef<{ [key: string]: HTMLElement | null }>({}); const [itemWidths, setItemWidths] = useState({}); const [textWidths, setTextWidths] = useState({}); const measureAndCalculate = useCallback(() => { if (!singleLine || !containerRef.current) { setItemWidths({}); setTextWidths({}); return; } // Skip if element is not visible const containerRect = containerRef.current.getBoundingClientRect(); if (containerRect.width === 0 || containerRect.height === 0) { return; } const containerWidth = containerRect.width; const naturalWidths: Record = {}; const naturalTextWidths: Record = {}; for (const item of metadataItems) { const el = itemRefs.current[item.key]; const textEl = textRefs.current[item.key]; if (el && textEl) { // Temporarily set width to 'auto' to measure natural width, including // the hidden overflow text, then restore. Similar to el.scrollWidth, but // fractional, which is important for accurate total width and for // avoiding ellipsis. const prevElWidth = el.style.width; const prevTextElWidth = textEl.style.width; el.style.width = 'auto'; textEl.style.width = 'auto'; naturalWidths[item.key] = el.getBoundingClientRect().width; naturalTextWidths[item.key] = textEl.getBoundingClientRect().width; el.style.width = prevElWidth; textEl.style.width = prevTextElWidth; } else { naturalWidths[item.key] = 0; naturalTextWidths[item.key] = 0; } } const calculatedWidths = calculateItemWidths( containerWidth, naturalWidths, ); // Calculate text widths based on the difference between item and text // natural widths const calculatedTextWidths: ItemWidths = {}; for (const item of metadataItems) { const itemWidth = calculatedWidths[item.key]; if (itemWidth !== undefined) { const widthDifference = naturalWidths[item.key] - naturalTextWidths[item.key]; calculatedTextWidths[item.key] = itemWidth - widthDifference; } } setItemWidths(calculatedWidths); setTextWidths(calculatedTextWidths); }, [singleLine, metadataItems]); // Use useLayoutEffect for synchronous measurement before paint useLayoutEffect(() => { if (!singleLine) { return; } // Initial measurement measureAndCalculate(); }, [singleLine, measureAndCalculate]); useEffect(() => { if (!singleLine) { return; } // Set up ResizeObserver for container size changes const resizeObserver = new ResizeObserver(() => { measureAndCalculate(); }); if (containerRef.current) { resizeObserver.observe(containerRef.current); } return () => { resizeObserver.disconnect(); }; }, [singleLine, measureAndCalculate]); if (metadataItems.length === 0) { return null; } return ( {metadataItems.map((item, i) => ( { itemRefs.current[item.key] = el; }} $width={itemWidths[item.key]} $singleLine={singleLine} > {/* Single-line: divider before item (except first) */} {singleLine && i !== 0 && } { textRefs.current[item.key] = el; }} style={{ width: textWidths[item.key] }} ellipsis={true} > {item.text} {/* Multi-line: divider after item (except last) */} {!singleLine && i < metadataItems.length - 1 && } ))} ); } export default ModMetadataLine; ================================================ FILE: src/vscode-windhawk-ui/apps/vscode-windhawk-ui/src/app/panel/ModPreview.tsx ================================================ import { Empty, message } from 'antd'; import { produce } from 'immer'; import { useCallback, useEffect, useLayoutEffect, useState } from 'react'; import { useTranslation } from 'react-i18next'; import { useParams } from 'react-router-dom'; import styled from 'styled-components'; import { useGetInstalledMods, useSetNewModConfig } from '../webviewIPC'; import { ModConfig, ModMetadata } from '../webviewIPCMessages'; import { mockModsBrowserLocalInitialMods } from './mockData'; import ModDetails from './ModDetails'; const CenteredContainer = styled.div` display: flex; flex-direction: column; height: 100%; `; const CenteredContent = styled.div` margin: auto; // Without this the centered content looks too low. padding-bottom: 10vh; `; type ModDetailsType = { metadata: ModMetadata | null; config: ModConfig | null; updateAvailable?: boolean; userRating?: number; }; interface Props { ContentWrapper: React.ComponentType< React.ComponentPropsWithoutRef<'div'> & { $hidden?: boolean } >; } function ModPreview({ ContentWrapper }: Props) { const { t } = useTranslation(); useLayoutEffect(() => { const header = document.querySelector('header'); if (header) { header.style.display = 'none'; } }, []); const { modId: displayedModId } = useParams<{ modId: string; }>(); const [installedMods, setInstalledMods] = useState | null>(mockModsBrowserLocalInitialMods); const { getInstalledMods } = useGetInstalledMods( useCallback((data) => { setInstalledMods(data.installedMods); }, []) ); useEffect(() => { getInstalledMods({}); }, [getInstalledMods]); useSetNewModConfig( useCallback( (data) => { const { modId, config: newConfig } = data; if (installedMods) { setInstalledMods( produce(installedMods, (draft) => { if (draft[modId]?.config) { draft[modId].config = { ...draft[modId].config, ...newConfig, }; } }) ); } }, [installedMods] ) ); const disabledAction = useCallback(() => { message.info(t('modPreview.actionUnavailable'), 1); }, [t]); if (!installedMods || !displayedModId) { return null; } if (!installedMods[displayedModId]) { return ( ); } return ( ); } export default ModPreview; ================================================ FILE: src/vscode-windhawk-ui/apps/vscode-windhawk-ui/src/app/panel/ModsBrowserLocal.tsx ================================================ import { faCaretDown, faFilter, faGripVertical, faHdd, faList, faSearch, faStar } from '@fortawesome/free-solid-svg-icons'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { Badge, Button, Empty, Modal, Spin, Switch, Table, Tag, Tooltip } from 'antd'; import { ItemType } from 'antd/lib/menu/hooks/useItems'; import { produce } from 'immer'; import { useCallback, useContext, useEffect, useMemo, useState } from 'react'; import { useTranslation } from 'react-i18next'; import { useBlocker, useNavigate, useParams } from 'react-router-dom'; import styled, { css } from 'styled-components'; import { AppUISettingsContext } from '../appUISettings'; import EllipsisText from '../components/EllipsisText'; import { DropdownModal, dropdownModalDismissed, InputWithContextMenu } from '../components/InputWithContextMenu'; import { editMod, forkMod, useCompileMod, useDeleteMod, useEnableMod, useGetFeaturedMods, useGetInstalledMods, useInstallMod, useSetNewModConfig, useUpdateInstalledModsDetails, useUpdateModRating, } from '../webviewIPC'; import { ModConfig, ModMetadata, RepositoryDetails, } from '../webviewIPCMessages'; import localModIcon from './assets/local-mod-icon.svg'; import { mockModsBrowserLocalFeaturedMods, mockModsBrowserLocalInitialMods, } from './mockData'; import ModCard from './ModCard'; import ModDetails from './ModDetails'; const SectionHeader = styled.div` display: flex; justify-content: space-between; align-items: start; margin-top: 20px; `; const SectionIcon = styled(FontAwesomeIcon)` margin-inline-end: 3px; `; const SearchFilterContainer = styled.div` display: flex; gap: 10px; margin-top: 12px; margin-bottom: 20px; `; const SearchFilterInput = styled(InputWithContextMenu)` > .ant-input-prefix { margin-inline-end: 8px; } `; const IconButton = styled(Button)` padding-inline-start: 0; padding-inline-end: 0; min-width: 40px; `; const ModsContainer = styled.div<{ $extraBottomPadding?: boolean }>` ${({ $extraBottomPadding }) => css` padding-bottom: ${$extraBottomPadding ? 70 : 20}px; `} `; const ModsGrid = styled.div` display: grid; grid-template-columns: repeat( auto-fill, calc(min(400px - 20px * 4 / 3, 100%)) ); gap: 20px; justify-content: center; `; const ModNameLink = styled.a` color: var(--vscode-textLink-foreground, #3794ff); &:hover { color: var(--vscode-textLink-activeForeground, #4daafc); } `; const TableActionsButton = styled(Button)` padding: 0 6px; height: 22px; `; const ModLocalIcon = styled.img` height: 20px; margin-inline-start: 8px; cursor: help; `; const ExploreModsButton = styled(Button)` height: 100%; font-size: 22px; `; const ProgressSpin = styled(Spin)` display: block; margin-inline-start: auto; margin-inline-end: auto; font-size: 32px; `; type ModDetailsType = { metadata: ModMetadata | null; config: ModConfig | null; updateAvailable: boolean; userRating: number; }; type FeaturedModDetailsType = { metadata: ModMetadata; details: RepositoryDetails; }; interface Props { ContentWrapper: React.ComponentType< React.ComponentPropsWithoutRef<'div'> & { $hidden?: boolean } >; } function ModsBrowserLocal({ ContentWrapper }: Props) { const { t } = useTranslation(); const navigate = useNavigate(); const { modType: displayedModType, modId: displayedModId } = useParams<{ modType: string; modId: string; }>(); const [installedMods, setInstalledMods] = useState | null>(mockModsBrowserLocalInitialMods); const [featuredMods, setFeaturedMods] = useState< Record | undefined | null >(mockModsBrowserLocalFeaturedMods || undefined); const [filterText, setFilterText] = useState(''); const [filterOptions, setFilterOptions] = useState>(new Set()); const [filterDropdownOpen, setFilterDropdownOpen] = useState(false); const [confirmModalOpen, setConfirmModalOpen] = useState(false); const [viewMode, setViewMode] = useState<'grid' | 'list'>(() => { try { const saved = localStorage.getItem('modsBrowserViewMode'); return saved === 'list' ? 'list' : 'grid'; } catch { return 'grid'; } }); const handleViewModeChange = useCallback((mode: 'grid' | 'list') => { setViewMode(mode); try { localStorage.setItem('modsBrowserViewMode', mode); } catch { // Ignore localStorage errors } }, []); const installedModsFilteredAndSorted = useMemo(() => { if (!installedMods) { return installedMods; } const filterWords = filterText.toLowerCase().split(/\s+/) .map(word => word.trim()) .filter(word => word.length > 0); return Object.entries(installedMods) .filter(([modId, mod]) => { // Apply text filter if (filterWords.length > 0) { const textMatch = filterWords.every((filterWord) => { return ( modId.toLowerCase().includes(filterWord) || mod.metadata?.name?.toLowerCase().includes(filterWord) || mod.metadata?.description?.toLowerCase().includes(filterWord) ); }); if (!textMatch) { return false; } } // Apply category filters - if none selected, show all if (filterOptions.size === 0) { return true; } // Use AND logic - mod must match ALL selected filters if (filterOptions.has('enabled')) { if (!mod.config || mod.config.disabled) { return false; } } if (filterOptions.has('disabled')) { if (mod.config && !mod.config.disabled) { return false; } } if (filterOptions.has('update-available')) { if (!mod.updateAvailable) { return false; } } return true; }) .sort((a, b) => { const [modIdA, modA] = a; const [modIdB, modB] = b; const modAIsLocal = modIdA.startsWith('local@'); const modBIsLocal = modIdB.startsWith('local@'); if (modAIsLocal !== modBIsLocal) { return modAIsLocal ? -1 : 1; } const modATitle = (modA.metadata?.name || modIdA).toLowerCase(); const modBTitle = (modB.metadata?.name || modIdB).toLowerCase(); if (modATitle < modBTitle) { return -1; } else if (modATitle > modBTitle) { return 1; } if (modIdA < modIdB) { return -1; } else if (modIdA > modIdB) { return 1; } return 0; }); }, [installedMods, filterText, filterOptions]); const featuredModsShuffled = useMemo(() => { if (!featuredMods) { return featuredMods; } // https://stackoverflow.com/a/6274381 /** * Shuffles array in place. ES6 version * @param {Array} a items An array containing the items. */ const shuffleArray = (a: T[]): T[] => { for (let i = a.length - 1; i > 0; i--) { const j = Math.floor(Math.random() * (i + 1)); [a[i], a[j]] = [a[j], a[i]]; } return a; }; return shuffleArray(Object.entries(featuredMods)); }, [featuredMods]); const featuredModsFilteredAndSorted = useMemo(() => { if (!featuredModsShuffled) { return featuredModsShuffled; } const maxFeaturedModsToShow = 5; // Return a random sample of non-installed mods. const notInstalled = featuredModsShuffled.filter( ([modId, mod]) => !installedMods?.[modId] ); return notInstalled.slice(0, maxFeaturedModsToShow); }, [featuredModsShuffled, installedMods]); const { devModeOptOut } = useContext(AppUISettingsContext); const { getInstalledMods } = useGetInstalledMods( useCallback((data) => { setInstalledMods(data.installedMods); }, []) ); const { getFeaturedMods } = useGetFeaturedMods( useCallback((data) => { setFeaturedMods(data.featuredMods); }, []) ); useEffect(() => { getInstalledMods({}); getFeaturedMods({}); }, [getInstalledMods, getFeaturedMods]); useUpdateInstalledModsDetails( useCallback( (data) => { if (installedMods) { const installedModsDetails = data.details; setInstalledMods( produce(installedMods, (draft) => { for (const [modId, updatedDetails] of Object.entries( installedModsDetails )) { const details = draft[modId]; if (details) { const { updateAvailable, userRating } = updatedDetails; details.updateAvailable = updateAvailable; details.userRating = userRating; } } }) ); } }, [installedMods] ) ); useSetNewModConfig( useCallback( (data) => { const { modId, config: newConfig } = data; if (installedMods) { setInstalledMods( produce(installedMods, (draft) => { if (draft[modId]?.config) { draft[modId].config = { ...draft[modId].config, ...newConfig, }; } }) ); } }, [installedMods] ) ); const { installMod, installModPending, installModContext } = useInstallMod<{ updating: boolean; }>( useCallback( (data) => { const { modId, installedModDetails } = data; if (installedModDetails && installedMods) { setInstalledMods( produce(installedMods, (draft) => { const { metadata, config } = installedModDetails; draft[modId] = draft[modId] || {}; draft[modId].metadata = metadata; draft[modId].config = config; draft[modId].updateAvailable = false; }) ); } }, [installedMods] ) ); const { compileMod, compileModPending } = useCompileMod( useCallback( (data) => { const { modId, compiledModDetails } = data; if (compiledModDetails && installedMods) { setInstalledMods( produce(installedMods, (draft) => { const { metadata, config } = compiledModDetails; draft[modId] = draft[modId] || {}; draft[modId].metadata = metadata; draft[modId].config = config; draft[modId].updateAvailable = false; }) ); } }, [installedMods] ) ); const { enableMod } = useEnableMod( useCallback( (data) => { if (data.succeeded && installedMods) { const modId = data.modId; setInstalledMods( produce(installedMods, (draft) => { const config = draft[modId].config; if (config) { config.disabled = !data.enabled; } }) ); } }, [installedMods] ) ); const { deleteMod } = useDeleteMod( useCallback( (data) => { if (data.succeeded && installedMods) { const modId = data.modId; if (displayedModType === 'local' && displayedModId === modId) { navigate('/', { replace: true }); } setInstalledMods( produce(installedMods, (draft) => { delete draft[modId]; }) ); } }, [displayedModId, displayedModType, installedMods, navigate] ) ); const { updateModRating } = useUpdateModRating( useCallback( (data) => { if (data.succeeded && installedMods) { const modId = data.modId; setInstalledMods( produce(installedMods, (draft) => { draft[modId].userRating = data.rating; }) ); } }, [installedMods] ) ); const [detailsButtonClicked, setDetailsButtonClicked] = useState(false); const handleFilterChange = (key: string) => { setFilterOptions((prevOptions) => { const newOptions = new Set(prevOptions); // Handle mutually exclusive filters if (key === 'enabled' && newOptions.has('disabled')) { newOptions.delete('disabled'); } else if (key === 'disabled' && newOptions.has('enabled')) { newOptions.delete('enabled'); } // Toggle the clicked option if (newOptions.has(key)) { newOptions.delete(key); } else { newOptions.add(key); } return newOptions; }); }; const handleClearFilters = () => { setFilterOptions(new Set()); }; // Block all navigation when modal is open const modalIsOpen = installModPending || compileModPending || confirmModalOpen; useBlocker(({ currentLocation, nextLocation }) => { return modalIsOpen && currentLocation.pathname !== nextLocation.pathname; }); if (!installedMods || !installedModsFilteredAndSorted) { return null; } const noInstalledMods = Object.keys(installedMods).length === 0; const noFilteredResults = installedModsFilteredAndSorted.length === 0 && !noInstalledMods; return ( <>

    {t('home.installedMods.title')}

    {!noInstalledMods && ( } placeholder={t('modSearch.placeholder') as string} allowClear value={filterText} onChange={(e) => setFilterText(e.target.value)} /> { if (e.key === 'clear-filters') { dropdownModalDismissed(); handleClearFilters(); setFilterDropdownOpen(false); } else { handleFilterChange(e.key); // Keep dropdown open for filter changes } }, }} > 0 ? 'primary' : undefined} > handleViewModeChange(viewMode === 'grid' ? 'list' : 'grid')} > )} {noInstalledMods ? ( ) : noFilteredResults ? ( ) : viewMode === 'grid' ? ( {installedModsFilteredAndSorted.map(([modId, mod]) => ( { setDetailsButtonClicked(true); navigate('/mods/local/' + modId); }, badge: (mod.config?.loggingEnabled || mod.config?.debugLoggingEnabled) ? { tooltip: t('mod.loggingEnabledInAdvancedTab') as string, } : undefined, }, { text: t('mod.remove'), confirmText: t('mod.removeConfirm') as string, confirmOkText: t('mod.removeConfirmOk') as string, confirmCancelText: t('mod.removeConfirmCancel') as string, confirmIsDanger: true, onClick: () => deleteMod({ modId }), }, ]} switch={{ title: mod.config ? undefined : (t('mod.notCompiled') as string), checked: mod.config ? !mod.config.disabled : false, disabled: !mod.config, onChange: (checked) => enableMod({ modId, enable: checked }), }} /> ))} ) : ( ({ key: modId, modId, name: mod.metadata?.name || modId.replace(/^local@/, ''), description: mod.metadata?.description, author: mod.metadata?.author, version: mod.metadata?.version, isLocal: modId.startsWith('local@'), updateAvailable: mod.updateAvailable, disabled: mod.config ? mod.config.disabled : true, notCompiled: !mod.config, mod, }))} columns={[ { title: '', key: 'actions', width: 50, align: 'center', render: (_, record) => { const isLocal = record.isLocal; const menuItems: ItemType[] = []; // Compile action (if not compiled) if (record.notCompiled) { menuItems.push({ label: t('mod.compile'), key: 'compile', onClick: () => { dropdownModalDismissed(); compileMod({ modId: record.modId }); }, }); } // Enable/Disable action (if compiled) if (!record.notCompiled) { menuItems.push({ label: record.disabled ? t('mod.enable') : t('mod.disable'), key: 'toggle-enable', onClick: () => { dropdownModalDismissed(); enableMod({ modId: record.modId, enable: record.disabled }); }, }); } // Divider before dev actions if (menuItems.length > 0) { menuItems.push({ type: 'divider' }); } // Edit action (local mods only) if (isLocal) { menuItems.push({ label: t('mod.edit'), key: 'edit', onClick: () => { dropdownModalDismissed(); editMod({ modId: record.modId }); }, }); } // Fork action menuItems.push({ label: t('mod.fork'), key: 'fork', onClick: () => { dropdownModalDismissed(); forkMod({ modId: record.modId }); }, }); // Divider before remove menuItems.push({ type: 'divider' }); // Remove action menuItems.push({ label: t('mod.remove'), key: 'remove', danger: true, onClick: () => { dropdownModalDismissed(); setConfirmModalOpen(true); Modal.confirm({ title: t('mod.removeConfirm'), okText: t('mod.removeConfirmOk'), cancelText: t('mod.removeConfirmCancel'), okButtonProps: { danger: true }, onOk: () => { setConfirmModalOpen(false); deleteMod({ modId: record.modId }); }, onCancel: () => { setConfirmModalOpen(false); }, closable: true, maskClosable: true, }); }, }); const hasLogging = record.mod.config?.loggingEnabled || record.mod.config?.debugLoggingEnabled; const actionsButton = ( ); if (hasLogging) { return ( {actionsButton} ); } return actionsButton; }, }, { title: t('home.installedMods.grid.name'), dataIndex: 'name', key: 'name', width: '30%', sorter: (a, b) => a.name.localeCompare(b.name), render: (name, record) => ( <> { setDetailsButtonClicked(true); navigate('/mods/local/' + record.modId); }} > {name} {record.updateAvailable && ( {t('mod.updateAvailable')} )} {record.isLocal && ( )} ), }, { title: t('home.installedMods.grid.description'), dataIndex: 'description', key: 'description', render: (description) => ( {description || '-'} ), ellipsis: { showTitle: false }, }, { title: t('home.installedMods.grid.author'), dataIndex: 'author', key: 'author', width: '12%', sorter: (a, b) => (a.author || '').localeCompare(b.author || ''), render: (author) => author || '-', }, { title: t('home.installedMods.grid.version'), dataIndex: 'version', key: 'version', width: '8%', sorter: (a, b) => { const versionA = a.version || ''; const versionB = b.version || ''; return versionA.localeCompare(versionB, undefined, { numeric: true, sensitivity: 'base' }); }, render: (version) => version || '-', }, { title: t('home.installedMods.grid.status'), key: 'status', width: 80, align: 'center', sorter: (a, b) => Number(a.disabled) - Number(b.disabled), render: (_, record) => ( enableMod({ modId: record.modId, enable: checked }) } title={ record.notCompiled ? (t('mod.notCompiled') as string) : undefined } /> ), }, ]} pagination={false} size="middle" showSorterTooltip={false} style={{ wordBreak: 'break-word' }} /> )}

    {t('home.featuredMods.title')}

    {featuredModsFilteredAndSorted === undefined ? ( ) : featuredModsFilteredAndSorted === null ? ( ) : featuredModsFilteredAndSorted.length === 0 ? ( ) : ( {featuredModsFilteredAndSorted.map(([modId, mod]) => ( { setDetailsButtonClicked(true); navigate('/mods/featured/' + modId); }, }, ]} /> ))} navigate('/mods-browser')} > {t('home.featuredMods.explore')} )} {displayedModId && ( {(displayedModType === 'local' && installedMods[displayedModId]) ? ( { // If we ever clicked on Details, go back. // Otherwise, we probably arrived from a different location, // go straight to the mods page. if (detailsButtonClicked) { navigate(-1); } else { navigate('/'); } }} updateMod={(modSource, disabled) => installMod( { modId: displayedModId, modSource, disabled }, { updating: true } ) } forkModFromSource={(modSource) => forkMod({ modId: displayedModId, modSource }) } compileMod={() => compileMod({ modId: displayedModId })} enableMod={(enable) => enableMod({ modId: displayedModId, enable }) } editMod={() => editMod({ modId: displayedModId })} forkMod={() => forkMod({ modId: displayedModId })} deleteMod={() => deleteMod({ modId: displayedModId })} updateModRating={(newRating) => updateModRating({ modId: displayedModId, rating: newRating }) } /> ) : ( { // If we ever clicked on Details, go back. // Otherwise, we probably arrived from a different location, // go straight to the mods page. if (detailsButtonClicked) { navigate(-1); } else { navigate('/'); } }} installMod={(modSource) => installMod({ modId: displayedModId, modSource: modSource }) } updateMod={(modSource, disabled) => installMod( { modId: displayedModId, modSource, disabled }, { updating: true } ) } forkModFromSource={(modSource) => forkMod({ modId: displayedModId, modSource }) } compileMod={() => compileMod({ modId: displayedModId })} enableMod={(enable) => enableMod({ modId: displayedModId, enable }) } editMod={() => editMod({ modId: displayedModId })} forkMod={() => forkMod({ modId: displayedModId })} deleteMod={() => deleteMod({ modId: displayedModId })} updateModRating={(newRating) => updateModRating({ modId: displayedModId, rating: newRating }) } /> )} )} {(installModPending || compileModPending) && ( )} ); } export default ModsBrowserLocal; ================================================ FILE: src/vscode-windhawk-ui/apps/vscode-windhawk-ui/src/app/panel/ModsBrowserOnline.tsx ================================================ import { faFilter, faSearch, faSort } from '@fortawesome/free-solid-svg-icons'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { Badge, Button, Empty, Modal, Result, Spin } from 'antd'; import { produce } from 'immer'; import { useCallback, useContext, useEffect, useMemo, useState } from 'react'; import { useTranslation } from 'react-i18next'; import InfiniteScroll from 'react-infinite-scroll-component'; import { useBlocker, useNavigate, useParams } from 'react-router-dom'; import styled, { css } from 'styled-components'; import { AppUISettingsContext } from '../appUISettings'; import { DropdownModal, dropdownModalDismissed, InputWithContextMenu } from '../components/InputWithContextMenu'; import { editMod, forkMod, useCompileMod, useDeleteMod, useEnableMod, useGetRepositoryMods, useInstallMod, useUpdateInstalledModsDetails, useUpdateModRating, } from '../webviewIPC'; import { ModConfig, ModMetadata, RepositoryDetails, } from '../webviewIPCMessages'; import { mockModsBrowserOnlineRepositoryMods, useMockData } from './mockData'; import ModCard from './ModCard'; import ModDetails from './ModDetails'; const CenteredContainer = styled.div` display: flex; flex-direction: column; height: 100%; `; const CenteredContent = styled.div` margin: auto; // Without this the centered content looks too low. padding-bottom: 10vh; `; const SearchFilterContainer = styled.div` display: flex; gap: 10px; margin: 20px 0; `; const SearchFilterInput = styled(InputWithContextMenu)` > .ant-input-prefix { margin-inline-end: 8px; } `; const IconButton = styled(Button)` padding-inline-start: 0; padding-inline-end: 0; min-width: 40px; `; const ModsContainer = styled.div<{ $extraBottomPadding?: boolean }>` ${({ $extraBottomPadding }) => css` padding-bottom: ${$extraBottomPadding ? 70 : 20}px; `} `; const ResultsMessageWrapper = styled.div` margin-top: 85px; `; const ModsGrid = styled.div` display: grid; grid-template-columns: repeat( auto-fill, calc(min(400px - 20px * 4 / 3, 100%)) ); gap: 20px; justify-content: center; `; const ProgressSpin = styled(Spin)` display: block; margin-inline-start: auto; margin-inline-end: auto; font-size: 32px; `; const FilterItemLabelWrapper = styled.span` display: flex; justify-content: space-between; align-items: center; gap: 12px; `; interface FilterItemLabelProps { label: string; count?: number; } const FilterItemLabel = ({ label, count }: FilterItemLabelProps) => ( {label} {count !== undefined && ( )} ); type ModDetailsType = { repository: { metadata: ModMetadata; details: RepositoryDetails; }; installed?: { metadata: ModMetadata | null; config: ModConfig | null; userRating?: number; }; }; const normalizeProcessName = (process: string): string => { return process.includes('\\') ? process.substring(process.lastIndexOf('\\') + 1) : process; }; const extractItemsWithCounts = ( repositoryMods: Record | null, keyPrefix: string, extractItems: (mod: { repository: { metadata: ModMetadata } }) => string[] ) => { if (!repositoryMods) { return []; } const itemCounts = new Map }>(); for (const mod of Object.values(repositoryMods)) { const items = extractItems(mod); for (const item of items) { if (!item) { continue; } const lowerItem = item.toLowerCase(); const existing = itemCounts.get(lowerItem); if (existing) { existing.count++; const casingCount = existing.casings.get(item); existing.casings.set(item, (casingCount || 0) + 1); } else { const casings = new Map(); casings.set(item, 1); itemCounts.set(lowerItem, { count: 1, casings }); } } } return Array.from(itemCounts.entries()) .map(([lowerName, { count, casings }]) => { // Find the most common casing, or first lexicographically if tied const displayName = Array.from(casings.entries()).reduce( (best, [casing, casingCount]) => { if (casingCount > best.count || (casingCount === best.count && casing < best.casing)) { return { casing, count: casingCount }; } return best; }, { casing: '', count: 0 } ).casing; return { name: displayName, count, key: `${keyPrefix}:${lowerName}`, lowerName, }; }) .sort((a, b) => { if (b.count !== a.count) { return b.count - a.count; } return a.lowerName.localeCompare(b.lowerName); }); }; const extractAuthorsWithCounts = ( repositoryMods: Record | null ) => { return extractItemsWithCounts( repositoryMods, 'author', (mod) => mod.repository.metadata.author ? [mod.repository.metadata.author] : [] ); }; const extractProcessesWithCounts = ( repositoryMods: Record | null ) => { return extractItemsWithCounts( repositoryMods, 'process', (mod) => { const processes = mod.repository.metadata.include || []; const validProcesses: string[] = []; for (const process of processes) { if (!process) { continue; } // Include "*" as-is if (process === '*') { validProcesses.push('*'); } else if (process.includes('*') || process.includes('?')) { // Skip other wildcard patterns continue; } else { validProcesses.push(normalizeProcessName(process)); } } return validProcesses; } ); }; const useFilterState = () => { const [filterText, setFilterText] = useState(''); const [filterOptions, setFilterOptions] = useState>(new Set()); const [filterDropdownOpen, setFilterDropdownOpen] = useState(false); const [showAllAuthors, setShowAllAuthors] = useState(false); const [showAllProcesses, setShowAllProcesses] = useState(false); const handleFilterChange = useCallback((key: string) => { setFilterOptions((prevOptions) => { const newOptions = new Set(prevOptions); // Handle mutually exclusive filters for installation status if (key === 'installed' && newOptions.has('not-installed')) { newOptions.delete('not-installed'); } else if (key === 'not-installed' && newOptions.has('installed')) { newOptions.delete('installed'); } // Toggle the clicked option if (newOptions.has(key)) { newOptions.delete(key); } else { newOptions.add(key); } return newOptions; }); }, []); const handleClearFilters = useCallback(() => { setFilterOptions(new Set()); setShowAllAuthors(false); setShowAllProcesses(false); }, []); return { filterText, setFilterText, filterOptions, filterDropdownOpen, setFilterDropdownOpen, showAllAuthors, setShowAllAuthors, showAllProcesses, setShowAllProcesses, handleFilterChange, handleClearFilters, }; }; interface Props { ContentWrapper: React.ComponentType< React.ComponentPropsWithoutRef<'div'> & { $hidden?: boolean } >; } function ModsBrowserOnline({ ContentWrapper }: Props) { const { t } = useTranslation(); const navigate = useNavigate(); const { modId: displayedModId } = useParams<{ modId: string }>(); const [initialDataPending, setInitialDataPending] = useState(true); const [repositoryMods, setRepositoryMods] = useState | null>(mockModsBrowserOnlineRepositoryMods); const [sortingOrder, setSortingOrder] = useState('popular-top-rated'); // Filter state const { filterText, setFilterText, filterOptions, filterDropdownOpen, setFilterDropdownOpen, showAllAuthors, setShowAllAuthors, showAllProcesses, setShowAllProcesses, handleFilterChange, handleClearFilters, } = useFilterState(); // Extract filter data const authorFilters = useMemo( () => extractAuthorsWithCounts(repositoryMods), [repositoryMods] ); const processFilters = useMemo( () => extractProcessesWithCounts(repositoryMods), [repositoryMods] ); const installedModsFilteredAndSorted = useMemo(() => { const filterWords = filterText.toLowerCase().split(/\s+/) .map(word => word.trim()) .filter(word => word.length > 0); return Object.entries(repositoryMods || {}) .filter(([modId, mod]) => { // Apply text filter if (filterWords.length > 0) { const textMatch = filterWords.every((filterWord) => { return ( modId.toLowerCase().includes(filterWord) || mod.repository.metadata.name?.toLowerCase().includes(filterWord) || mod.repository.metadata.description ?.toLowerCase() .includes(filterWord) ); }); if (!textMatch) { return false; } } // Apply category filters - if none selected, show all if (filterOptions.size === 0) { return true; } // Collect selected authors and processes const selectedAuthors: string[] = []; const selectedProcesses: string[] = []; let installedFilter: boolean | null = null; for (const key of filterOptions) { if (key.startsWith('author:')) { selectedAuthors.push(key.substring('author:'.length)); } else if (key.startsWith('process:')) { selectedProcesses.push(key.substring('process:'.length)); } else if (key === 'installed') { installedFilter = true; } else if (key === 'not-installed') { installedFilter = false; } } // Check installation status filter if (installedFilter !== null) { const isInstalled = mod.installed !== undefined; if (isInstalled !== installedFilter) { return false; } } // Check author filter (OR logic within authors) if (selectedAuthors.length > 0) { const author = mod.repository.metadata.author?.toLowerCase(); if (!author || !selectedAuthors.some(a => a === author)) { return false; } } // Check process filter (OR logic within processes) if (selectedProcesses.length > 0) { const processes = (mod.repository.metadata.include || []) .map(p => normalizeProcessName(p).toLowerCase()) .filter(p => p); // Remove empty strings if (!selectedProcesses.some(sp => processes.includes(sp))) { return false; } } return true; }) .sort((a, b) => { const [modIdA, modA] = a; const [modIdB, modB] = b; switch (sortingOrder) { case 'popular-top-rated': if ( modB.repository.details.defaultSorting < modA.repository.details.defaultSorting ) { return -1; } else if ( modB.repository.details.defaultSorting > modA.repository.details.defaultSorting ) { return 1; } break; case 'popular': if (modB.repository.details.users < modA.repository.details.users) { return -1; } else if ( modB.repository.details.users > modA.repository.details.users ) { return 1; } break; case 'top-rated': if ( modB.repository.details.rating < modA.repository.details.rating ) { return -1; } else if ( modB.repository.details.rating > modA.repository.details.rating ) { return 1; } break; case 'newest': if ( modB.repository.details.published < modA.repository.details.published ) { return -1; } else if ( modB.repository.details.published > modA.repository.details.published ) { return 1; } break; case 'last-updated': if ( modB.repository.details.updated < modA.repository.details.updated ) { return -1; } else if ( modB.repository.details.updated > modA.repository.details.updated ) { return 1; } break; case 'alphabetical': // Nothing to do. break; } // Fallback sorting: Sort by name, then id. const modATitle = ( modA.repository.metadata.name || modIdA ).toLowerCase(); const modBTitle = ( modB.repository.metadata.name || modIdB ).toLowerCase(); if (modATitle < modBTitle) { return -1; } else if (modATitle > modBTitle) { return 1; } if (modIdA < modIdB) { return -1; } else if (modIdA > modIdB) { return 1; } return 0; }); }, [repositoryMods, sortingOrder, filterText, filterOptions]); const { devModeOptOut } = useContext(AppUISettingsContext); const { getRepositoryMods } = useGetRepositoryMods( useCallback((data) => { setRepositoryMods(data.mods); setInitialDataPending(false); }, []) ); useEffect(() => { let pending = false; if (!useMockData) { getRepositoryMods({}); pending = true; } setInitialDataPending(pending); }, [getRepositoryMods]); useUpdateInstalledModsDetails( useCallback( (data) => { if (repositoryMods) { const installedModsDetails = data.details; setRepositoryMods( produce(repositoryMods, (draft) => { for (const [modId, updatedDetails] of Object.entries( installedModsDetails )) { const details = draft[modId]?.installed; if (details) { const { userRating } = updatedDetails; details.userRating = userRating; } } }) ); } }, [repositoryMods] ) ); const { installMod, installModPending, installModContext } = useInstallMod<{ updating: boolean; }>( useCallback( (data) => { const { installedModDetails } = data; if (installedModDetails && repositoryMods) { const modId = data.modId; setRepositoryMods( produce(repositoryMods, (draft) => { draft[modId].installed = installedModDetails; }) ); } }, [repositoryMods] ) ); const { compileMod, compileModPending } = useCompileMod( useCallback( (data) => { const { compiledModDetails } = data; if (compiledModDetails && repositoryMods) { const modId = data.modId; setRepositoryMods( produce(repositoryMods, (draft) => { draft[modId].installed = compiledModDetails; }) ); } }, [repositoryMods] ) ); const { enableMod } = useEnableMod( useCallback( (data) => { if (data.succeeded && repositoryMods) { const modId = data.modId; setRepositoryMods( produce(repositoryMods, (draft) => { const config = draft[modId].installed?.config; if (config) { config.disabled = !data.enabled; } }) ); } }, [repositoryMods] ) ); const { deleteMod } = useDeleteMod( useCallback( (data) => { if (data.succeeded && repositoryMods) { const modId = data.modId; setRepositoryMods( produce(repositoryMods, (draft) => { delete draft[modId].installed; }) ); } }, [repositoryMods] ) ); const { updateModRating } = useUpdateModRating( useCallback( (data) => { if (data.succeeded && repositoryMods) { const modId = data.modId; setRepositoryMods( produce(repositoryMods, (draft) => { const installed = draft[modId].installed; if (installed) { installed.userRating = data.rating; } }) ); } }, [repositoryMods] ) ); const [infiniteScrollLoadedItems, setInfiniteScrollLoadedItems] = useState(30); const resetInfiniteScrollLoadedItems = () => setInfiniteScrollLoadedItems(30); const [detailsButtonClicked, setDetailsButtonClicked] = useState(false); // Block all navigation when modal is open const modalIsOpen = installModPending || compileModPending; useBlocker(({ currentLocation, nextLocation }) => { return modalIsOpen && currentLocation.pathname !== nextLocation.pathname; }); if (initialDataPending) { return ( ); } if (!repositoryMods) { return ( getRepositoryMods({})} > {t('general.tryAgain')} , ]} /> ); } return ( <> } placeholder={t('modSearch.placeholder') as string} allowClear value={filterText} onChange={(e) => { resetInfiniteScrollLoadedItems(); setFilterText(e.target.value); }} /> ({ label: , key: author.key, })), ...(authorFilters.length > 5 && !showAllAuthors ? [{ label: t('explore.filter.showMore'), key: 'show-more-authors', }] : []), ], }, { type: 'group', label: t('explore.filter.process'), children: [ ...(showAllProcesses ? processFilters : processFilters.slice(0, 5)).map(process => ({ label: , key: process.key, })), ...(processFilters.length > 5 && !showAllProcesses ? [{ label: t('explore.filter.showMore'), key: 'show-more-processes', }] : []), ], }, { type: 'divider', }, { label: t('explore.filter.clearFilters'), key: 'clear-filters', }, ], selectedKeys: Array.from(filterOptions), onClick: (e) => { if (e.key === 'clear-filters') { dropdownModalDismissed(); handleClearFilters(); setFilterDropdownOpen(false); resetInfiniteScrollLoadedItems(); } else if (e.key === 'show-more-authors') { setShowAllAuthors(true); } else if (e.key === 'show-more-processes') { setShowAllProcesses(true); } else { handleFilterChange(e.key); resetInfiniteScrollLoadedItems(); // Keep dropdown open for filter changes } }, }} > 0 ? 'primary' : undefined} > { dropdownModalDismissed(); resetInfiniteScrollLoadedItems(); setSortingOrder(e.key); }, }} > {installedModsFilteredAndSorted.length === 0 ? ( ) : ( setInfiniteScrollLoadedItems( Math.min( infiniteScrollLoadedItems + 30, installedModsFilteredAndSorted.length ) ) } hasMore={ infiniteScrollLoadedItems < installedModsFilteredAndSorted.length } loader={null} scrollableTarget="ModsBrowserOnline-ContentWrapper" style={{ overflow: 'visible' }} // for the ribbon > {installedModsFilteredAndSorted .slice(0, infiniteScrollLoadedItems) .map(([modId, mod]) => ( { setDetailsButtonClicked(true); navigate('/mods-browser/' + modId); }, }, ]} /> ))} )} {displayedModId && ( { // If we ever clicked on Details, go back. // Otherwise, we probably arrived from a different location, // go straight to the mods page. if (detailsButtonClicked) { navigate(-1); } else { navigate('/mods-browser'); } }} installMod={(modSource) => installMod({ modId: displayedModId, modSource }) } updateMod={(modSource, disabled) => installMod( { modId: displayedModId, modSource, disabled }, { updating: true } ) } forkModFromSource={(modSource) => forkMod({ modId: displayedModId, modSource }) } compileMod={() => compileMod({ modId: displayedModId })} enableMod={(enable) => enableMod({ modId: displayedModId, enable })} editMod={() => editMod({ modId: displayedModId })} forkMod={() => forkMod({ modId: displayedModId })} deleteMod={() => deleteMod({ modId: displayedModId })} updateModRating={(newRating) => updateModRating({ modId: displayedModId, rating: newRating }) } /> )} {(installModPending || compileModPending) && ( )} ); } export default ModsBrowserOnline; ================================================ FILE: src/vscode-windhawk-ui/apps/vscode-windhawk-ui/src/app/panel/Panel.tsx ================================================ import React, { useEffect } from 'react'; import { createHashRouter, Outlet, RouterProvider, useNavigate } from 'react-router-dom'; import styled, { css } from 'styled-components'; import About from './About'; import AppHeader from './AppHeader'; import CreateNewModButton from './CreateNewModButton'; import ModPreview from './ModPreview'; import ModsBrowserLocal from './ModsBrowserLocal'; import ModsBrowserOnline from './ModsBrowserOnline'; import SafeModeIndicator from './SafeModeIndicator'; import Settings from './Settings'; const PanelContainer = styled.div` display: flex; height: 100vh; overflow: hidden; flex-direction: column; `; const ContentContainerScroll = styled.div<{ $hidden?: boolean }>` ${({ $hidden }) => css` display: ${$hidden ? 'none' : 'flex'}; `} position: relative; // needed by nested elements that use position: absolute flex: 1; overflow: overlay; `; const ContentContainer = styled.div` width: 100%; height: 100%; max-width: var(--app-max-width); margin: 0 auto; padding: 0 20px; // Disable margin-collapsing: https://stackoverflow.com/a/47351270 display: flex; flex-direction: column; `; function ContentWrapper({ ref, ...props }: React.ComponentProps<'div'> & { $hidden?: boolean }) { return ( {props.children} ); } function ContentWrapperWithOutlet() { return ( ); } function KeyboardNavigationHandler() { const navigate = useNavigate(); useEffect(() => { const handleKeyDown = (event: KeyboardEvent) => { // Alt+Left for back navigation if (event.altKey && event.key === 'ArrowLeft') { event.preventDefault(); navigate(-1); } // Alt+Right for forward navigation else if (event.altKey && event.key === 'ArrowRight') { event.preventDefault(); navigate(1); } }; window.addEventListener('keydown', handleKeyDown); return () => window.removeEventListener('keydown', handleKeyDown); }, [navigate]); return null; } function Layout() { return ( <> ); } // Must be done before creating the router to ensure the initial route is // correct. const bodyParams = document.querySelector('body')?.getAttribute('data-params'); const previewModId = bodyParams && JSON.parse(bodyParams).previewModId; if (previewModId) { const url = new URL(window.location.href); url.hash = '#/mod-preview/' + previewModId; window.history.replaceState(null, '', url); } const router = createHashRouter([ { element: , children: [ { path: '/', element: ( <> ), children: [ { path: 'mods/:modType/:modId', element: null, }, ], }, { path: '/mod-preview/:modId', element: , }, { path: '/mods-browser', element: ( <> ), children: [ { path: ':modId', element: null, }, ], }, { path: '/settings', element: , children: [ { index: true, element: , }, ], }, { path: '/about', element: , children: [ { index: true, element: , }, ], }, ], }, ]); function Panel() { return ( ); } export default Panel; ================================================ FILE: src/vscode-windhawk-ui/apps/vscode-windhawk-ui/src/app/panel/SafeModeIndicator.tsx ================================================ import { Alert, Button } from 'antd'; import { useCallback, useContext } from 'react'; import { useTranslation } from 'react-i18next'; import styled from 'styled-components'; import { AppUISettingsContext } from '../appUISettings'; import { PopconfirmModal } from '../components/InputWithContextMenu'; import { useUpdateAppSettings } from '../webviewIPC'; const FullWidthAlert = styled(Alert)` padding-inline-start: calc(20px + max(50% - var(--app-max-width) / 2, 0px)); padding-inline-end: calc(20px + max(50% - var(--app-max-width) / 2, 0px)); `; const FullWidthAlertContent = styled.div` display: flex; align-items: center; gap: 8px; `; function SafeModeIndicator() { const { t } = useTranslation(); const { updateAppSettings } = useUpdateAppSettings( useCallback((data) => { // Do nothing, we should be restarted soon. }, []) ); const { safeMode } = useContext(AppUISettingsContext); if (!safeMode) { return null; } return (
    {t('safeMode.alert')}
    { updateAppSettings({ appSettings: { safeMode: false, }, }); }} >
    } banner /> ); } export default SafeModeIndicator; ================================================ FILE: src/vscode-windhawk-ui/apps/vscode-windhawk-ui/src/app/panel/Settings.tsx ================================================ import { Alert, Badge, Button, Checkbox, Collapse, List, Modal, Select, Space, Switch, Tooltip } from 'antd'; import { useCallback, useContext, useEffect, useState } from 'react'; import { Trans, useTranslation } from 'react-i18next'; import styled from 'styled-components'; import { AppUISettingsContext } from '../appUISettings'; import { InputNumberWithContextMenu, SelectModal, TextAreaWithContextMenu } from '../components/InputWithContextMenu'; import { sanitizeUrl } from '../utils'; import { useGetAppSettings, useUpdateAppSettings } from '../webviewIPC'; import { AppSettings } from '../webviewIPCMessages'; import { mockSettings } from './mockData'; const SettingsWrapper = styled.div` padding-bottom: 20px; `; const SettingsList = styled(List)` margin-bottom: 20px; `; const SettingsListItemMeta = styled(List.Item.Meta)` .ant-list-item-meta { margin-bottom: 8px; } .ant-list-item-meta-title { margin-bottom: 0; } `; const SettingsSelect = styled(SelectModal)` width: 200px; `; const SettingsNotice = styled.div` margin-top: 14px; color: rgba(255, 255, 255, 0.45); `; const SettingInputNumber = styled(InputNumberWithContextMenu)` width: 100%; max-width: 130px; // Remove default VSCode focus highlighting color. input:focus { outline: none !important; } `; const appLanguages = [ ['en', 'English'], ...Object.entries({ ar: 'العربية', cs: 'Čeština', da: 'Dansk', de: 'Deutsch', el: 'Ελληνικά', es: 'Español', fr: 'Français', hi: 'हिन्दी', hr: 'Hrvatski', hu: 'Magyar', id: 'Bahasa Indonesia', it: 'Italiano', ja: '日本語', ko: '한국어', nl: 'Nederlands', pl: 'Polski', 'pt-BR': 'Português', ro: 'Română', ru: 'Русский', sv: 'Svenska', ta: 'தமிழ்', th: 'ภาษาไทย', tr: 'Türkçe', uk: 'Українська', vi: 'Tiếng Việt', 'zh-CN': '简体中文', 'zh-TW': '繁體中文', }).sort((a, b) => a[1].localeCompare(b[1])), ]; function parseIntLax(value?: string | number | null) { const result = parseInt((value ?? 0).toString(), 10); return Number.isNaN(result) ? 0 : result; } function engineArrayToProcessList(processArray: string[]) { return processArray.join('\n'); } function engineProcessListToArray(processList: string) { return processList .split('\n') .map((x) => x.replace(/["/<>|]/g, '').trim()) .filter((x) => x); } function Settings() { const { t, i18n } = useTranslation(); const appLanguage = i18n.resolvedLanguage; const { loggingEnabled } = useContext(AppUISettingsContext); const [appSettings, setAppSettings] = useState | null>( mockSettings ); // More advanced settings. const [appLoggingVerbosity, setAppLoggingVerbosity] = useState(0); const [engineLoggingVerbosity, setEngineLoggingVerbosity] = useState(0); const [engineInclude, setEngineInclude] = useState(''); const [engineExclude, setEngineExclude] = useState(''); const [engineInjectIntoCriticalProcesses, setEngineInjectIntoCriticalProcesses] = useState(false); const [engineInjectIntoIncompatiblePrograms, setEngineInjectIntoIncompatiblePrograms] = useState(false); const [engineInjectIntoGames, setEngineInjectIntoGames] = useState(false); const resetMoreAdvancedSettings = useCallback(() => { setAppLoggingVerbosity(appSettings?.loggingVerbosity ?? 0); setEngineLoggingVerbosity(appSettings?.engine?.loggingVerbosity ?? 0); setEngineInclude(engineArrayToProcessList(appSettings?.engine?.include ?? [])); setEngineExclude(engineArrayToProcessList(appSettings?.engine?.exclude ?? [])); setEngineInjectIntoCriticalProcesses(appSettings?.engine?.injectIntoCriticalProcesses ?? false); setEngineInjectIntoIncompatiblePrograms(appSettings?.engine?.injectIntoIncompatiblePrograms ?? false); setEngineInjectIntoGames(appSettings?.engine?.injectIntoGames ?? false); }, [appSettings]); const { getAppSettings } = useGetAppSettings( useCallback((data) => { setAppSettings(data.appSettings); }, []) ); useEffect(() => { getAppSettings({}); }, [getAppSettings]); const { updateAppSettings } = useUpdateAppSettings( useCallback( (data) => { if (data.succeeded && appSettings) { setAppSettings({ ...appSettings, ...data.appSettings, }); } }, [appSettings] ) ); const [isMoreAdvancedSettingsModalOpen, setIsMoreAdvancedSettingsModalOpen] = useState(false); if (!appSettings) { return null; } const includeListEmpty = engineInclude.trim() === ''; const excludeListEmpty = engineExclude.trim() === '' && engineInjectIntoCriticalProcesses && engineInjectIntoIncompatiblePrograms && engineInjectIntoGames; const excludeListHasWildcard = !!engineExclude.match(/^[ \t]*\*[ \t]*$/m); return (
    {t('settings.language.description')}
    website , ]} />
    } /> { updateAppSettings({ appSettings: { language: typeof value === 'string' ? value : 'en', }, }); }} dropdownMatchSelectWidth={false} > {appLanguages.map(([languageId, languageDisplayName]) => ( {languageDisplayName} ))} {appLanguage !== 'en' && ( website , ]} /> )}
    { updateAppSettings({ appSettings: { disableUpdateCheck: !checked, }, }); }} /> { updateAppSettings({ appSettings: { devModeOptOut: !checked, }, }); }} />
    {t('settings.advancedSettings')} {' '} {loggingEnabled && ( )} } key="1"> { updateAppSettings({ appSettings: { hideTrayIcon: checked, }, }); }} /> { updateAppSettings({ appSettings: { alwaysCompileModsLocally: checked, }, }); }} /> {appSettings.disableRunUIScheduledTask !== null && ( { updateAppSettings({ appSettings: { disableRunUIScheduledTask: checked, }, }); }} /> )} { updateAppSettings({ appSettings: { dontAutoShowToolkit: checked, }, }); }} /> { updateAppSettings({ appSettings: { modTasksDialogDelay: parseIntLax(value) - 1000, }, }); }} /> { updateAppSettings({ appSettings: { loggingVerbosity: appLoggingVerbosity, engine: { loggingVerbosity: engineLoggingVerbosity, include: engineProcessListToArray(engineInclude), exclude: engineProcessListToArray(engineExclude), injectIntoCriticalProcesses: engineInjectIntoCriticalProcesses, injectIntoIncompatiblePrograms: engineInjectIntoIncompatiblePrograms, injectIntoGames: engineInjectIntoGames, }, }, }); setIsMoreAdvancedSettingsModalOpen(false); }} onCancel={() => { setIsMoreAdvancedSettingsModalOpen(false); }} okText={t('settings.moreAdvancedSettings.saveButton')} cancelText={t('settings.moreAdvancedSettings.cancelButton')} > { setAppLoggingVerbosity(typeof value === 'number' ? value : 0); }} dropdownMatchSelectWidth={false} > {t('settings.loggingVerbosity.none')} {t('settings.loggingVerbosity.error')} {t('settings.loggingVerbosity.verbose')} { setEngineLoggingVerbosity( typeof value === 'number' ? value : 0 ); }} dropdownMatchSelectWidth={false} > {t('settings.loggingVerbosity.none')} {t('settings.loggingVerbosity.error')} {t('settings.loggingVerbosity.verbose')}

    {t('settings.processList.descriptionExclusion')}

    wiki]} />
    } /> { setEngineExclude(e.target.value); }} /> {engineExclude.match(/["/<>|]/) && ( |', })} type="warning" showIcon /> )} { setEngineInjectIntoCriticalProcesses(!e.target.checked); }} > {t('settings.processList.excludeCriticalProcesses')} { setEngineInjectIntoIncompatiblePrograms(!e.target.checked); }} > {t('settings.processList.excludeIncompatiblePrograms')} { setEngineInjectIntoGames(!e.target.checked); }} > {t('settings.processList.excludeGames')}
    { setEngineInclude(e.target.value); }} /> {engineInclude.match(/["/<>|]/) && ( |', })} type="warning" showIcon /> )} {!includeListEmpty && excludeListEmpty && ( )} {!includeListEmpty && !excludeListHasWildcard && ( )}
    ); } export default Settings; ================================================ FILE: src/vscode-windhawk-ui/apps/vscode-windhawk-ui/src/app/panel/UpdateModal.tsx ================================================ import { Button, Modal, Progress, Result } from 'antd'; import { useCallback, useEffect, useState } from 'react'; import { useTranslation } from 'react-i18next'; import styled from 'styled-components'; import { useCancelUpdate, useStartUpdate, useUpdateDownloadProgress, useUpdateInstalling, } from '../webviewIPC'; const ModalContent = styled.div` display: flex; flex-direction: column; gap: 16px; padding: 16px 0; `; const StatusMessage = styled.div` text-align: center; font-size: 16px; `; const Note = styled.div` text-align: center; color: var(--vscode-descriptionForeground, #9d9d9d); font-size: 14px; `; type UpdateStatus = 'idle' | 'downloading' | 'installing' | 'failed'; interface Props { open: boolean; onClose: () => void; } export function UpdateModal(props: Props) { const { t } = useTranslation(); const [status, setStatus] = useState('idle'); const [downloadProgress, setDownloadProgress] = useState(0); const [errorMessage, setErrorMessage] = useState(''); const resetState = useCallback(() => { setStatus('idle'); setDownloadProgress(0); setErrorMessage(''); }, []); const resetAndClose = useCallback(() => { resetState(); props.onClose(); }, [props, resetState]); const { startUpdate } = useStartUpdate(useCallback((data) => { if (!data.succeeded) { setStatus('failed'); setErrorMessage(data.error || 'Unknown error'); } // At this point, the installer was started successfully. }, [])); // Listen for update progress events useUpdateDownloadProgress( useCallback((data) => { setStatus('downloading'); setDownloadProgress(data.progress); }, []) ); useUpdateInstalling( useCallback(() => { setStatus('installing'); }, []) ); // Start update when modal opens useEffect(() => { if (props.open) { resetState(); startUpdate({}); } }, [props.open, resetState, startUpdate]); const { cancelUpdate } = useCancelUpdate(useCallback((data) => { if (data.succeeded) { resetAndClose(); } // If cancellation failed, stay in current state and let user try again }, [resetAndClose])); const canCancel = status === 'downloading'; const canClose = status === 'installing' || status === 'failed'; const showProgress = status === 'downloading' || status === 'idle'; const handleCancel = () => { if (canCancel) { cancelUpdate({}); } else if (canClose) { resetAndClose(); } }; return ( {t('about.update.modal.cancel')} , ] : null } title={t('about.update.modal.title')} width={500} centered > {status === 'failed' ? ( ) : ( <> {status === 'downloading' && t('about.update.modal.downloading')} {status === 'installing' && t('about.update.modal.installing')} {showProgress && ( )} {status === 'installing' && ( {t('about.update.modal.installingNote')} )} )} ); } ================================================ FILE: src/vscode-windhawk-ui/apps/vscode-windhawk-ui/src/app/panel/VersionSelectorModal.tsx ================================================ import { Badge, Menu, Modal, Spin } from 'antd'; import { useCallback, useEffect, useMemo, useState } from 'react'; import { useTranslation } from 'react-i18next'; import styled from 'styled-components'; import { useGetModVersions } from '../webviewIPC'; import { mockModVersions } from './mockData'; type ModVersionInfo = { version: string; timestamp: number; isPreRelease: boolean; }; const ModalContent = styled.div` display: flex; flex-direction: column; gap: 16px; `; const MenuWrapper = styled.div` max-height: 400px; overflow-y: auto; border: 1px solid #303030; .ant-menu { border: none; border-radius: 2px; .ant-menu-item { margin: 0; } } `; const VersionItemContainer = styled.div` display: flex; justify-content: space-between; align-items: center; width: 100%; `; const VersionText = styled.span` flex: 1; `; const VersionDate = styled.span` color: var(--vscode-descriptionForeground, #9d9d9d); font-size: 12px; margin-inline-start: 8px; `; const PreReleaseBadge = styled(Badge)` .ant-badge-count { background-color: #faad14; color: #000; font-size: 11px; } `; interface Props { modId: string; open: boolean; selectedVersion?: string | null; onSelect: (version: string, versionTimestamps: Record) => void; onCancel: () => void; } export function VersionSelectorModal(props: Props) { const { t } = useTranslation(); const [selectedVersion, setSelectedVersion] = useState(); const [versions, setVersions] = useState(null); const [loadedModId, setLoadedModId] = useState(null); // IPC hook for fetching versions const { getModVersions, getModVersionsPending } = useGetModVersions( useCallback( (data) => { if (data.modId === props.modId) { setVersions(data.versions); setLoadedModId(data.modId); } }, [props.modId] ) ); // Fetch versions when modal opens (only if not already loaded for this modId) useEffect(() => { if (props.open && loadedModId !== props.modId) { if (mockModVersions) { setVersions(mockModVersions); setLoadedModId(props.modId); } else { getModVersions({ modId: props.modId }); } } }, [props.open, props.modId, loadedModId, getModVersions]); // Pre-select the version when modal opens useEffect(() => { if (props.open && props.selectedVersion) { setSelectedVersion(props.selectedVersion); } }, [props.open, props.selectedVersion]); const sortedVersions = useMemo(() => { if (!versions) { return []; } // Sort by timestamp, newest first return [...versions].sort((a, b) => b.timestamp - a.timestamp); }, [versions]); const formatDate = (timestamp: number) => { const date = new Date(timestamp * 1000); return date.toLocaleDateString(undefined, { year: 'numeric', month: 'short', day: 'numeric', }); }; const handleMenuClick = (version: string) => { setSelectedVersion(version); }; const handleSelect = () => { if (selectedVersion) { const versionTimestamps = versions?.reduce((acc, v) => { acc[v.version] = v.timestamp; return acc; }, {} as Record) ?? {}; props.onSelect(selectedVersion, versionTimestamps); setSelectedVersion(undefined); } }; const handleCancel = () => { setSelectedVersion(undefined); props.onCancel(); }; const menuItems = useMemo(() => { return sortedVersions.map((version) => ({ key: version.version, label: ( {version.version} {version.isPreRelease && ( <> {' '} )} {formatDate(version.timestamp)} ), })); }, [sortedVersions, t]); return ( {getModVersionsPending ? ( ) : ( handleMenuClick(key)} /> )} ); } ================================================ FILE: src/vscode-windhawk-ui/apps/vscode-windhawk-ui/src/app/panel/mockData.ts ================================================ import vsCodeApi from '../vsCodeApi'; export const useMockData = !vsCodeApi; export const mockAppUISettings = !useMockData ? null : { language: 'en', devModeOptOut: false, devModeUsedAtLeastOnce: false, loggingEnabled: false, updateIsAvailable: false, safeMode: false, }; export const mockSettings = !useMockData ? null : { language: 'en', disableUpdateCheck: false, disableRunUIScheduledTask: false, devModeOptOut: false, devModeUsedAtLeastOnce: false, hideTrayIcon: false, alwaysCompileModsLocally: false, dontAutoShowToolkit: false, modTasksDialogDelay: 2000, safeMode: false, loggingVerbosity: 0, engine: { loggingVerbosity: 0, include: ['a.exe', 'b.exe'], exclude: ['c.exe', 'd.exe'], injectIntoCriticalProcesses: false, injectIntoIncompatiblePrograms: false, injectIntoGames: false, }, }; const mockModMetadata = { id: 'custom-message-box', name: 'Custom Message Box', description: 'Customizes the message box', version: '0.1', author: 'Michael Jackson', github: 'https://github.com/jackson', twitter: 'https://twitter.com/jackson', homepage: 'http://custom-message-box.com/', include: ['*'], exclude: ['explorer.exe'], license: 'MIT', donateUrl: 'https://example.com/donate', }; const mockModMetadataOnline = { ...mockModMetadata, id: undefined, version: '0.2', }; const mockModConfig = { libraryFileName: 'custom-message-box-123456.dll', disabled: false, loggingEnabled: false, debugLoggingEnabled: false, include: ['*'], exclude: ['explorer.exe'], includeCustom: [], excludeCustom: [], includeExcludeCustomOnly: false, patternsMatchCriticalSystemProcesses: true, architecture: ['x86-64'], version: '1.0', }; const mockModDetails = { metadata: {}, config: mockModConfig, updateAvailable: false, userRating: 0, }; export const mockModsBrowserLocalInitialMods = !useMockData ? null : { 'custom-message-box': { metadata: mockModMetadata, config: mockModConfig, updateAvailable: true, userRating: 4, }, 'local@asdf2': mockModDetails, asdf3: mockModDetails, asdf4: mockModDetails, asdf5: mockModDetails, asdf6: mockModDetails, asdf7: mockModDetails, }; export const mockModsBrowserLocalFeaturedMods = !useMockData ? null : { online1: { metadata: mockModMetadataOnline, details: { users: 111222333, rating: 5, ratingBreakdown: [1, 2, 16, 3, 5], defaultSorting: 2, published: 1618321977408, updated: 1718321977408, }, }, }; export const mockModsBrowserOnlineRepositoryMods = !useMockData ? null : { online1: { repository: { metadata: mockModMetadataOnline, details: { users: 111222333, rating: 5, ratingBreakdown: [1, 2, 16, 3, 5], defaultSorting: 2, published: 1618321977408, updated: 1718321977408, }, }, installed: { metadata: mockModMetadata, config: mockModConfig, }, }, ...Object.fromEntries( Array(100) .fill(undefined) .map((e, i) => [ `online${(i + 1).toString().padStart(3, '0')}`, { repository: { metadata: { name: `My Mod ${(i + 1).toString().padStart(3, '0')}`, description: 'A good mod', version: '1.2', author: 'John Smith', github: 'https://github.com/john', twitter: 'https://twitter.com/john', homepage: 'https://example.com/', }, details: { users: 20, rating: 7, ratingBreakdown: [1, 2, 4, 8, 16], defaultSorting: 1, published: 1618321977408, updated: 1718321977408, }, }, }, ]) ), }; export const mockInstalledModSourceData = !useMockData ? null : { source: '// Mock local source...\n', metadata: mockModMetadata, readme: `# Mock readme... | Month | Savings | | -------- | ------- | | January | $250 | | February | $80 | | March | $420 | More text...`, initialSettings: [ { key: 'mock-setting', value: 'mock-setting-value', name: 'Mock Setting Name', description: 'Mock setting description', }, { key: 'mock-setting-dropdown', value: 'a', name: 'Mock Setting Dropdown Name', description: 'Mock setting dropdown description', options: [ { a: 'a option' } as Record, { b: 'b option' } as Record, { c: 'c option' } as Record, { d: 'd option' } as Record, { e: 'e option' } as Record, { f: 'f option' } as Record, { g: 'g option' } as Record, { h: 'h option' } as Record, { i: 'i option' } as Record, ], }, { key: 'mock-setting-array', value: ['a', 'b', 'c'], name: 'Mock Setting Array Name', description: 'Mock setting array description', }, { key: 'mock-setting-nested-array', value: [ [ { key: 'mock-setting-nested', value: ['a', 'b', 'c'], name: 'Mock Setting Nested Name', description: 'Mock setting nested description', } ] ], name: 'Mock Setting Nested Array Name', description: 'Mock setting nested array description', }, ], }; export const mockModSettings = !useMockData ? null : { 'mock-setting': 'mock-setting-value', 'mock-setting-dropdown': 'mock-setting-value', 'mock-setting-array[0]': 'a', 'mock-setting-array[1]': 'b', 'mock-setting-array[2]': 'c', }; export const mockModVersions = !useMockData ? null : [ { version: '0.3-alpha', timestamp: 1758321977, // Sep 20, 2025 isPreRelease: true, }, { version: '0.2', timestamp: 1718321977, // Jun 14, 2024 isPreRelease: false, }, { version: '0.1', timestamp: 1690444800, // Jul 27, 2023 isPreRelease: false, }, { version: '0.1-beta', timestamp: 1684454400, // May 19, 2023 isPreRelease: true, }, ]; export const mockModVersionSource = !useMockData ? null : (version: string) => ({ source: `// Mock source for version ${version}...\n`, metadata: { ...mockModMetadata, version, }, readme: `# Mock readme for version ${version}...\n`, initialSettings: [], }); ================================================ FILE: src/vscode-windhawk-ui/apps/vscode-windhawk-ui/src/app/sidebar/EditorModeControls.tsx ================================================ import { Badge, Button, Dropdown, Switch, Tooltip } from 'antd'; import { useCallback, useState } from 'react'; import { useTranslation } from 'react-i18next'; import styled from 'styled-components'; import { PopconfirmModal } from '../components/InputWithContextMenu'; import { previewEditedMod, showLogOutput, stopCompileEditedMod, useCompileEditedMod, useCompileEditedModStart, useEditedModWasModified, useEnableEditedMod, useEnableEditedModLogging, useExitEditorMode, useSetEditedModId, } from '../webviewIPC'; const SidebarContainer = styled.div` padding: 0 10px; text-align: center; `; const SwitchesContainer = styled.div` margin-bottom: 10px; > * { width: 100%; display: flex; justify-content: space-between; background-color: var(--vscode-editor-background); border: 1px solid #303030; padding: 4px 10px; } > *:not(:last-child) { border-bottom: none; } > *:first-child { border-top-left-radius: 2px; border-top-right-radius: 2px; } > *:last-child { border-bottom-left-radius: 2px; border-bottom-right-radius: 2px; } `; const SwitchesContainerRow = styled.div` // Fixes a button alignment bug. > .ant-tooltip-disabled-compatible-wrapper { font-size: 0; } `; const ButtonsContainer = styled.div` > * { margin-bottom: 10px; } `; const ModIdBox = styled.div` display: inline-block; border-radius: 2px; background: #444; padding: 0 4px; overflow-wrap: anywhere; margin-bottom: 10px; `; const CompileButtonBadge = styled(Badge)` display: block; cursor: default; // Fixes badge z-index issue with dropdown button. > .ant-scroll-number { z-index: 3; } `; const FullWidthDropdownButton = styled(Dropdown.Button)` .ant-btn:not(.ant-dropdown-trigger) { width: 100%; } `; type ModDetailsCommon = { modId: string; modWasModified: boolean; }; type ModDetailsNotCompiled = ModDetailsCommon & { compiled: false; }; type ModDetailsCompiled = ModDetailsCommon & { compiled: true; disabled: boolean; loggingEnabled: boolean; debugLoggingEnabled: boolean; }; export type ModDetails = ModDetailsNotCompiled | ModDetailsCompiled; interface Props { initialModDetails: ModDetails; onExitEditorMode?: () => void; } function EditorModeControls({ initialModDetails, onExitEditorMode }: Props) { const { t } = useTranslation(); const [modId, setModId] = useState(initialModDetails.modId); const [modWasModified, setModWasModified] = useState( initialModDetails.modWasModified ); const [isModCompiled, setIsModCompiled] = useState( initialModDetails.compiled ); const [isModDisabled, setIsModDisabled] = useState( initialModDetails.compiled && initialModDetails.disabled ); const [isLoggingEnabled, setIsLoggingEnabled] = useState( initialModDetails.compiled && initialModDetails.loggingEnabled ); const [compilationFailed, setCompilationFailed] = useState(false); useSetEditedModId( useCallback((data) => { setModId(data.modId); }, []) ); const { enableEditedMod } = useEnableEditedMod( useCallback((data) => { if (data.succeeded) { setIsModDisabled(!data.enabled); } }, []) ); const { enableEditedModLogging } = useEnableEditedModLogging( useCallback((data) => { if (data.succeeded) { setIsLoggingEnabled(data.enabled); } }, []) ); const { compileEditedMod, compileEditedModPending } = useCompileEditedMod( useCallback((data) => { if (data.succeeded) { if (data.clearModified) { setModWasModified(false); } setCompilationFailed(false); setIsModCompiled(true); } else { setCompilationFailed(true); } }, []) ); const { exitEditorMode } = useExitEditorMode( useCallback( (data) => { if (data.succeeded) { onExitEditorMode?.(); } }, [onExitEditorMode] ) ); useCompileEditedModStart( useCallback(() => { if (!compileEditedModPending) { compileEditedMod({ disabled: isModDisabled, loggingEnabled: isLoggingEnabled, }); } }, [ compileEditedMod, compileEditedModPending, isLoggingEnabled, isModDisabled, ]) ); useEditedModWasModified( useCallback(() => { setModWasModified(true); setCompilationFailed(false); }, []) ); return ( {modId}
    {t('sidebar.enableMod')}
    enableEditedMod({ enable: checked })} />
    {t('sidebar.enableLogging')}
    enableEditedModLogging({ enable: checked }) } />
    {compileEditedModPending ? ( stopCompileEditedMod(), }, ], }} > {t('general.compiling')} ) : ( )} exitEditorMode({ saveToDrafts: false })} >
    ); } export default EditorModeControls; ================================================ FILE: src/vscode-windhawk-ui/apps/vscode-windhawk-ui/src/app/sidebar/Sidebar.tsx ================================================ import { useCallback, useEffect, useState } from 'react'; import { getInitialSidebarParams, useSetEditedModDetails, } from '../webviewIPC'; import EditorModeControls, { ModDetails } from './EditorModeControls'; import { mockSidebarModDetails } from './mockData'; function Sidebar() { const [modDetails, setModDetails] = useState( mockSidebarModDetails ); useEffect(() => { getInitialSidebarParams(); }, []); useSetEditedModDetails( useCallback((data) => { if (!data.modDetails) { setModDetails({ modId: data.modId, modWasModified: data.modWasModified, compiled: false, }); } else { setModDetails({ modId: data.modId, modWasModified: data.modWasModified, compiled: true, disabled: data.modDetails.disabled, loggingEnabled: data.modDetails.loggingEnabled, debugLoggingEnabled: data.modDetails.debugLoggingEnabled, }); } }, []) ); const onExitEditorMode = useCallback(() => { setModDetails(null); }, []); if (!modDetails) { return null; } return ( ); } export default Sidebar; ================================================ FILE: src/vscode-windhawk-ui/apps/vscode-windhawk-ui/src/app/sidebar/mockData.ts ================================================ import vsCodeApi from '../vsCodeApi'; export const useMockData = !vsCodeApi; export const mockSidebarModDetails = !useMockData ? null : { modId: 'new-mod-test', modWasModified: false, compiled: true, disabled: false, loggingEnabled: false, debugLoggingEnabled: false, }; ================================================ FILE: src/vscode-windhawk-ui/apps/vscode-windhawk-ui/src/app/swrHelpers.ts ================================================ export const fetchText = (input: RequestInfo | URL, init?: RequestInit) => fetch(input, init).then((res) => res.text()); ================================================ FILE: src/vscode-windhawk-ui/apps/vscode-windhawk-ui/src/app/utils.spec.ts ================================================ import { sanitizeUrl } from './utils'; describe('sanitizeUrl', () => { it('should allow http URLs', () => { expect(sanitizeUrl('http://example.com')).toBe('http://example.com'); expect(sanitizeUrl('http://example.com/path')).toBe('http://example.com/path'); }); it('should allow https URLs', () => { expect(sanitizeUrl('https://example.com')).toBe('https://example.com'); expect(sanitizeUrl('https://example.com/path')).toBe('https://example.com/path'); }); it('should reject javascript: URLs', () => { // eslint-disable-next-line no-script-url expect(sanitizeUrl('javascript:alert(1)')).toBeUndefined(); }); it('should reject data: URLs', () => { expect(sanitizeUrl('data:text/html,')).toBeUndefined(); }); it('should reject file: URLs', () => { expect(sanitizeUrl('file:///etc/passwd')).toBeUndefined(); }); it('should reject vbscript: URLs', () => { expect(sanitizeUrl('vbscript:msgbox(1)')).toBeUndefined(); }); it('should handle undefined input', () => { expect(sanitizeUrl(undefined)).toBeUndefined(); }); it('should handle empty string', () => { expect(sanitizeUrl('')).toBeUndefined(); expect(sanitizeUrl(' ')).toBeUndefined(); }); it('should handle invalid URLs', () => { expect(sanitizeUrl('not a url')).toBeUndefined(); expect(sanitizeUrl('htp://example.com')).toBeUndefined(); }); it('should preserve query parameters', () => { expect(sanitizeUrl('https://example.com?foo=bar')).toBe('https://example.com?foo=bar'); }); it('should preserve URL fragments', () => { expect(sanitizeUrl('https://example.com#section')).toBe('https://example.com#section'); }); }); ================================================ FILE: src/vscode-windhawk-ui/apps/vscode-windhawk-ui/src/app/utils.ts ================================================ /** * Sanitizes a URL to only allow http:// or https:// protocols. * Returns undefined if the URL is invalid or uses a disallowed protocol. * * @param url - The URL to sanitize * @returns The sanitized URL or undefined if invalid */ export function sanitizeUrl(url: string | undefined): string | undefined { if (!url || typeof url !== 'string') { return undefined; } const trimmedUrl = url.trim(); if (!trimmedUrl) { return undefined; } try { const parsed = new URL(trimmedUrl); // Only allow http and https protocols if (parsed.protocol === 'http:' || parsed.protocol === 'https:') { return trimmedUrl; } return undefined; } catch (e) { console.warn(`Invalid URL format (${url}):`, e); return undefined; } } ================================================ FILE: src/vscode-windhawk-ui/apps/vscode-windhawk-ui/src/app/vsCodeApi.ts ================================================ // https://github.com/microsoft/vscode/issues/96221#issuecomment-735408921 declare function acquireVsCodeApi(): { getState: () => T; setState: (data: T) => void; postMessage: (msg: unknown) => void; }; const vsCodeApi = typeof acquireVsCodeApi !== 'undefined' ? acquireVsCodeApi() : null; export default vsCodeApi; ================================================ FILE: src/vscode-windhawk-ui/apps/vscode-windhawk-ui/src/app/webviewIPC.ts ================================================ import { useCallback, useState } from 'react'; import { useEventListener } from 'usehooks-ts'; import vsCodeApi from './vsCodeApi'; import { CancelUpdateReplyData, CompileEditedModData, CompileEditedModReplyData, CompileModData, CompileModReplyData, DeleteModData, DeleteModReplyData, EditModData, EnableEditedModData, EnableEditedModLoggingData, EnableEditedModLoggingReplyData, EnableEditedModReplyData, EnableModData, EnableModReplyData, ExitEditorModeData, ExitEditorModeReplyData, ForkModData, GetAppSettingsReplyData, GetFeaturedModsReplyData, GetInitialAppSettingsReplyData, GetInstalledModsReplyData, GetModConfigData, GetModConfigReplyData, GetModSettingsData, GetModSettingsReplyData, GetModSourceDataData, GetModSourceDataReplyData, GetModVersionsData, GetModVersionsReplyData, GetRepositoryModSourceDataData, GetRepositoryModSourceDataReplyData, GetRepositoryModsReplyData, InstallModData, InstallModReplyData, NoData, SetEditedModDetailsData, SetEditedModIdData, SetModSettingsData, SetModSettingsReplyData, SetNewAppSettingsData, SetNewModConfigData, StartUpdateReplyData, UpdateAppSettingsData, UpdateAppSettingsReplyData, UpdateDownloadProgressEventData, UpdateInstalledModsDetailsData, UpdateInstallingEventData, UpdateModConfigData, UpdateModConfigReplyData, UpdateModRatingData, UpdateModRatingReplyData } from './webviewIPCMessages'; // Message types: // * 'message' is a message from the webview to the extension. // * 'messageWithReply' is a message from the webview to the extension that expects a reply. // * 'reply' is a reply to a 'messageWithReply' message. // * 'event' is a message from the extension to the webview. type MessageType = 'message' | 'messageWithReply' | 'reply' | 'event'; type CommonMessageBase = { type: MessageType; command: string; data: Record; }; type MessageRegular = CommonMessageBase & { type: 'message'; command: string; data: Record; }; type MessageWithReply = CommonMessageBase & { type: 'messageWithReply'; command: string; data: Record; messageId: number; }; type Reply = CommonMessageBase & { type: 'reply'; command: string; data: Record; messageId: number; }; type Event = CommonMessageBase & { type: 'event'; command: string; data: Record; }; type MessageAny = MessageRegular | MessageWithReply | Reply | Event; //////////////////////////////////////////////////////////// // Messages. export function createNewMod() { const msg: MessageRegular = { type: 'message', command: 'createNewMod', data: {}, }; vsCodeApi?.postMessage(msg); } export function editMod(data: EditModData) { const msg: MessageRegular = { type: 'message', command: 'editMod', data, }; vsCodeApi?.postMessage(msg); } export function forkMod(data: ForkModData) { const msg: MessageRegular = { type: 'message', command: 'forkMod', data, }; vsCodeApi?.postMessage(msg); } export function showAdvancedDebugLogOutput() { const msg: MessageRegular = { type: 'message', command: 'showAdvancedDebugLogOutput', data: {}, }; vsCodeApi?.postMessage(msg); } export function showLogOutput() { const msg: MessageRegular = { type: 'message', command: 'showLogOutput', data: {}, }; vsCodeApi?.postMessage(msg); } export function getInitialSidebarParams() { const msg: MessageRegular = { type: 'message', command: 'getInitialSidebarParams', data: {}, }; vsCodeApi?.postMessage(msg); } export function stopCompileEditedMod() { const msg: MessageRegular = { type: 'message', command: 'stopCompileEditedMod', data: {}, }; vsCodeApi?.postMessage(msg); } export function previewEditedMod() { const msg: MessageRegular = { type: 'message', command: 'previewEditedMod', data: {}, }; vsCodeApi?.postMessage(msg); } //////////////////////////////////////////////////////////// // Messages with replies. let messageId = 0; function usePostMessageWithReplyWithHandler< TPostMessage extends Record, TReply, TContext extends Record >(eventName: string, handler: (data: TReply, context?: TContext) => void) { const [pendingMessageId, setPendingMessageId] = useState(); const [context, setContext] = useState(); const postMessage = useCallback( (data: TPostMessage, context?: TContext) => { messageId++; if (messageId > 0x7fffffff) { messageId = 1; } const message: MessageWithReply = { type: 'messageWithReply', command: eventName, data, messageId, }; vsCodeApi?.postMessage(message); setPendingMessageId(messageId); setContext(context); }, [eventName] ); useEventListener( 'message', useCallback( (message) => { const data = message.data as MessageAny; if (pendingMessageId === undefined) { return; } if ( data.type === 'reply' && data.command === eventName && data.messageId === pendingMessageId ) { handler(data.data as TReply, context); setPendingMessageId(undefined); setContext(undefined); } }, [context, eventName, handler, pendingMessageId] ) ); return { postMessage, pending: pendingMessageId !== undefined, context }; } export function useGetInitialAppSettings< TContext extends Record >(handler: (data: GetInitialAppSettingsReplyData, context?: TContext) => void) { const result = usePostMessageWithReplyWithHandler< NoData, GetInitialAppSettingsReplyData, TContext >('getInitialAppSettings', handler); return { getInitialAppSettings: result.postMessage, getInitialAppSettingsPending: result.pending, getInitialAppSettingsContext: result.context, }; } export function useInstallMod>( handler: (data: InstallModReplyData, context?: TContext) => void ) { const result = usePostMessageWithReplyWithHandler< InstallModData, InstallModReplyData, TContext >('installMod', handler); return { installMod: result.postMessage, installModPending: result.pending, installModContext: result.context, }; } export function useCompileMod>( handler: (data: CompileModReplyData, context?: TContext) => void ) { const result = usePostMessageWithReplyWithHandler< CompileModData, CompileModReplyData, TContext >('compileMod', handler); return { compileMod: result.postMessage, compileModPending: result.pending, compileModContext: result.context, }; } export function useEnableMod>( handler: (data: EnableModReplyData, context?: TContext) => void ) { const result = usePostMessageWithReplyWithHandler< EnableModData, EnableModReplyData, TContext >('enableMod', handler); return { enableMod: result.postMessage, enableModPending: result.pending, enableModContext: result.context, }; } export function useDeleteMod>( handler: (data: DeleteModReplyData, context?: TContext) => void ) { const result = usePostMessageWithReplyWithHandler< DeleteModData, DeleteModReplyData, TContext >('deleteMod', handler); return { deleteMod: result.postMessage, deleteModPending: result.pending, deleteModContext: result.context, }; } export function useUpdateModRating>( handler: (data: UpdateModRatingReplyData, context?: TContext) => void ) { const result = usePostMessageWithReplyWithHandler< UpdateModRatingData, UpdateModRatingReplyData, TContext >('updateModRating', handler); return { updateModRating: result.postMessage, updateModRatingPending: result.pending, updateModRatingContext: result.context, }; } export function useGetInstalledMods>( handler: (data: GetInstalledModsReplyData, context?: TContext) => void ) { const result = usePostMessageWithReplyWithHandler< NoData, GetInstalledModsReplyData, TContext >('getInstalledMods', handler); return { getInstalledMods: result.postMessage, getInstalledModsPending: result.pending, getInstalledModsContext: result.context, }; } export function useGetFeaturedMods>( handler: (data: GetFeaturedModsReplyData, context?: TContext) => void ) { const result = usePostMessageWithReplyWithHandler< NoData, GetFeaturedModsReplyData, TContext >('getFeaturedMods', handler); return { getFeaturedMods: result.postMessage, getFeaturedModsPending: result.pending, getFeaturedModsContext: result.context, }; } export function useGetModSourceData>( handler: (data: GetModSourceDataReplyData, context?: TContext) => void ) { const result = usePostMessageWithReplyWithHandler< GetModSourceDataData, GetModSourceDataReplyData, TContext >('getModSourceData', handler); return { getModSourceData: result.postMessage, getModSourceDataPending: result.pending, getModSourceDataContext: result.context, }; } export function useGetRepositoryModSourceData< TContext extends Record >( handler: ( data: GetRepositoryModSourceDataReplyData, context?: TContext ) => void ) { const result = usePostMessageWithReplyWithHandler< GetRepositoryModSourceDataData, GetRepositoryModSourceDataReplyData, TContext >('getRepositoryModSourceData', handler); return { getRepositoryModSourceData: result.postMessage, getRepositoryModSourceDataPending: result.pending, getRepositoryModSourceDataContext: result.context, }; } export function useGetModVersions>( handler: (data: GetModVersionsReplyData, context?: TContext) => void ) { const result = usePostMessageWithReplyWithHandler< GetModVersionsData, GetModVersionsReplyData, TContext >('getModVersions', handler); return { getModVersions: result.postMessage, getModVersionsPending: result.pending, getModVersionsContext: result.context, }; } export function useGetAppSettings>( handler: (data: GetAppSettingsReplyData, context?: TContext) => void ) { const result = usePostMessageWithReplyWithHandler< NoData, GetAppSettingsReplyData, TContext >('getAppSettings', handler); return { getAppSettings: result.postMessage, getAppSettingsPending: result.pending, getAppSettingsContext: result.context, }; } export function useUpdateAppSettings>( handler: (data: UpdateAppSettingsReplyData, context?: TContext) => void ) { const result = usePostMessageWithReplyWithHandler< UpdateAppSettingsData, UpdateAppSettingsReplyData, TContext >('updateAppSettings', handler); return { updateAppSettings: result.postMessage, updateAppSettingsPending: result.pending, updateAppSettingsContext: result.context, }; } export function useGetModSettings>( handler: (data: GetModSettingsReplyData, context?: TContext) => void ) { const result = usePostMessageWithReplyWithHandler< GetModSettingsData, GetModSettingsReplyData, TContext >('getModSettings', handler); return { getModSettings: result.postMessage, getModSettingsPending: result.pending, getModSettingsContext: result.context, }; } export function useSetModSettings>( handler: (data: SetModSettingsReplyData, context?: TContext) => void ) { const result = usePostMessageWithReplyWithHandler< SetModSettingsData, SetModSettingsReplyData, TContext >('setModSettings', handler); return { setModSettings: result.postMessage, setModSettingsPending: result.pending, setModSettingsContext: result.context, }; } export function useGetModConfig>( handler: (data: GetModConfigReplyData, context?: TContext) => void ) { const result = usePostMessageWithReplyWithHandler< GetModConfigData, GetModConfigReplyData, TContext >('getModConfig', handler); return { getModConfig: result.postMessage, getModConfigPending: result.pending, getModConfigContext: result.context, }; } export function useUpdateModConfig>( handler: (data: UpdateModConfigReplyData, context?: TContext) => void ) { const result = usePostMessageWithReplyWithHandler< UpdateModConfigData, UpdateModConfigReplyData, TContext >('updateModConfig', handler); return { updateModConfig: result.postMessage, updateModConfigPending: result.pending, updateModConfigContext: result.context, }; } export function useGetRepositoryMods>( handler: (data: GetRepositoryModsReplyData, context?: TContext) => void ) { const result = usePostMessageWithReplyWithHandler< NoData, GetRepositoryModsReplyData, TContext >('getRepositoryMods', handler); return { getRepositoryMods: result.postMessage, getRepositoryModsPending: result.pending, getRepositoryModsContext: result.context, }; } export function useStartUpdate>( handler: (data: StartUpdateReplyData, context?: TContext) => void ) { const result = usePostMessageWithReplyWithHandler< NoData, StartUpdateReplyData, TContext >('startUpdate', handler); return { startUpdate: result.postMessage, startUpdatePending: result.pending, startUpdateContext: result.context, }; } export function useCancelUpdate>( handler: (data: CancelUpdateReplyData, context?: TContext) => void ) { const result = usePostMessageWithReplyWithHandler< NoData, CancelUpdateReplyData, TContext >('cancelUpdate', handler); return { cancelUpdate: result.postMessage, cancelUpdatePending: result.pending, cancelUpdateContext: result.context, }; } export function useEnableEditedMod>( handler: (data: EnableEditedModReplyData, context?: TContext) => void ) { const result = usePostMessageWithReplyWithHandler< EnableEditedModData, EnableEditedModReplyData, TContext >('enableEditedMod', handler); return { enableEditedMod: result.postMessage, enableEditedModPending: result.pending, enableEditedModContext: result.context, }; } export function useEnableEditedModLogging< TContext extends Record >( handler: (data: EnableEditedModLoggingReplyData, context?: TContext) => void ) { const result = usePostMessageWithReplyWithHandler< EnableEditedModLoggingData, EnableEditedModLoggingReplyData, TContext >('enableEditedModLogging', handler); return { enableEditedModLogging: result.postMessage, enableEditedModLoggingPending: result.pending, enableEditedModLoggingContext: result.context, }; } export function useCompileEditedMod>( handler: (data: CompileEditedModReplyData, context?: TContext) => void ) { const result = usePostMessageWithReplyWithHandler< CompileEditedModData, CompileEditedModReplyData, TContext >('compileEditedMod', handler); return { compileEditedMod: result.postMessage, compileEditedModPending: result.pending, compileEditedModContext: result.context, }; } export function useExitEditorMode>( handler: (data: ExitEditorModeReplyData, context?: TContext) => void ) { const result = usePostMessageWithReplyWithHandler< ExitEditorModeData, ExitEditorModeReplyData, TContext >('exitEditorMode', handler); return { exitEditorMode: result.postMessage, exitEditorModePending: result.pending, exitEditorModeContext: result.context, }; } //////////////////////////////////////////////////////////// // Events. function useEventMessageWithHandler( eventName: string, handler: (data: T) => void ) { useEventListener( 'message', useCallback( (message) => { const data = message.data as MessageAny; if (data.type === 'event' && data.command === eventName) { handler(data.data as T); } }, [eventName, handler] ) ); } export function useSetNewAppSettings( handler: (data: SetNewAppSettingsData) => void ) { useEventMessageWithHandler( 'setNewAppSettings', handler ); } export function useUpdateDownloadProgress( handler: (data: UpdateDownloadProgressEventData) => void ) { useEventMessageWithHandler( 'updateDownloadProgress', handler ); } export function useUpdateInstalling( handler: (data: UpdateInstallingEventData) => void ) { useEventMessageWithHandler( 'updateInstalling', handler ); } export function useUpdateInstalledModsDetails( handler: (data: UpdateInstalledModsDetailsData) => void ) { useEventMessageWithHandler( 'updateInstalledModsDetails', handler ); } export function useSetNewModConfig( handler: (data: SetNewModConfigData) => void ) { useEventMessageWithHandler( 'setNewModConfig', handler ); } export function useSetEditedModId(handler: (data: SetEditedModIdData) => void) { useEventMessageWithHandler('setEditedModId', handler); } export function useCompileEditedModStart(handler: (data: NoData) => void) { useEventMessageWithHandler('compileEditedModStart', handler); } export function useEditedModWasModified(handler: (data: NoData) => void) { useEventMessageWithHandler('editedModWasModified', handler); } export function useSetEditedModDetails( handler: (data: SetEditedModDetailsData) => void ) { useEventMessageWithHandler( 'setEditedModDetails', handler ); } ================================================ FILE: src/vscode-windhawk-ui/apps/vscode-windhawk-ui/src/app/webviewIPCMessages.ts ================================================ // Message types: // * 'message' is a message from the webview to the extension. // * 'messageWithReply' is a message from the webview to the extension that expects a reply. // * 'reply' is a reply to a 'messageWithReply' message. // * 'event' is a message from the extension to the webview. export type webviewIPCMessageType = | 'message' | 'messageWithReply' | 'reply' | 'event'; export type webviewIPCMessageCommon = { type: webviewIPCMessageType; command: string; data: Record; }; export type webviewIPCMessage = webviewIPCMessageCommon & { type: 'message'; command: string; data: Record; }; export type webviewIPCMessageWithReply = webviewIPCMessageCommon & { type: 'messageWithReply'; command: string; data: Record; messageId: number; }; export type webviewIPCReply = webviewIPCMessageCommon & { type: 'reply'; command: string; data: Record; messageId: number; }; export type webviewIPCEvent = webviewIPCMessageCommon & { type: 'event'; command: string; data: Record; }; export type webviewIPCMessageAny = | webviewIPCMessage | webviewIPCMessageWithReply | webviewIPCReply | webviewIPCEvent; //////////////////////////////////////////////////////////// // Types. export type NoData = Record; export type ModConfig = { // libraryFileName: string; disabled: boolean; loggingEnabled: boolean; debugLoggingEnabled: boolean; include: string[]; exclude: string[]; includeCustom: string[]; excludeCustom: string[]; includeExcludeCustomOnly: boolean; patternsMatchCriticalSystemProcesses: boolean; architecture: string[]; version: string; }; export type AppSettings = { language: string; disableUpdateCheck: boolean; disableRunUIScheduledTask: boolean | null; devModeOptOut: boolean; devModeUsedAtLeastOnce: boolean; hideTrayIcon: boolean; alwaysCompileModsLocally: boolean; dontAutoShowToolkit: boolean; modTasksDialogDelay: number; safeMode: boolean; loggingVerbosity: number; engine: { loggingVerbosity: number; include: string[]; exclude: string[]; injectIntoCriticalProcesses: boolean; injectIntoIncompatiblePrograms: boolean; injectIntoGames: boolean; }; }; export type ModMetadata = Partial<{ version: string; // id: string; github: string; twitter: string; homepage: string; compilerOptions: string; license: string; donateUrl: string; name: string; description: string; author: string; include: string[]; exclude: string[]; architecture: string[]; }>; export type RepositoryDetails = { users: number; rating: number; // ratingUsers: number; ratingBreakdown: number[]; defaultSorting: number; published: number; updated: number; }; export type AppUISettings = { language: string; devModeOptOut: boolean; devModeUsedAtLeastOnce: boolean; loggingEnabled: boolean; updateIsAvailable: boolean; safeMode: boolean; }; export type InitialSettingsValue = | boolean | number | string | InitialSettings | InitialSettingsArrayValue; export type InitialSettingsArrayValue = number[] | string[] | InitialSettings[]; export type InitialSettingItem = { key: string; value: InitialSettingsValue; name?: string; description?: string; options?: Record[]; }; export type InitialSettings = InitialSettingItem[]; //////////////////////////////////////////////////////////// // Messages. export type EditModData = { modId: string; }; export type ForkModData = { modId: string; modSource?: string; }; //////////////////////////////////////////////////////////// // Messages with replies. export type GetInitialAppSettingsReplyData = { appUISettings: Partial; }; export type InstallModData = { modId: string; modSource: string; disabled?: boolean; }; export type InstallModReplyData = { modId: string; installedModDetails: { metadata: ModMetadata; config: ModConfig; } | null; }; export type CompileModData = { modId: string; disabled?: boolean; }; export type CompileModReplyData = { modId: string; compiledModDetails: { metadata: ModMetadata; config: ModConfig; } | null; }; export type EnableModData = { modId: string; enable: boolean; }; export type EnableModReplyData = { modId: string; enabled: boolean; succeeded: boolean; }; export type DeleteModData = { modId: string; }; export type DeleteModReplyData = { modId: string; succeeded: boolean; }; export type UpdateModRatingData = { modId: string; rating: number; }; export type UpdateModRatingReplyData = { modId: string; rating: number; succeeded: boolean; }; export type GetInstalledModsReplyData = { installedMods: Record< string, { metadata: ModMetadata | null; config: ModConfig | null; updateAvailable: boolean; userRating: number; } >; }; export type GetFeaturedModsReplyData = { featuredMods: Record< string, { metadata: ModMetadata; details: RepositoryDetails; } > | null; }; export type GetModSourceDataData = { modId: string; }; export type GetModSourceDataReplyData = { modId: string; data: { source: string | null; metadata: ModMetadata | null; readme: string | null; initialSettings: InitialSettings | null; }; }; export type GetRepositoryModSourceDataData = { modId: string; version?: string; }; export type GetRepositoryModSourceDataReplyData = { modId: string; version?: string; data: { source: string | null; metadata: ModMetadata | null; readme: string | null; initialSettings: InitialSettings | null; }; }; export type GetModVersionsData = { modId: string; }; export type GetModVersionsReplyData = { modId: string; versions: { version: string; timestamp: number; isPreRelease: boolean; }[]; }; export type GetAppSettingsReplyData = { appSettings: Partial; }; export type UpdateAppSettingsData = { appSettings: Partial; }; export type UpdateAppSettingsReplyData = { appSettings: Partial; succeeded: boolean; }; export type GetModSettingsData = { modId: string; }; export type GetModSettingsReplyData = { modId: string; settings: Record; }; export type SetModSettingsData = { modId: string; settings: Record; }; export type SetModSettingsReplyData = { modId: string; succeeded: boolean; }; export type GetModConfigData = { modId: string; }; export type GetModConfigReplyData = { modId: string; config: ModConfig | null; }; export type UpdateModConfigData = { modId: string; config: Partial; }; export type UpdateModConfigReplyData = { modId: string; succeeded: boolean; }; export type GetRepositoryModsReplyData = { mods: Record< string, { repository: { metadata: ModMetadata; details: RepositoryDetails; featured?: boolean; }; installed?: { metadata: ModMetadata | null; config: ModConfig | null; userRating: number; }; } > | null; }; export type StartUpdateReplyData = { succeeded: boolean; error?: string; }; export type CancelUpdateReplyData = { succeeded: boolean; }; export type EnableEditedModData = { enable: boolean; }; export type EnableEditedModReplyData = { enabled: boolean; succeeded: boolean; }; export type EnableEditedModLoggingData = { enable: boolean; }; export type EnableEditedModLoggingReplyData = { enabled: boolean; succeeded: boolean; }; export type CompileEditedModData = { disabled: boolean; loggingEnabled: boolean; }; export type CompileEditedModReplyData = { succeeded: boolean; clearModified: boolean; }; export type ExitEditorModeData = { saveToDrafts: boolean; }; export type ExitEditorModeReplyData = { succeeded: boolean; }; //////////////////////////////////////////////////////////// // Events. export type SetNewAppSettingsData = { appUISettings: Partial; }; export type UpdateDownloadProgressEventData = { progress: number; // 0-100 }; export type UpdateInstallingEventData = NoData; export type UpdateInstalledModsDetailsData = { details: Record< string, { updateAvailable: boolean; userRating: number; } >; }; export type SetNewModConfigData = { modId: string, config: Partial }; export type SetEditedModIdData = { modId: string; }; export type SetEditedModDetailsData = { modId: string; modDetails: ModConfig | null; modWasModified: boolean; }; ================================================ FILE: src/vscode-windhawk-ui/apps/vscode-windhawk-ui/src/environments/environment.prod.ts ================================================ export const environment = { production: true, }; ================================================ FILE: src/vscode-windhawk-ui/apps/vscode-windhawk-ui/src/environments/environment.ts ================================================ // This file can be replaced during build by using the `fileReplacements` array. // When building for production, this file is replaced with `environment.prod.ts`. export const environment = { production: false, }; ================================================ FILE: src/vscode-windhawk-ui/apps/vscode-windhawk-ui/src/index.html ================================================ Windhawk
    ================================================ FILE: src/vscode-windhawk-ui/apps/vscode-windhawk-ui/src/locales/DO_NOT_EDIT.txt ================================================ The files in this folder are generated automatically. To submit a translation update, please refer to this repository: https://github.com/ramensoftware/windhawk-translate ================================================ FILE: src/vscode-windhawk-ui/apps/vscode-windhawk-ui/src/locales/ar/DO_NOT_EDIT.txt ================================================ The files in this folder are generated automatically. To submit a translation update, please refer to this repository: https://github.com/ramensoftware/windhawk-translate ================================================ FILE: src/vscode-windhawk-ui/apps/vscode-windhawk-ui/src/locales/ar/translation.json ================================================ { "general": { "loading": "جار التحميل...", "loadingFailed": "فشل التحميل، يرجى التحقق من اتصال الإنترنت", "loadingFailedTitle": "فشل التحميل", "loadingFailedSubtitle": "يرجى التحقق من اتصال الإنترنت والمحاولة مرة أخرى", "tryAgain": "حاول مرة أخرى", "updating": "جار التحديث...", "installing": "جار التثبيت...", "compiling": "جار التجميع...", "cut": "قص", "copy": "نسخ", "paste": "لصق", "selectAll": "تحديد الكل" }, "appHeader": { "home": "الرئيسية", "explore": "استكشاف", "settings": "الإعدادات", "about": "حول" }, "mod": { "updateAvailable": "تحديث متاح", "installed": "مثبت", "details": "تفاصيل", "update": "تحديث", "install": "تثبيت", "compile": "تجميع", "disable": "تعطيل", "enable": "تمكين", "edit": "تعديل", "fork": "تفرع", "remove": "إزالة", "removeConfirm": "هل أنت متأكد أنك تريد إزالة هذا المود؟", "removeConfirmOk": "إزالة المود", "removeConfirmCancel": "إلغاء", "notCompiled": "يجب تجميع المود", "editedLocally": "تم تعديل المود محليًا", "noDescription": "(لا يوجد وصف)", "users_zero": "لا يوجد مستخدمون", "users_one": "{{formattedCount}} مستخدم", "users_two": "{{formattedCount}} مستخدمان", "users_few": "{{formattedCount}} مستخدمون", "users_many": "{{formattedCount}} مستخدم", "users_other": "{{formattedCount}} مستخدم" }, "modDetails": { "header": { "installedVersion": "الإصدار المثبت", "latestVersion": "أحدث إصدار", "loading": "جار التحميل...", "loadingFailed": "فشل التحميل", "modId": "معرف المود", "modVersion": "إصدار المود", "modAuthor": { "title": "مؤلف المود", "homepage": "الصفحة الرئيسية", "github": "GitHub", "twitter": "X (تويتر)" }, "processes": { "all": "جميع العمليات", "allBut": "جميع ما عدا {{list}}", "except": "{{included}} باستثناء {{excluded}}", "tooltip": { "targets": "العمليات المستهدفة", "excluded": "مستبعد" } }, "updateNotNeeded": "الإصدار المثبت مطابق لأحدث إصدار" }, "details": { "title": "تفاصيل", "noData": "تفاصيل المود مفقودة." }, "settings": { "title": "الإعدادات", "noData": "لا توجد إعدادات متاحة للمود.", "saveButton": "حفظ الإعدادات", "sampleValue": "قيمة نموذجية", "arrayItemAdd": "إضافة عنصر جديد", "arrayItemRemove": "إزالة عنصر" }, "code": { "title": "كود المصدر", "noData": "مصدر المود مفقود.", "collapseExtra": "طي ملف Readme والإعدادات" }, "changelog": { "title": "سجل التغييرات", "loadingFailed": "فشل تحميل سجل التغييرات. <0>انقر هنا لعرضه على GitHub." }, "advanced": { "title": "متقدم", "debugLogging": { "title": "تسجيل التصحيح", "description": "يمكن أن يكون مفيدًا لاستكشاف مشكلات المود.", "none": "لا شيء", "modLogs": "سجلات المود", "detailedLogs": "سجلات تصحيح مفصلة", "showLogButton": "عرض سجل الإخراج" }, "modSettings": { "title": "إعدادات المود", "description": "تصدير أو مشاركة إعدادات المود بسهولة.", "loadButton": "تحميل", "loadFormattedButton": "تحميل منسق", "saveButton": "حفظ", "invalidData": "بيانات JSON غير صالحة" }, "customList": { "titleInclusion": "قائمة تضمين العمليات المخصصة", "descriptionInclusion": "قائمة مخصصة من أسماء/مسارات الملفات التنفيذية الإضافية التي سيستهدفها المود. تضاف القائمة إلى قائمة التضمين التي حددها مؤلف المود لتحديد العمليات المستهدفة. لكل عملية، يتم تحميل المود إذا كان مسار الملف التنفيذي يطابق أحد عناصر التضمين ولا يطابق أي عنصر استبعاد.", "titleExclusion": "قائمة استبعاد العمليات المخصصة", "descriptionExclusion": "قائمة مخصصة من أسماء/مسارات الملفات التنفيذية الإضافية التي سيتم استبعادها من الاستهداف. تضاف القائمة إلى قائمة الاستبعاد التي حددها مؤلف المود لتحديد العمليات المستهدفة. لكل عملية، يتم تحميل المود إذا كان مسار الملف التنفيذي يطابق أحد عناصر التضمين ولا يطابق أي عنصر استبعاد.", "processListPlaceholder": "أسماء/مسارات العمليات، كل واحد في سطر، على سبيل المثال:", "invalidCharactersWarning": "تحتوي قائمة العمليات على أحرف غير صالحة سيتم حذفها: {{invalidCharacters}}", "saveButton": "حفظ" }, "includeExcludeCustomOnly": { "title": "تجاهل قوائم التضمين/الاستبعاد الخاصة بالمود", "description": "تجاهل قوائم التضمين/الاستبعاد الخاصة بالمود واستخدم فقط القوائم المخصصة أعلاه." }, "patternsMatchCriticalSystemProcesses": { "title": "اعتبر أنماط قائمة التضمين للعمليات النظامية الحرجة", "description": "افتراضيًا، يقوم Windhawk بتحميل المودات في العمليات النظامية الحرجة فقط إذا تم تضمين العملية بدون أنماط، مثل <0>critical.exe، وليس <0>* أو <0>*.exe. لمزيد من التفاصيل حول العمليات النظامية الحرجة، يرجى الرجوع إلى <1>التوثيق." } }, "changes": { "title": "تغييرات أحدث إصدار", "noData": "الإصدار المثبت مطابق لأحدث إصدار.", "splitView": "عرض مقسم", "expandLines_zero": "لا توجد أسطر مخفية", "expandLines_one": "توسيع سطر مخفي واحد", "expandLines_two": "توسيع سطرين مخفيين", "expandLines_few": "توسيع {{count}} أسطر مخفية", "expandLines_many": "توسيع {{count}} سطراً مخفياً", "expandLines_other": "توسيع {{count}} سطر مخفي" } }, "modSearch": { "placeholder": "ابحث عن المودات..." }, "home": { "browse": "تصفح المودات", "installedMods": { "title": "المودات المثبتة", "noMods": "لا توجد مودات مثبتة" }, "featuredMods": { "title": "مودات مميزة", "noMods": "لا توجد مودات مميزة لم يتم تثبيتها بعد", "explore": "استكشاف مودات أخرى" } }, "explore": { "search": { "popularAndTopRated": "الأكثر شهرة والأعلى تقييماً", "popular": "الأكثر شهرة", "topRated": "الأعلى تقييماً", "newest": "الأحدث", "lastUpdated": "آخر تحديث", "alphabeticalOrder": "ترتيب أبجدي" } }, "settings": { "language": { "title": "اللغة", "description": "اختر لغة العرض المفضلة لديك لـ Windhawk.", "contribute": "<0>ساهم بترجمة جديدة.", "credits": "الترجمة العربية بواسطة <0>عبد الحق العمراوي.", "creditsLink": "mailto:abdelhaq1080@gmail.com" }, "updates": { "title": "التحقق من التحديثات", "description": "التحقق بشكل دوري من وجود إصدارات جديدة من Windhawk والمودات المثبتة." }, "devMode": { "title": "وضع المطور", "description": "عرض إجراءات المطورين مثل إنشاء وتعديل المودات." }, "advancedSettings": "إعدادات متقدمة", "hideTrayIcon": { "title": "إخفاء أيقونة الدرج", "description": "ستحتاج إلى تعطيل هذا الخيار للخروج من Windhawk." }, "requireElevation": { "title": "طلب صلاحيات المسؤول لتشغيل Windhawk", "description": "يتطلب Windhawk حقوق المسؤول، ولكن بالنسبة لجهاز مستخدم واحد، قد يكون ظهور مطالبة UAC في كل مرة مزعجًا، لذلك يتجاوز Windhawk ذلك. فعّل هذا الخيار لطلب صلاحيات المسؤول لتشغيل Windhawk." }, "dontAutoShowToolkit": { "title": "عدم إظهار مربع أدوات المجموعة تلقائيًا", "description": "افتراضيًا، يعرض Windhawk مربع أدوات المجموعة تلقائيًا عند اكتشاف أن شريط المهام غير متاح، إما بسبب عدم استقرار النظام أو لأي سبب آخر. يتيح مربع الأدوات الخروج من Windhawk، أو التبديل إلى الوضع الآمن، أو إجراء عمليات أخرى. يمكن أيضًا عرض مربع الأدوات باستخدام اختصار لوحة المفاتيح Ctrl+Win+W." }, "modInitDialogDelay": { "title": "تأخير مربع حوار تهيئة المود", "description": "عدد المللي ثانية للانتظار قبل عرض مربع تقدم تهيئة المود." }, "moreAdvancedSettings": { "title": "إعدادات أكثر تقدمًا", "restartNotice": "سيتم إعادة تشغيل Windhawk لتطبيق الإعدادات.", "saveButton": "حفظ وإعادة تشغيل Windhawk", "cancelButton": "إلغاء" }, "loggingVerbosity": { "appLoggingTitle": "مستوى تفصيل سجلات Windhawk", "engineLoggingTitle": "مستوى تفصيل سجلات محرك Windhawk", "description": "يمكن عرض السجلات باستخدام أداة مثل DebugView.", "none": "لا شيء", "error": "خطأ", "verbose": "مفصل" }, "processList": { "titleExclusion": "قائمة استبعاد العمليات", "descriptionExclusion": "قائمة بأسماء/مسارات العمليات التي لن يقوم Windhawk بحقنها. يمكن أن يكون ذلك مفيدًا في الحالات التي لا يكون فيها Windhawk متوافقًا مع برنامج معين. لاحظ أن إضافة عملية إلى هذه القائمة لن يمنع فقط تخصيص Windhawk لها، بل سيمنع أيضًا Windhawk من اعتراض العمليات الفرعية التي تنشئها هذه العملية. سيؤدي ذلك إلى تحميل المودات مع تأخير طفيف في مثل هذه الحالات.", "descriptionExclusionWiki": "لمزيد من التفاصيل حول استبعاد العمليات وقوائم الاستبعاد المدمجة، يرجى الرجوع إلى <0>التوثيق.", "excludeCriticalProcesses": "استبعاد العمليات النظامية الحرجة", "excludeIncompatiblePrograms": "استبعاد البرامج غير المتوافقة المعروفة", "excludeGames": "استبعاد الألعاب المعروفة", "titleInclusion": "قائمة تضمين العمليات", "descriptionInclusion": "قائمة بأسماء/مسارات العمليات التي سيقوم Windhawk بحقنها، حتى لو كانت في قائمة الاستبعاد.", "inclusionWithoutExclusionNotice": "قائمة تضمين العمليات ليس لها تأثير مع قائمة استبعاد العمليات الفارغة.", "inclusionWithoutTotalExclusionNotice": "إذا كنت تقصد استبعاد جميع العمليات ما عدا هذه، يمكنك تعيين \"*\" في قائمة استبعاد العمليات.", "processListPlaceholder": "أسماء/مسارات العمليات، كل واحد في سطر، على سبيل المثال:", "invalidCharactersWarning": "تحتوي قائمة العمليات على أحرف غير صالحة سيتم حذفها: {{invalidCharacters}}" } }, "about": { "title": "Windhawk v{{version}}", "beta": "بيتا", "subtitle": "سوق التخصيص لنظام ويندوز والبرامج", "credit": "بواسطة <0>{{author}}", "update": { "title": "يتوفر تحديث", "subtitle": "فكر في تحديث Windhawk للحصول على أحدث الميزات وإصلاحات الأخطاء", "updateButton": "مزيد من التفاصيل" }, "links": { "title": "روابط", "homepage": "الصفحة الرئيسية", "documentation": "التوثيق" }, "builtWith": { "title": "بُني باستخدام", "vscodium": "توزيعة مجتمع من محرر VSCode من مايكروسوفت", "llvmMingw": "مجموعة أدوات mingw-w64 مبنية على LLVM/Clang/LLD", "minHook": "مكتبة API Hooking بسيطة لنظام ويندوز", "others": "أدوات ومكتبات أخرى، وقليل من الشيفرة" } }, "installModal": { "title": "تثبيت {{mod}}", "warningTitle": "تابع بحذر", "warningDescription": "يمكن أن تتسبب المودات الضارة في إتلاف جهاز الكمبيوتر أو انتهاك خصوصيتك. قم بتثبيت المودات فقط من المؤلفين الذين تثق بهم.", "modAuthor": "مؤلف المود", "homepage": "الصفحة الرئيسية", "github": "GitHub", "twitter": "X (تويتر)", "verified": "تم التحقق", "verifiedTooltip": "لقد تحققنا من أن هذا الملف الشخصي يخص مؤلف المود، ولكن لاحظ أن <0>تم التحقق لا تعني <0>موثوق. تأكد من أنك تثق بمؤلف المود، أو تحقق بعناية من الشيفرة المصدرية قبل التثبيت.", "acceptButton": "قبول المخاطرة والتثبيت", "cancelButton": "إلغاء" }, "createNewModButton": { "title": "إنشاء مود جديد" }, "devModeAction": { "message": "يتطلب إنشاء وتعديل المودات بعض المعرفة بتطوير C/C++ لنظام ويندوز. إذا لم تكن متأكدًا مما يعنيه ذلك، ربما لا تكون هذه الخيارات مناسبة لك.", "hideOptionsCheckbox": "إخفاء جميع الخيارات المتعلقة بالتطوير", "hideOptionsButton": "إخفاء الخيارات", "beginCodingButton": "ابدأ البرمجة", "cancelButton": "إلغاء" }, "safeMode": { "alert": "يعمل Windhawk في الوضع الآمن، تم إيقاف جميع وظائف حقن الشيفرة.", "offButton": "إيقاف الوضع الآمن", "offConfirm": "سيتم إعادة تشغيل Windhawk لتطبيق الإعدادات.", "offConfirmOk": "إعادة تشغيل Windhawk", "offConfirmCancel": "إلغاء" }, "modPreview": { "actionUnavailable": "الإجراء غير متاح في وضع المعاينة", "notCompiled": "يجب تجميع المود قبل معاينته" }, "sidebar": { "modId": "معرف المود", "enableMod": "تمكين المود", "enableLogging": "تمكين السجلات", "notCompiled": "يجب تجميع المود", "compile": "تجميع المود", "compilationFailed": "فشل التجميع", "preview": "معاينة المود", "showLogOutput": "عرض سجل الإخراج", "exit": "الخروج من وضع التحرير", "exitConfirmation": "سيتم فقدان التغييرات منذ آخر تجميع ناجح", "exitButtonOk": "خروج", "exitButtonCancel": "البقاء" } } ================================================ FILE: src/vscode-windhawk-ui/apps/vscode-windhawk-ui/src/locales/cs/DO_NOT_EDIT.txt ================================================ The files in this folder are generated automatically. To submit a translation update, please refer to this repository: https://github.com/ramensoftware/windhawk-translate ================================================ FILE: src/vscode-windhawk-ui/apps/vscode-windhawk-ui/src/locales/cs/translation.json ================================================ { "general": { "loading": "Načítání...", "loadingFailed": "Stahování se nezdařilo, zkontrolujte prosím své připojení k internetu", "loadingFailedTitle": "Načítání se nezdařilo", "loadingFailedSubtitle": "Zkontrolujte prosím své připojení k internetu a zkuste to znovu", "tryAgain": "Zkuste to znovu", "updating": "Aktualizuji...", "installing": "Instaluji...", "compiling": "Kompiluji...", "cut": "Vystřihnout", "copy": "Kopírovat", "paste": "vložit", "selectAll": "Vybrat vše" }, "appHeader": { "home": "Hlavní", "explore": "Hledat", "settings": "Parametry", "about": "O programu" }, "mod": { "updateAvailable": "K dispozici aktualizace", "installed": "Instalováno", "details": "Popis", "update": "Aktualizovat", "install": "Instalovat", "compile": "kompilovat", "disable": "Zakázat", "enable": "Povolit", "edit": "Upravit", "fork": "Vytvořit kopii", "remove": "smazat", "removeConfirm": "Opravdu chcete odstranit tuto úpravu?", "removeConfirmOk": "Odstranit úpravu", "removeConfirmCancel": "Zrušit", "notCompiled": "Modifikace musí být zkompilována", "editedLocally": "Úprava upravena místně", "noDescription": "(žádný popis)", "users_one": "{{formattedCount}} uživatel", "users_few": "{{formattedCount}} uživatelé", "users_many": "{{formattedCount}} uživatelů", "users_other": "{{formattedCount}} uživatelů" }, "modDetails": { "header": { "installedVersion": "Instalovaná verze", "latestVersion": "Nejnovější verze", "loading": "načítání...", "loadingFailed": "načítání se nezdařilo", "modId": "ID modifikace", "modVersion": "Verze modifikace", "modAuthor": { "title": "Autor úpravy", "homepage": "Domovská stránka", "github": "GitHub", "twitter": "X (Twitter)" }, "processes": { "all": "Všechny procesy", "allBut": "Vše kromě {{list}}", "except": "{{included}} kromě {{excluded}}", "tooltip": { "targets": "Cílové procesy", "excluded": "Vyloučeno" } }, "updateNotNeeded": "Je nainstalována nejnovější verze" }, "details": { "title": "Popis", "noData": "Chybí popis úpravy." }, "settings": { "title": "Nastavení", "noData": "Není k dispozici žádná nastavení úprav.", "saveButton": "Uložit nastavení", "sampleValue": "Standardní hodnota", "arrayItemAdd": "Přidat novou položku", "arrayItemRemove": "Odstranit položku" }, "code": { "title": "Zdrojový kód", "noData": "Chybí zdrojový kód.", "collapseExtra": "Skrýt nastavení a soubor Readme" }, "changelog": { "title": "Seznam změn", "loadingFailed": "Nelze načíst protokol změn. Kliknutím <0>sem jej zobrazíte na GitHubu." }, "advanced": { "title": "Další", "debugLogging": { "title": "Ladění protokolování", "description": "Lze použít k řešení problémů s úpravou.", "none": "Ne", "modLogs": "Protokoly úprav", "detailedLogs": "Podrobné protokoly ladění", "showLogButton": "Zobrazit protokoly" }, "modSettings": { "title": "Nastavení úprav", "description": "Snadno exportujte nebo sdílejte nastavení modu.", "loadButton": "Načíst", "loadFormattedButton": "Načíst formátované", "saveButton": "Uložit", "invalidData": "Neplatná data JSON" }, "customList": { "titleInclusion": "Vlastní seznam cílových procesů", "descriptionInclusion": "Vlastní seznam dalších exe souborů nebo cest k nim, které budou ovlivněny úpravou. Tento seznam bude přidán do seznamu ovlivněných procesů, které definoval autor modifikace. To znamená, že úprava se načte pro každý proces pouze v případě, že cesta odpovídá jednomu z cílového seznamu a neodpovídá žádnému z vyloučených.", "titleExclusion": "Vlastní seznam vyloučení", "descriptionExclusion": "Vlastní seznam dalších exe souborů nebo cest k nim, které modifikace NEovlivní. Tento seznam bude přidán do seznamu vyloučení od autora modifikace. To znamená, že modifikace se načte pro každý proces pouze v případě, že cesta odpovídá jednomu z cílového seznamu a neodpovídá žádnému ze seznamu vyloučených.", "processListPlaceholder": "Název/cesty procesu (exe), jedna na řádek, např.:", "invalidCharactersWarning": "Seznam procesů obsahuje neplatné znaky, které budou odstraněny: {{invalidCharacters}}", "saveButton": "Uložit" }, "includeExcludeCustomOnly": { "title": "Ignorovat cílové/vyloučit seznamy úprav", "description": "Ignorujte seznamy cílů/vylučujících seznamy nastavené v modu a používejte pouze vlastní seznamy výše." }, "patternsMatchCriticalSystemProcesses": { "title": "Zvažte vzory seznamu zahrnutí pro kritické systémové procesy", "description": "Ve výchozím nastavení Windhawk načítá mody pouze v kritických systémových procesech, pokud je proces zahrnut bez vzorů, např. <0>critical.exe, nikoli <0>* nebo <0>*.exe. Další podrobnosti o kritických systémových procesech naleznete v <1>dokumentace." } }, "changes": { "title": "Změny v nejnovější verzi", "noData": "Je nainstalována nejnovější verze.", "splitView": "Oddělené prohlížení", "expandLines_one": "Rozbalit jeden skrytý řádek", "expandLines_few": "Rozbalit {{count}} skryté řádky", "expandLines_many": "Rozbalit {{count}} skrytých řádků", "expandLines_other": "Rozbalit {{count}} skrytých řádků" } }, "modSearch": { "placeholder": "Hledat úpravy..." }, "home": { "browse": "Vyhledat úpravy", "installedMods": { "title": "Instalované úpravy", "noMods": "Není nainstalovány žádné mody" }, "featuredMods": { "title": "Doporučené úpravy", "noMods": "Neexistují žádné doporučené úpravy, které ještě nejsou nainstalovány", "explore": "Vyhledat další úpravy" } }, "explore": { "search": { "popularAndTopRated": "Populární a nejlépe hodnocené", "popular": "Populární", "topRated": "Nejlépe hodnocené", "newest": "Nové", "lastUpdated": "Nedávno aktualizováno", "alphabeticalOrder": "Abecední pořadí" } }, "settings": { "language": { "title": "Jazyk", "description": "Vyberte požadovaný jazyk rozhraní pro Windhawk.", "contribute": "<0>Přidat nový překlad.", "credits": "Český překlad <0>Milan Kliment s několika úpravami od Berry a dalších členů komunity.", "creditsLink": "mailto:milan-k@volny.cz" }, "updates": { "title": "Zkontrolovat aktualizace", "description": "Pravidelně kontrolujte aktualizace Windhawk a nainstalované modifikace." }, "devMode": { "title": "Režim pro vývojáře", "description": "Zobrazit akce vývojáře, jako je vytváření a úpravy modů." }, "advancedSettings": "Pokročilá nastavení", "hideTrayIcon": { "title": "Skrýt ikonu na liště", "description": "Pro ukončení Windhawk musíte toto nastavení zakázat." }, "requireElevation": { "title": "Vyžadovat UAC ke spuštění Windhawk", "description": "Windhawk vyžaduje administrátorská práva, ale pro jednouživatelské počítače jsou neustálé výzvy UAC otravné, takže je Windhawk obchází. Povolte toto nastavení, chcete-li vrátit výzvy UAC ke spuštění Windhawk." }, "dontAutoShowToolkit": { "title": "Nezobrazovat dialogové okno nabídky automaticky", "description": "Ve výchozím nastavení Windhawk automaticky zobrazí dialogové okno nabídky, když zjistí, že hlavní panel nefunguje kvůli nestabilitě systému nebo z jiných důvodů. Dialog nabídky umožňuje zavřít Windhawk, vstoupit do nouzového režimu a provést další akce. Dialogové okno nabídky lze zobrazit také stisknutím kombinace kláves Ctrl+Win+W." }, "modInitDialogDelay": { "title": "Zpoždění dialogu inicializace úprav", "description": "Počet milisekund, které se čeká před zobrazením dialogu průběhu inicializace modifikace." }, "moreAdvancedSettings": { "title": "Další pokročilé možnosti", "restartNotice": "Windhawk bude restartován, aby bylo možné použít nastavení.", "saveButton": "Uložit a restartovat Windhawk", "cancelButton": "Zrušit" }, "loggingVerbosity": { "appLoggingTitle": "Windhawk Verbose Logging", "engineLoggingTitle": "Podrobné protokolování motoru Windhawk", "description": "Protokoly lze prohlížet například pomocí nástroje DebugView.", "none": "Ne", "error": "Chyba", "verbose": "Podrobně" }, "processList": { "titleExclusion": "Seznam vyloučených procesů", "descriptionExclusion": "Seznam procesů/cest, do kterých Windhawk nevloží kód. To může být užitečné v případech, kdy Windhawk není kompatibilní s konkrétním programem. Upozorňujeme, že přidání procesu do tohoto seznamu nejen zabrání Windhawku v jeho úpravě, ale také zabrání Windhawku v zachycení podřízených procesů vytvořených vyloučeným procesem. To způsobí zpoždění při načítání úprav do Windhaw.", "descriptionExclusionWiki": "Další podrobnosti o vyloučení procesů a o integrovaných seznamech vyloučení procesů naleznete v <0>dokumentace.", "excludeCriticalProcesses": "Vyloučit kritické systémové procesy", "excludeIncompatiblePrograms": "Vyloučit známé nekompatibilní programy", "excludeGames": "Vyloučit známé hry", "titleInclusion": "Seznam cílových procesů", "descriptionInclusion": "Seznam cílových procesů/cest, do kterých Windhawk vloží kód, i když jsou na seznamu vyloučených.", "inclusionWithoutExclusionNotice": "Seznam cílových procesů nedělá nic, pokud je seznam vyloučených procesů prázdný.", "inclusionWithoutTotalExclusionNotice": "Pokud chcete vyloučit všechny procesy kromě těchto, můžete zadat \"*\" v seznamu vyloučených procesů.", "processListPlaceholder": "Název/cesty procesu (exe), jedna na řádek, např.:", "invalidCharactersWarning": "Seznam procesů obsahuje neplatné znaky, které budou odstraněny: {{invalidCharacters}}" } }, "about": { "title": "Windhawk ver. {{version}}", "beta": "beta", "subtitle": "Tržiště pro přizpůsobení programů Windows", "credit": "Autor: <0>{{author}}", "update": { "title": "K dispozici aktualizace", "subtitle": "Aktualizujte Windhawk pro nejnovější funkce a opravy chyb", "updateButton": "Další podrobnosti" }, "links": { "title": "Odkazy", "homepage": "Domovská stránka", "documentation": "Dokumentace" }, "builtWith": { "title": "Zkompilováno s", "vscodium": "Komunitní verze editoru VSCode od společnosti Microsoft", "llvmMingw": "LLVM/Clang/LLD založené na prostředí mingw-w64", "minHook": "Minimalistická knihovna pro připojení API pro Windows", "others": "Další nástroje a knihovny a nějaký psaný kód" } }, "installModal": { "title": "Instalovat {{mod}}", "warningTitle": "Postupujte opatrně", "warningDescription": "Škodlivé úpravy mohou poškodit váš počítač a narušit vaše soukromí. Instalujte úpravy pouze od autorů, kterým důvěřujete.", "modAuthor": "Autor úpravy", "homepage": "Domovská stránka", "github": "GitHub", "twitter": "X (Twitter)", "verified": "ověřeno", "verifiedTooltip": "Potvrzujeme, že tento profil patří autorovi modu, ale mějte na paměti, že <0>ověřený neznamená <0>důvěryhodný. Ujistěte se, že důvěřujete autorovi modu, nebo před instalací pečlivě zkontrolujte zdrojový kód.", "acceptButton": "Přijměte riziko a pokračujte", "cancelButton": "Zrušit" }, "createNewModButton": { "title": "Vytvořit novou úpravu" }, "devModeAction": { "message": "Vytváření a úpravy modů vyžaduje určitou znalost C/C++ ve Windows. Pokud nevíte, o čem mluvím, tyto možnosti nemusí být pro vás.", "hideOptionsCheckbox": "Skrýt všechny možnosti vývojáře", "hideOptionsButton": "Skrýt možnosti", "beginCodingButton": "Začít kódovat", "cancelButton": "Zrušit" }, "safeMode": { "alert": "Windhawk běží v nouzovém režimu, přizpůsobení programu je zakázáno.", "offButton": "Vypnout nouzový režim", "offConfirm": "Windhawk bude restartován pro použití nastavení.", "offConfirmOk": "Restartovat Windhawk", "offConfirmCancel": "Zrušit" }, "modPreview": { "actionUnavailable": "Akce není dostupná v režimu zobrazení", "notCompiled": "Abyste si ji mohli prohlédnout, musíte úpravu zkompilovat" }, "sidebar": { "modId": "ID modifikace", "enableMod": "Povolit úpravy", "enableLogging": "Povolit protokolování", "notCompiled": "je třeba zkompilovat úpravu", "compile": "Zkompilovat modifikaci", "compilationFailed": "Kompilace se nezdařila", "preview": "Zobrazit modifikaci", "showLogOutput": "Zobrazit protokoly", "exit": "Ukončit režim úprav", "exitConfirmation": "Změny od poslední úspěšné kompilace budou ztraceny", "exitButtonOk": "Konec", "exitButtonCancel": "Zůstat" } } ================================================ FILE: src/vscode-windhawk-ui/apps/vscode-windhawk-ui/src/locales/da/DO_NOT_EDIT.txt ================================================ The files in this folder are generated automatically. To submit a translation update, please refer to this repository: https://github.com/ramensoftware/windhawk-translate ================================================ FILE: src/vscode-windhawk-ui/apps/vscode-windhawk-ui/src/locales/da/translation.json ================================================ { "general": { "loading": "Indlæser...", "loadingFailed": "Indlæsning fejlede, tjek din internetforbindelse", "loadingFailedTitle": "Indlæsning fejlede", "loadingFailedSubtitle": "Tjek venligst din internetforbindelse og prøv igen", "tryAgain": "Prøv igen", "updating": "Opdaterer...", "installing": "Installerer...", "compiling": "Kompilerer...", "cut": "Klip", "copy": "Kopier", "paste": "Indsæt", "selectAll": "Vælg alt" }, "appHeader": { "home": "Hjem", "explore": "Udforsk", "settings": "Indstillinger", "about": "Om" }, "mod": { "updateAvailable": "Opdatering tilgængelig", "installed": "Installeret", "details": "Detaljer", "update": "Opdater", "install": "Installer", "compile": "Kompiler", "disable": "Slå fra", "enable": "Slå til", "edit": "Rediger", "fork": "Fork", "remove": "Fjern", "removeConfirm": "Er du sikker på at du vil fjerne denne modifikation?", "removeConfirmOk": "Fjern modifikation", "removeConfirmCancel": "Annuller", "notCompiled": "Modifikation skal kompileres", "editedLocally": "Modifikation var redigeret lokalt", "noDescription": "(ingen beskrivelse)", "users_one": "{{formattedCount}} bruger", "users_other": "{{formattedCount}} brugers" }, "modDetails": { "header": { "installedVersion": "Installeret version", "latestVersion": "Seneste version", "loading": "indlæser...", "loadingFailed": "indlæsning fejlede", "modId": "Modfikations-identifikator", "modVersion": "Modifikations-version", "modAuthor": { "title": "Modifikations-forfatter", "homepage": "Forside", "github": "GitHub", "twitter": "X (Twitter)" }, "processes": { "all": "Alle processer", "allBut": "Alle undtagen {{list}}", "except": "{{included}} undtagen {{excluded}}", "tooltip": { "targets": "Målprocesser", "excluded": "Ekskluderede" } }, "updateNotNeeded": "Den installerede version er identisk med den seneste version" }, "details": { "title": "Detaljer", "noData": "Modifikationsdetaljer mangler." }, "settings": { "title": "Indstillinger", "noData": "Ingen modifikationsindstillinger er tilgængelige.", "saveButton": "Gem indstillinger", "sampleValue": "Eksempelværdi", "arrayItemAdd": "Tilføj ny genstand", "arrayItemRemove": "Fjern genstand" }, "code": { "title": "Kildekode", "noData": "Modifikationskilde mangler.", "collapseExtra": "Kollaps Readme og Indstillinger" }, "changelog": { "title": "Ændringslog", "loadingFailed": "Indlæsning af ændringslog fejlede. <0>Klik her for at vise den på GitHub." }, "advanced": { "title": "Avanceret", "debugLogging": { "title": "Fejlfindingslogning", "description": "Kan være brugbart til at fejlfinde problemer med modifikationen.", "none": "Ingen", "modLogs": "Modifikationslog", "detailedLogs": "Detaljerede fejlfindingslogs", "showLogButton": "Vis logoutput" }, "modSettings": { "title": "Modifikationsindstillinger", "description": "Eksporter eller del nemt dine modifikationsindstillinger.", "loadButton": "Indlæs", "loadFormattedButton": "Indlæs formatteret", "saveButton": "Gem", "invalidData": "Ikke validt JSON data" }, "customList": { "titleInclusion": "Tilpasset procesinklusionsliste", "descriptionInclusion": "En brugerdefineret liste over yderligere eksekverbare filnavne/stier, som modifikationen vil målrettes mod. Listen føjes til inklusionslisten, som modifikationsforfatteren definerede for at bestemme, hvilke processer der skal målrettes mod. For hver proces indlæses modifikationen, hvis den eksekverbare filsti matcher en af ​​inkluderingsposterne og ikke matcher nogen ekskluderingsindgang.", "titleExclusion": "Tilpasset procesekslusionsliste", "descriptionExclusion": "En tilpasset liste over yderligere eksekverbare filnavne/stier, som modifikationen vil udelukke fra målretning. Listen føjes til ekskluderingslisten, som modifikations-forfatteren definerede for at bestemme, hvilke processer der skal målrettes mod. For hver proces indlæses modifikationen, hvis den eksekverbare filsti matcher en af ​​inkluderingsposterne og ikke matcher nogen ekskluderingsindgang.", "processListPlaceholder": "Proces-navne/stige, en per linje, for eksempel:", "saveButton": "Gem" }, "includeExcludeCustomOnly": { "title": "Ignorer modifikations-inkluderings-/ekskluderingslister", "description": "Ignorer modifikationens procesinkluderings-/ekskluderingslister og brug kun de tilpassede lister herover." } }, "changes": { "title": "Seneste versionsændringer", "noData": "Den installerede version er identisk med den seneste version.", "splitView": "Delt visning", "expandLines_one": "Udvid en gemt linje", "expandLines_other": "Udvid {{count}} gemte linjer" } }, "modSearch": { "placeholder": "Søg efter modifikationer..." }, "home": { "browse": "Gennemse modifikationer", "installedMods": { "title": "Installerede modifikationer", "noMods": "Ingen modifikationer er installerede" }, "featuredMods": { "title": "Fremhævede modifikationer", "noMods": "Der er ingen fremhævede modifikationer der ikke er installerede", "explore": "Udforsk andre modifikationer" } }, "explore": { "search": { "popularAndTopRated": "Populær og topbedømte", "popular": "Populær", "topRated": "Topbedømte", "newest": "Nyeste", "lastUpdated": "Seneste opdaterede", "alphabeticalOrder": "Alfabetisk rækkefølge" } }, "settings": { "language": { "title": "Sprog", "description": "Vælg dit foretrukne visningssprog for Windhawk.", "contribute": "<0>Bidrag en ny oversættelse.", "credits": "Dansk oversættelse af <0>AlbertCoolGuy.", "creditsLink": "https://github.com/AlbertCoolGuy" }, "updates": { "title": "Tjek efter opdateringer", "description": "Tjek jævnligt efter nye versioner af Windhawk og af de installerede modifikationer." }, "devMode": { "title": "Udviklertilstand", "description": "Vis handlinger for udviklere, såsom at skabe og ændre modifikationer." }, "advancedSettings": "Avancerede indstillinger", "hideTrayIcon": { "title": "Skjul systembakkeikon", "description": "Du bliver nødt til at deaktivere denne indstilling for at afslutte Windhawk." }, "requireElevation": { "title": "Kræv UAC-elevation for at køre Windhawk", "description": "Windhawk kræver administratorrettigheder, men for en enkeltbrugercomputer kan det være irriterende at få UAC-prompten hver gang, så Windhawk omgår den. Aktiver denne mulighed for at kræve UAC elevation for at køre Windhawk." }, "dontAutoShowToolkit": { "title": "Vis ikke automatisk værktøjskasse-dialogen", "description": "Som standard viser Windhawk automatisk værktøjskasse-dialogen, når den registrerer, at proceslinjen ikke er tilgængelig, enten på grund af systemets ustabilitet eller af en anden årsag. Værktøjskassedialogen gør det muligt at afslutte Windhawk, skifte til sikker tilstand og foretage andre handlinger. Værktøjskassedialogen kan også vises med Ctrl+Win+W tastaturgenvejen." }, "modInitDialogDelay": { "title": "Modifikation-initialiseringsdialogforsinkelse", "description": "Antal millisekunder der ventes før fremskridtsdialogen for modifikationsinitaliseringen vises." }, "moreAdvancedSettings": { "title": "Flere avancerede indstillinger", "restartNotice": "Windhawk bliver genstartet for at anvende indstillingerne.", "saveButton": "Gem og genstart Windhawk", "cancelButton": "Annuller" }, "loggingVerbosity": { "appLoggingTitle": "Windhawk logverbositet", "engineLoggingTitle": "Windhawk-motor logverbositet", "description": "Logs kan blive vist med et værktøj såsom DebugView.", "none": "Intet", "error": "Fejl", "verbose": "Verbos" }, "processList": { "titleExclusion": "Procesinklusionsliste", "descriptionExclusion": "En liste over procesnavne/stier, som Windhawk ikke vil injicere i. Kan være nyttigt i tilfælde, hvor Windhawk ikke er kompatibel med et specifikt program. Bemærk, at tilføjelse af en proces til denne liste ikke kun forhindrer Windhawk i at tilpasse den, men også forhindrer Windhawk i at opfange underprocesser, som denne proces opretter. Dette vil forårsage en lille forsinkelse, når Windhawk indlæser mods i sådanne tilfælde.", "titleInclusion": "Proceseksklusionsliste", "descriptionInclusion": "En liste over procesnavne/stier, som Windhawk vil injicere i, selvom de er på ekskluderingslisten.", "inclusionWithoutExclusionNotice": "Procesinkluderingslisten har ingen effekt med en tom procesekskluderingsliste.", "inclusionWithoutTotalExclusionNotice": "Hvis du havde til hensigt at ekskludere alle processer undtagen disse, kan du indstille \"*\" i procesekskluderingslisten.", "processListPlaceholder": "Procesnavne/stier, én pr. linje, for eksempel:" } }, "about": { "title": "Windhawk v{{version}}", "beta": "beta", "subtitle": "Tilpasningsmarkedspladsen for Windows og programmer", "credit": "Af <0>{{author}}", "update": { "title": "En opdatering er tilgængelig", "subtitle": "Overvej at opdatere Windhawk for at få de nyeste funktioner og fejlrettelser", "updateButton": "Flere detaljer" }, "links": { "title": "Links", "homepage": "Forside", "documentation": "Dokumentation" }, "builtWith": { "title": "Bygget med", "vscodium": "En fællesskabsdrevet distribution af Microsofts VSCode-editor", "llvmMingw": "En LLVM/Clang/LLD baseret mingw-w64 værktøjskæde", "minHook": "Det minimalistiske API-hooking-bibliotek til Windows", "others": "Andre værktøjer og biblioteker og en smule kode" } }, "installModal": { "title": "Installer {{mod}}", "warningTitle": "Fortsæt med omhu", "warningDescription": "Ondsindede mods kan beskadige din computer eller krænke dit privatliv. Installer kun mods fra forfattere, som du har tillid til.", "modAuthor": "Modifikationsforfatter", "homepage": "Forside", "github": "GitHub", "twitter": "X (Twitter)", "verified": "verificeret", "verifiedTooltip": "Vi har bekræftet, at denne profil tilhører modifikationsforfatteren, men bemærk, at <0>verificeret ikke er det samme som <0>trusted. Sørg for, at du stoler på modifikationsforfatteren, eller inspicer omhyggeligt kildekoden før installation.", "acceptButton": "Accepter risiko og installer", "cancelButton": "Annuller" }, "createNewModButton": { "title": "Skab en ny modifikation" }, "devModeAction": { "message": "Oprettelse og ændring af mods kræver en vis viden om C/C++ udvikling til Windows. Hvis du ikke er sikker på, hvad det betyder, er disse muligheder måske ikke noget for dig.", "hideOptionsCheckbox": "Gem all udvikler-relaterede indstillinger", "hideOptionsButton": "Gem indstillinger", "beginCodingButton": "Begynd på at kode", "cancelButton": "Annuller" }, "safeMode": { "alert": "Windhawk kører i sikker tilstand, al kodeindsprøjtningsfunktionalitet er slået fra.", "offButton": "Slå sikker tilstand fra", "offConfirm": "Windhawk vil blive genstartet for at anvende indstillingerne.", "offConfirmOk": "Genstart Windhawk", "offConfirmCancel": "Annuller" }, "modPreview": { "actionUnavailable": "Handling er ikke tilgængelig i forhåndsvisningstilstand", "notCompiled": "Modfikation skal kompileres før den kan forhåndsvises" }, "sidebar": { "modId": "Modifikationsidenfikator", "enableMod": "Slå modifikation til", "enableLogging": "Slå logging til", "notCompiled": "Modifikation skal kompileres", "compile": "Kompiler modifikation", "compilationFailed": "Kompiliation fejlede", "preview": "Forhåndsvis modifikation", "showLogOutput": "Vis logoutput", "exit": "Afslut redigeringstilstand", "exitConfirmation": "Ændringer siden den sidste sucessfulde kompilation vil blive tabt", "exitButtonOk": "Afslut", "exitButtonCancel": "Bliv" } } ================================================ FILE: src/vscode-windhawk-ui/apps/vscode-windhawk-ui/src/locales/de/DO_NOT_EDIT.txt ================================================ The files in this folder are generated automatically. To submit a translation update, please refer to this repository: https://github.com/ramensoftware/windhawk-translate ================================================ FILE: src/vscode-windhawk-ui/apps/vscode-windhawk-ui/src/locales/de/translation.json ================================================ { "general": { "loading": "Lade...", "loadingFailed": "Laden nicht erfolgreich. Bitte überprüfen Sie Ihre Internetverbindung.", "loadingFailedTitle": "Laden fehlgeschlagen.", "loadingFailedSubtitle": "Bitte überprüfen Sie Ihre Internetverbindung und versuchen Sie es erneut.", "tryAgain": "Erneut versuchen", "updating": "Aktualisiere", "installing": "Installiere", "compiling": "Kompiliere", "cut": "Ausschneiden", "copy": "Kopieren", "paste": "Einfügen", "selectAll": "Alles auswählen" }, "appHeader": { "home": "Startseite", "explore": "Durchsuchen", "settings": "Einstellungen", "about": "Über" }, "mod": { "updateAvailable": "Aktualisierung verfügbar", "installed": "Installiert", "details": "Details", "update": "Aktualisierung", "install": "Installiere", "compile": "Kompiliere", "disable": "Deaktivieren", "enable": "Aktivieren", "edit": "Bearbeiten", "fork": "Fork", "remove": "Entfernen", "removeConfirm": "Sind Sie sicher, dass Sie diese Mod entfernen wollen?", "removeConfirmOk": "Entferne Mod", "removeConfirmCancel": "Abbrechen", "notCompiled": "Mod muss kompiliert werden.", "editedLocally": "Mod wurde lokal bearbeitet.", "noDescription": "Leider keine Beschreibung vorhanden.", "users_one": "{{formattedCount}} Benutzer", "users_other": "{{formattedCount}} Benutzer" }, "modDetails": { "header": { "installedVersion": "Installierte Version", "latestVersion": "Neueste Version", "loading": "Lade...", "loadingFailed": "Laden fehlgeschlagen.", "modId": "Mod-Id", "modVersion": "Mod-Version", "modAuthor": { "title": "Mod-Autor", "homepage": "Homepage", "github": "GitHub", "twitter": "X (Twitter)" }, "processes": { "all": "Alle Prozesse", "allBut": "Alle außer {{list}}", "except": "{{included}} außer {{excluded}}", "tooltip": { "targets": "Zielprozesse", "excluded": "Ausgeschlossen" } }, "updateNotNeeded": "Die installierte Version ist identisch mit der neusten Version." }, "details": { "title": "Details", "noData": "Mod-Details fehlen." }, "settings": { "title": "Einstellungen", "noData": "Mod-Einstellungen nicht verfügbar.", "saveButton": "Einstellungen speichern", "sampleValue": "Beispielwert", "arrayItemAdd": "Neues Element hinzufügen", "arrayItemRemove": "Element entfernen" }, "code": { "title": "Quellcode", "noData": "Mod-Quellcode fehlt.", "collapseExtra": "Readme und Einstellungen einklappen" }, "changelog": { "title": "Änderungsprotokoll (Changelog)", "loadingFailed": "Laden des Änderungsprotokolls fehlgeschlagen. <0>Hier klicken um das Änderungsprotokoll auf GitHub zu lesen." }, "advanced": { "title": "Erweitert", "debugLogging": { "title": "Debug Protokollierung", "description": "Kann nützlich sein, um Probleme mit dem Mod zu beheben.", "none": "Nichts", "modLogs": "Mod-Protokolle", "detailedLogs": "Detailierte Debugprotokolle", "showLogButton": "Protokoll anzeigen" }, "modSettings": { "title": "Mod-Einstellungen", "description": "Exportieren und teilen der benutzerdefinierten Mod-Einstellungen.", "loadButton": "Lade", "loadFormattedButton": "Lade formatiert", "saveButton": "Speichern", "invalidData": "Ungültige JSON-Daten!" }, "customList": { "titleInclusion": "Benutzerdefinierte Liste einbezogener Prozesse", "descriptionInclusion": "Eine benutzerdefinierte Liste zusätzlicher ausführbarer Dateinamen/Pfade, auf die die Mod abzielt. Die Liste wird der Aufnahmeliste hinzugefügt, die der Mod-Autor definiert hat, um zu bestimmen, auf welche Prozesse abgezielt werden soll. Für jeden Prozess wird die Mod geladen, wenn der Pfad der ausführbaren Datei mit einem der Include-Einträge übereinstimmt und mit keinem Exclude-Eintrag übereinstimmt.", "titleExclusion": "Benutzerdefinierte Liste ausgeschlossener Prozesse", "descriptionExclusion": "Eine benutzerdefinierte Liste zusätzlicher ausführbarer Dateinamen/Pfade, die die Mod vom Targeting ausschließt. Die Liste wird der Ausschlussliste hinzugefügt, die der Mod-Autor definiert hat, um zu bestimmen, auf welche Prozesse abgezielt werden soll. Für jeden Prozess wird die Mod geladen, wenn der Pfad der ausführbaren Datei mit einem der Include-Einträge übereinstimmt und mit keinem Exclude-Eintrag übereinstimmt.", "processListPlaceholder": "Prozessnamen/-pfade, einer pro Zeile, z. B.:", "invalidCharactersWarning": "Die Prozessliste enthält ungültige Zeichen, die entfernt werden: {{invalidCharacters}}", "saveButton": "Speichern" }, "includeExcludeCustomOnly": { "title": "Mod-Einschluss-/Ausschlusslisten ignorieren", "description": "Ignoriere die Einschluss-/Ausschlusslisten der Mod und verwende nur die benutzerdefinierten Listen oben." }, "patternsMatchCriticalSystemProcesses": { "title": "Berücksichtige Einschlusslistenmuster für kritische Systemprozesse", "description": "Standardmäßig lädt Windhawk Mods in kritischen Systemprozessen nur, wenn der Prozess ohne Muster angegeben wird, z.B. <0>critical.exe, nicht <0>* oder <0>*.exe. Weitere Details zu kritischen Systemprozessen sind in <1>der Dokumentation zu finden." } }, "changes": { "title": "Letzte Versionsänderungen", "noData": "Die installierte Version ist identisch mit der neusten Version.", "splitView": "Ansicht aufteilen", "expandLines_one": "Zeige eine versteckte Zeile", "expandLines_other": "Zeige {{count}} versteckte Zeilen" } }, "modSearch": { "placeholder": "Suche nach Mods..." }, "home": { "browse": "Nach Mods suchen", "installedMods": { "title": "Installierte Mods", "noMods": "Keine Mods installiert." }, "featuredMods": { "title": "Vorgestellte Mods", "noMods": "Es gibt keine vorgestellten Mods, die noch nicht installiert wurden.", "explore": "Nach anderen Mods suchen" } }, "explore": { "search": { "popularAndTopRated": "Beliebt und am besten bewertet", "popular": "Beliebt", "topRated": "Beste Bewertung", "newest": "Neueste", "lastUpdated": "Zuletzt aktualisiert", "alphabeticalOrder": "Alphabetisch sortiert" } }, "settings": { "language": { "title": "Sprache", "description": "Sprache für Windhawk auswählen", "contribute": "<0>Eine neue Übersetzung erstellen.", "credits": "Deutsche Übersetzung: <0>TomfromBerlin.", "creditsLink": "mailto:tom-at-github@quantentunnel.de" }, "updates": { "title": "Nach Aktualisierungen suchen", "description": "Regelmäßig nach neuen Versionen von Windhawk und den installierten Mods suchen" }, "devMode": { "title": "Entwickler-Modus", "description": "Aktionen für Entwickler anzeigen, z. B. das Erstellen und Ändern von Mods" }, "advancedSettings": "Erweiterte Einstellungen", "hideTrayIcon": { "title": "Taskleistensymbol ausblenden", "description": "Diese Option muss deaktiviert sein, um Windhawk zu beenden." }, "requireElevation": { "title": "Die Erhöhung der Benutzerrechte (user account control elevation) wird zum Ausführen von Windhawk benötigt", "description": "Windhawk erfordert Administratorrechte, aber auf einem Einzelbenutzer-Computer kann es lästig sein, jedes Mal die Rechteerhöhung zu bestätigen, sodass Windhawk die Bestätung umgeht. Aktivieren Sie diese Option, um eine Bestätigung der Rechteerhöhung (UAC elevation) durch den Benutzer für die Ausführung von Windhawk anzufordern." }, "dontAutoShowToolkit": { "title": "Toolkit-Dialog nicht automatisch anzeigen", "description": "Standardmäßig zeigt Windhawk den Toolkit-Dialog automatisch an, wenn es erkennt, dass die Taskleiste nicht zugänglich ist, sei es aufgrund von Systeminstabilität oder einem anderen Grund. Der Toolkit-Dialog ermöglicht es, Windhawk zu beenden, in den abgesicherten Modus zu wechseln und andere Aktionen auszuführen. Der Toolkit-Dialog kann auch mit der Tastenkombination Strg+Win+W aufgerufen werden." }, "modInitDialogDelay": { "title": "Verzögerung des Mod-Initialisierungsdialogs", "description": "Wartezeit in Millisekunden, bevor der Fortschrittsdialog für die Mod-Initialisierung angezeigt wird." }, "moreAdvancedSettings": { "title": "Mehr erweiterte Einstellungen", "restartNotice": "Windhawk wird neu gestartet, um die Einstellungen zu übernehmen.", "saveButton": "Speichern und Windhawk neu starten", "cancelButton": "Abbrechen" }, "loggingVerbosity": { "appLoggingTitle": "Art der Windhawk-Protokollierung", "engineLoggingTitle": "Art der Windhawk-Engine-Protokollierung", "description": "Protokolle können mit einem Tool wie DebugView angezeigt werden.", "none": "Keine", "error": "Error", "verbose": "Ausführlich" }, "processList": { "titleExclusion": "Liste der ausgeschlossenen Prozesse", "descriptionExclusion": "Eine Liste von Prozessnamen/-pfaden, in die Windhawk nicht einfügt. Kann nützlich sein, wenn Windhawk nicht mit einem bestimmten Programm kompatibel ist. Beachten Sie, dass das Hinzufügen eines Prozesses zu dieser Liste Windhawk nicht nur daran hindert, ihn anzupassen, sondern auch daran hindert, untergeordnete Prozesse abzufangen, die dieser Prozess erstellt. Dies führt dazu, dass Windhawk Mods in solchen Fällen mit einer leichten Verzögerung lädt.", "descriptionExclusionWiki": "Weitere Details zum Ausschluss von Prozessen und zu den eingebauten Listen der aufgeschlossenen Prozesse sind in <0>der Dokumentation zu finden.", "excludeCriticalProcesses": "Kritische Systemprozesse ausschließen", "excludeIncompatiblePrograms": "Bekannte inkompatible Programme ausschließen", "excludeGames": "Bekannte Spiele ausschließen", "titleInclusion": "Liste der eingeschlossenen Prozesse", "descriptionInclusion": "Eine Liste von Prozessnamen/-pfaden, in die Windhawk einfügt, selbst wenn sie in der Ausschlussliste stehen.", "inclusionWithoutExclusionNotice": "Bei einer leeren Prozessausschlussliste hat die Prozessaufnahmeliste keine Wirkung.", "inclusionWithoutTotalExclusionNotice": "Wenn Sie alle Prozesse außer diesen ausschließen wollten, können Sie \"*\" in der Prozessausschlussliste setzen.", "processListPlaceholder": "Prozessnamen/-pfade, einer pro Zeile, z. B.:", "invalidCharactersWarning": "Die Prozessliste enthält ungültige Zeichen, die entfernt werden: {{invalidCharacters}}" } }, "about": { "title": "Windhawk v{{version}}", "beta": "beta", "subtitle": "Der Marktplatz mit Anpassungen für Windowsprogramme", "credit": "Von <0>{{author}}", "update": { "title": "Aktualisierung verfügbar", "subtitle": "Erwägen Sie, Windhawk zu aktualisieren, um die neuesten Funktionen und Fehlerbehebungen zu erhalten.", "updateButton": "Mehr Details" }, "links": { "title": "Links", "homepage": "Startseite", "documentation": "Dokumentation" }, "builtWith": { "title": "Erstellt mit...", "vscodium": "eine von der Community betriebene Distribution des VSCode-Editors von Microsoft", "llvmMingw": "eine LLVM/Clang/LLD-basierte mingw-w64 Toolchain", "minHook": "die minimalistische API-Hooking-Bibliothek für Windows", "others": "...sowie anderen Tools, Bibliotheken und ein bisschen Code..." } }, "installModal": { "title": "{{mod}} installieren", "warningTitle": "Vorsicht ist hier geboten!", "warningDescription": "Bösartige Mods können Ihren Computer beschädigen oder Ihre Privatsphäre verletzen. Installieren Sie Mods nur von Autoren, denen Sie vertrauen.", "modAuthor": "Mod-Autor", "homepage": "Homepage", "github": "GitHub", "twitter": "X (Twitter)", "verified": "verifiziert", "verifiedTooltip": "Wir haben verifiziert, dass dieses Profil dem Mod-Autor gehört, aber beachten Sie, dass <0>verifiziert nicht dasselbe ist wie <0>vertrauenswürdig. Stellen Sie sicher, dass Sie dem Mod-Autor vertrauen, oder prüfen Sie den Quellcode vor der Installation sorgfältig.", "acceptButton": "Das Risiko akzeptieren (Installiere Modifikation)", "cancelButton": "Abbrechen" }, "createNewModButton": { "title": "Eine neue Mod erstellen" }, "devModeAction": { "message": "Das Erstellen und Modifizieren von Mods erfordert einige Kenntnisse der C/C++-Entwicklung für Windows. Wenn Sie sich nicht sicher sind, was das bedeutet, sind diese Optionen vielleicht nichts für Sie.", "hideOptionsCheckbox": "Alle entwicklungsbezogenen Optionen ausblenden", "hideOptionsButton": "Optionen ausblenden", "beginCodingButton": "Starte Programmieren", "cancelButton": "Abbrechen" }, "safeMode": { "alert": "Windhawk läuft im abgesicherten Modus, alle Code-Injektionsfunktionen sind deaktiviert.", "offButton": "Abgesicherten Modus deaktivieren", "offConfirm": "Windhawk wird neu gestartet, um die Einstellungen anzuwenden.", "offConfirmOk": "Windhawk neu starten", "offConfirmCancel": "Abbrechen" }, "modPreview": { "actionUnavailable": "Die Aktion ist im Vorschaumodus nicht verfügbar.", "notCompiled": "Die Mod muss kompiliert werden, bevor die Vorschau angezeigt werden kann" }, "sidebar": { "modId": "Mod-Id", "enableMod": "Aktiviere Mod", "enableLogging": "Aktiviere Protokollierung", "notCompiled": "Mod muss kompiliert werden.", "compile": "Kompiliere Mod...", "compilationFailed": "Kompilierung fehlgeschlagen", "preview": "Mod Vorschau", "showLogOutput": "Zeige Protokoll", "exit": "Beende Bearbeitungsmodus", "exitConfirmation": "Alle Änderungen seit der letzten erfolgreichen Kompilierung gehen verloren!", "exitButtonOk": "Verlassen", "exitButtonCancel": "Bleiben" } } ================================================ FILE: src/vscode-windhawk-ui/apps/vscode-windhawk-ui/src/locales/el/DO_NOT_EDIT.txt ================================================ The files in this folder are generated automatically. To submit a translation update, please refer to this repository: https://github.com/ramensoftware/windhawk-translate ================================================ FILE: src/vscode-windhawk-ui/apps/vscode-windhawk-ui/src/locales/el/translation.json ================================================ { "general": { "loading": "Φόρτωση...", "loadingFailed": "Η φόρτωση απέτυχε, ελέγξτε τη σύνδεσή σας στο Διαδίκτυο", "loadingFailedTitle": "Η φόρτωση απέτυχε", "loadingFailedSubtitle": "Ελέγξτε τη σύνδεσή σας στο Διαδίκτυο και δοκιμάστε ξανά", "tryAgain": "Προσπαθήστε ξανά", "updating": "Ενημέρωση...", "installing": "Εγκατάσταση...", "compiling": "Μετατροπή σε .exe...", "cut": "Αποκοπή", "copy": "Αντιγραφή", "paste": "Επικόλληση", "selectAll": "Επιλογή όλων" }, "appHeader": { "home": "Κεντρική", "explore": "Περιήγηση", "settings": "Ρυθμίσεις", "about": "Σχετικά" }, "mod": { "updateAvailable": "Διαθέσιμη ενημέρωση", "installed": "Εγκατεστημένο", "details": "?Λεπτομέρειες", "update": "Ενημέρωση", "install": "Εγκαθιστώ", "compile": "Μετατροπή σε .exe", "disable": "Απενεργοποίηση", "enable": "Ενεργοποίηση", "edit": "Επεξεργασία", "fork": "Διακλάδωση", "remove": "Αφαίρεση", "removeConfirm": "Είστε βέβαιοι ότι θέλετε να καταργήσετε αυτό το mod?", "removeConfirmOk": "Κατάργηση mod", "removeConfirmCancel": "Άκυρο", "notCompiled": "Το Mod πρέπει να μεταγλωττιστεί", "editedLocally": "Το Mod επεξεργάστηκε τοπικά", "noDescription": "(καμία περιγραφή)", "users_one": "{{formattedCount}} χρήστης", "users_other": "{{formattedCount}} χρήστες" }, "modDetails": { "header": { "installedVersion": "Εγκατεστημένη έκδοση", "latestVersion": "Τελευταία έκδοση", "loading": "φόρτωση...", "loadingFailed": "η φόρτωση απέτυχε", "modId": "Αναγνωριστικό mod", "modVersion": "ΈκδοσηMod", "modAuthor": { "title": "Δημιουργός του Mod", "homepage": "Αρχική σελίδα", "github": "GitHub", "twitter": "X (Twitter)" }, "processes": { "all": "Όλες οι διαδικασίες", "allBut": "Όλα αλλά {{list}}", "except": "{{included}} εκτός {{excluded}}", "tooltip": { "targets": "Στοχευμένες διαδικασίες", "excluded": "Εξαιρείται" } }, "updateNotNeeded": "Η εγκατεστημένη έκδοση είναι πανομοιότυπη με την πιο πρόσφατη έκδοση" }, "details": { "title": "Λεπτομέρειες", "noData": "Οι λεπτομέρειες του Mod λείπουν." }, "settings": { "title": "Ρυθμίσεις", "noData": "Δεν υπάρχουν διαθέσιμες ρυθμίσεις mod.", "saveButton": "Αποθήκευση ρυθμίσεων", "sampleValue": "Δείγμα τιμής", "arrayItemAdd": "Προσθήκη νέου στοιχείου", "arrayItemRemove": "Αφαίρεση στοιχείου" }, "code": { "title": "Πηγαίος κώδικας", "noData": "Η πηγή mod λείπει.", "collapseExtra": "Σύμπτυξη Readme και Ρυθμίσεων" }, "changelog": { "title": "Καταγραφή αλλαγών", "loadingFailed": "Η φόρτωση του αρχείου καταγραφής αλλαγών απέτυχε <0>Click here να το δείτε στο GitHub." }, "advanced": { "title": "Για Προχωρημένους", "debugLogging": { "title": "Καταγραφή εντοπισμού σφαλμάτων", "description": "Μπορεί να είναι χρήσιμο για την αντιμετώπιση προβλημάτων με το mod.", "none": "Κανένα", "modLogs": "Αρχεία καταγραφής mod", "detailedLogs": "Λεπτομερή αρχεία καταγραφής εντοπισμού σφαλμάτων", "showLogButton": "Εμφάνιση εξόδου καταγραφής" }, "modSettings": { "title": "Ρυθμίσεις Mod", "description": "Εξάγετε ή κοινοποιήστε εύκολα τις ρυθμίσεις mod σας.", "loadButton": "Φόρτωση", "loadFormattedButton": "Μορφοποιημένη φόρτωση", "saveButton": "Αποθήκευση", "invalidData": "Μη έγκυρα δεδομένα JSON" }, "customList": { "titleInclusion": "Προσαρμοσμένη λίστα συμπερίληψης διεργασιών", "descriptionInclusion": "Μια προσαρμοσμένη λίστα πρόσθετων εκτελέσιμων ονομάτων αρχείων/διαδρομών που θα στοχεύσει το mod. Η λίστα προστίθεται στη λίστα συμπερίληψης που όρισε ο συντάκτης του mod για να καθορίσει ποιες διαδικασίες θα στοχεύσει. Για κάθε διαδικασία, το mod φορτώνεται εάν η διαδρομή του εκτελέσιμου αρχείου ταιριάζει με μία από τις καταχωρήσεις συμπερίληψης και δεν αντιστοιχεί σε καμία εξαίρεση καταχώρισης.", "titleExclusion": "Λίστα εξαιρέσεων προσαρμοσμένης διαδικασίας", "descriptionExclusion": "Μια προσαρμοσμένη λίστα με επιπλέον εκτελέσιμα ονόματα/διαδρομές αρχείων που το mod θα εξαιρέσει από τη στόχευση. Η λίστα προστίθεται στη λίστα εξαιρέσεων που όρισε ο συντάκτης του mod για να καθορίσει ποιες διεργασίες θα στοχεύσει. Για κάθε διεργασία, το mod φορτώνεται εάν η διαδρομή του εκτελέσιμου αρχείου ταιριάζει με μία από τις καταχωρήσεις συμπερίληψης και δεν ταιριάζει με καμία καταχώρηση εξαίρεσης.", "processListPlaceholder": "Ονόματα διεργασίας/διαδρομές, μία ανά γραμμή, για παράδειγμα:", "invalidCharactersWarning": "Η λίστα διεργασιών περιέχει μη έγκυρους χαρακτήρες που θα αφαιρεθούν: {{invalidCharacters}}", "saveButton": "Αποθήκευση" }, "includeExcludeCustomOnly": { "title": "Αγνόησε τις λίστες συμπερίληψης/εξαίρεσης mod", "description": "Αγνοήστε τις λίστες συμπερίληψης/εξαίρεσης διεργασιών του mod και χρησιμοποιήστε μόνο τις προσαρμοσμένες λίστες παραπάνω." }, "patternsMatchCriticalSystemProcesses": { "title": "Εξετάστε τα μοτίβα λίστας συμπερίληψης για κρίσιμες διαδικασίες συστήματος", "description": "Από προεπιλογή, το Windhawk φορτώνει mods μόνο σε κρίσιμες διεργασίες συστήματος εάν η διαδικασία περιλαμβάνεται χωρίς πρότυπα, e.g. <0>critical.exe, not <0>* or <0>*.exe. Για περισσότερες λεπτομέρειες σχετικά με τις κρίσιμες διαδικασίες του συστήματος, ανατρέξτε <1>στην τεκμηρίωση." } }, "changes": { "title": "Τελευταίες αλλαγές έκδοσης", "noData": "Η εγκατεστημένη έκδοση είναι πανομοιότυπη με την πιο πρόσφατη έκδοση.", "splitView": "Διαίρεση προβολής", "expandLines_one": "Ανάπτυξη μιας κρυφής γραμμής", "expandLines_other": "Ανάπτυξη {{count}} κρυφών γραμμών" } }, "modSearch": { "placeholder": "Αναζήτηση για mods..." }, "home": { "browse": "Περιήγηση για Mods", "installedMods": { "title": "Εγκατεστημένα Mods", "noMods": "Δεν έχουν εγκατασταθεί mods" }, "featuredMods": { "title": "Αναβαθμισμένα Mods", "noMods": "Δεν υπάρχουν αναβαθμισμένα mods που να μην έχουν εγκατασταθεί ακόμα", "explore": "Εξερεύνηση άλλων Mods" } }, "explore": { "search": { "popularAndTopRated": "Δημοφιλής και κορυφαία βαθμολογία", "popular": "Δημοφιλία", "topRated": "Κορυφαία βαθμολογία", "newest": "Νεότερο", "lastUpdated": "Τελευταία ενημέρωση", "alphabeticalOrder": "Αλφαβητική σειρά" } }, "settings": { "language": { "title": "Γλώσσα", "description": "Επιλέξτε τη γλώσσα εμφάνισης που προτιμάτε για το Windhawk.", "contribute": "<0>Συνεισφέρετε σε μια νέα μετάφραση.", "credits": "Ελληνική μετάφραση από <0>Σπύρος Κρικέλης-Βόλος.", "creditsLink": "E-mail:skrikelis@sch.gr" }, "updates": { "title": "Έλεγχος για ενημερώσεις", "description": "Ελέγχετε περιοδικά για νέες εκδόσεις του Windhawk και των εγκατεστημένων mods." }, "devMode": { "title": "Λειτουργία προγραμματιστή", "description": "Εμφάνιση ενεργειών για προγραμματιστές, όπως δημιουργία και τροποποίηση mods." }, "advancedSettings": "Ρυθμίσεις για προχωρημένους", "hideTrayIcon": { "title": "Απόκρυψη εικονιδίου παρασκηνίου", "description": "Θα πρέπει να απενεργοποιήσετε αυτήν την επιλογή για να βγείτε από το Windhawk." }, "requireElevation": { "title": "Απαιτείται κλιμάκωση UAC για τη λειτουργία του Windhawk", "description": "Το Windhawk απαιτεί δικαιώματα διαχειριστή, αλλά για έναν υπολογιστή ενός χρήστη, η λήψη της προτροπής UAC κάθε φορά μπορεί να είναι ενοχλητική, οπότε ο Windhawk το παρακάμπτει. Ενεργοποιήστε αυτήν την επιλογή για να απαιτείται κλμάκωση UAC για την εκτέλεση του Windhawk." }, "dontAutoShowToolkit": { "title": "Να μην εμφανίζεται αυτόματα το παράθυρο διαλόγου της εργαλειοθήκης", "description": "Από προεπιλογή, το Windhawk εμφανίζει αυτόματα το παράθυρο διαλόγου της εργαλειοθήκης όταν εντοπίζει ότι η γραμμή εργασιών δεν είναι προσβάσιμη, είτε λόγω αστάθειας του συστήματος είτε για άλλο λόγο. Το παράθυρο διαλόγου εργαλειοθήκης επιτρέπει την έξοδο από το Windhawk, τη μετάβαση σε ασφαλή λειτουργία και να κάνει άλλες παρεμβάσεις. Ο διάλογος της εργαλειοθήκης μπορεί επίσης να εμφανιστεί με τη συντόμευση πληκτρολογίου Ctrl+Win+W." }, "modInitDialogDelay": { "title": "Καθυστέρηση διαλόγου προετοιμασίας Mod", "description": "Όριο χιλιοστών του δευτερολέπτου αναμονής πριν εμφανιστεί το παράθυρο διαλόγου προόδου για την προετοιμασία του mod." }, "moreAdvancedSettings": { "title": "Πιο σύνθετες ρυθμίσεις", "restartNotice": "Το Windhawk θα επανεκκινηθεί για να εφαρμοστούν οι ρυθμίσεις.", "saveButton": "Αποθήκευση και επανεκκίνηση του Windhawk", "cancelButton": "Άκυρο" }, "loggingVerbosity": { "appLoggingTitle": "Βελτίωση καταγραφής του Windhawk", "engineLoggingTitle": "Λεπτομέρειες καταγραφής κινητήρα Windhawk", "description": "Τα αρχεία καταγραφής μπορούν να προβληθούν με ένα εργαλείο όπως το DebugView.", "none": "Κανένα", "error": "Σφάλμα", "verbose": "Εμφάνιση λεπτομερειών" }, "processList": { "titleExclusion": "Λίστα εξαιρέσεων διαδικασίας", "descriptionExclusion": "Μια λίστα ονομάτων/διαδρομών διεργασιών τις οποίες η Windhawk δεν θα εισάγει. Μπορεί να είναι χρήσιμο για περιπτώσεις όπου το Windhawk δεν είναι συμβατό με ένα συγκεκριμένο πρόγραμμα. Σημειώστε ότι η προσθήκη μιας διεργασίας σε αυτήν τη λίστα όχι μόνο θα εμποδίσει το Windhawk να την προσαρμόσει, αλλά θα εμποδίσει επίσης το Windhawk να παρεμποδίσει τις θυγατρικές διεργασίες που δημιουργεί αυτή η διαδικασία. Αυτό που θα κάνει το Windhawk, είναι να φορτώσει τα mods με μια μικρή καθυστέρηση σε τέτοιες περιπτώσεις.", "descriptionExclusionWiki": "Για περισσότερες λεπτομέρειες σχετικά με την εξαίρεση διεργασιών και σχετικά με τις ενσωματωμένες λίστες εξαίρεσης διεργασιών, ανατρέξτε <0>στην τεκμηρίωση.", "excludeCriticalProcesses": "Εξαίρεση κρίσιμων διεργασιών συστήματος", "excludeIncompatiblePrograms": "Εξαίρεση γνωστών μη συμβατών προγραμμάτων", "excludeGames": "Εξαίρεση γνωστών παιχνιδιών", "titleInclusion": "Λίστα συμπερίληψης διαδικασίας", "descriptionInclusion": "Μια λίστα ονομάτων/διαδρομών διεργασιών τις οποίες θα εισάγει το Windhawk, ακόμα κι αν βρίσκονται στη λίστα εξαιρέσεων.", "inclusionWithoutExclusionNotice": "Η λίστα συμπερίληψης διεργασιών δεν έχει αποτέλεσμα με μια κενή λίστα εξαίρεσης διεργασιών.", "inclusionWithoutTotalExclusionNotice": "Εάν θέλετε να εξαιρέσετε όλες τις διαδικασίες εκτός από αυτές, μπορείτε να ορίσετε \"*\" στη λίστα εξαιρέσεων διαδικασίας.", "processListPlaceholder": "Ονόματα/διαδρομές διεργασίας, ανά μία γραμμή, για παράδειγμα:", "invalidCharactersWarning": "Η λίστα διεργασιών περιέχει μη έγκυρους χαρακτήρες που θα αφαιρεθούν: {{invalidCharacters}}" } }, "about": { "title": "Windhawk v{{version}}", "beta": "beta", "subtitle": "Η αγορά προσαρμογής για Windows και προγράμματα", "credit": "Από <0>{{author}}", "update": { "title": "Υπάρχει διαθέσιμη ενημέρωση", "subtitle": "Εξετάστε το ενδεχόμενο να ενημερώσετε το Windhawk για να λάβετε τις πιο πρόσφατες δυνατότητες και διορθώσεις σφαλμάτων", "updateButton": "Περισσότερες λεπτομέρειες" }, "links": { "title": "Συνδέσεις", "homepage": "Αρχική σελίδα", "documentation": "Τεκμηρίωση" }, "builtWith": { "title": "Δομημένο με", "vscodium": "Μια διανομή βάσει κοινότητας του επεξεργαστή VSCode της Microsoft", "llvmMingw": "Μια αλυσίδα εργαλείων mingw-w64 που βασίζεται στο LLVM/Clang/LLD", "minHook": "Η ελαχιστοποιημένη βιβλιοθήκη σύνδεσης API για Windows", "others": "Άλλα εργαλεία και βιβλιοθήκες και λίγος κώδικας" } }, "installModal": { "title": "Εγκατάσταση {{mod}}", "warningTitle": "Προχωρήστε με προσοχή", "warningDescription": "Κακόβουλα mods μπορεί να βλάψουν τον υπολογιστή σας ή να παραβιάσουν το απόρρητό σας. Εγκαταστήστε mods μόνο από συγγραφείς που εμπιστεύεστε.", "modAuthor": "Δημιουργός Mod", "homepage": "Η ιστοσελίδα μας", "github": "GitHub", "twitter": "X (Twitter)", "verified": "επαληθεύτηκε", "verifiedTooltip": "Επαληθεύσαμε ότι αυτό το προφίλ ανήκει στον συγγραφέα του mod, αλλά σημειώστε ότι <0>η επαλήθευση δεν είναι <0>εμπιστοσύνης. Βεβαιωθείτε ότι εμπιστεύεστε τον συγγραφέα του mod ή ελέγξτε προσεκτικά τον πηγαίο κώδικα πριν από την εγκατάσταση", "acceptButton": "Αποδοχή κινδύνου και εγκατάσταση", "cancelButton": "Ακύρωση" }, "createNewModButton": { "title": "Δημιουργήστε ένα νέο Mod" }, "devModeAction": { "message": "Η δημιουργία και η τροποποίηση mods απαιτεί κάποια γνώση C/C++ ανάπτυξης για Windows. Εάν δεν είστε σίγουροι τι σημαίνει αυτό, ίσως αυτές οι επιλογές δεν είναι για εσάς.", "hideOptionsCheckbox": "Απόκρυψη όλων των επιλογών που σχετίζονται με την ανάπτυξη", "hideOptionsButton": "Απόκρυψη επιλογών", "beginCodingButton": "Έναρξη κωδικοποίησης", "cancelButton": "Άκυρο" }, "safeMode": { "alert": "Το Windhawk λειτουργεί σε ασφαλή λειτουργία, όλες οι λειτουργίες εισαγωγής κώδικα είναι απενεργοποιημένες.", "offButton": "Απενεργοποίηση ασφαλούς λειτουργίας", "offConfirm": "Το Windhawk θα επανεκκινηθεί για να εφαρμοστούν οι ρυθμίσεις.", "offConfirmOk": "Επανεκκίνηση του Windhawk", "offConfirmCancel": "Άκυρο" }, "modPreview": { "actionUnavailable": "Η ενέργεια δεν είναι διαθέσιμη σε λειτουργία προεπισκόπησης", "notCompiled": "Το Mod πρέπει να μεταγλωττιστεί για να είναι δυνατή η προεπισκόπησή του" }, "sidebar": { "modId": "Αναγνωριστικό mod", "enableMod": "Ενεργοποίηση mod", "enableLogging": "Ενεργοποίηση καταγραφής", "notCompiled": "Το Mod πρέπει να μεταγλωττιστεί", "compile": "Μεταγλώττιση Mod", "compilationFailed": "Η συλλογή απέτυχε", "preview": "Προεπισκόπηση Mod", "showLogOutput": "Εμφάνιση εξόδου καταγραφής", "exit": "Έξοδος από τη λειτουργία επεξεργασίας", "exitConfirmation": "Οι αλλαγές από την τελευταία επιτυχημένη συλλογή θα χαθούν", "exitButtonOk": "Εξοδος", "exitButtonCancel": "Παραμονή" } } ================================================ FILE: src/vscode-windhawk-ui/apps/vscode-windhawk-ui/src/locales/en/DO_NOT_EDIT.txt ================================================ The files in this folder are generated automatically. To submit a translation update, please refer to this repository: https://github.com/ramensoftware/windhawk-translate ================================================ FILE: src/vscode-windhawk-ui/apps/vscode-windhawk-ui/src/locales/en/translation.json ================================================ { "general": { "loading": "Loading...", "loadingFailed": "Loading failed, please check your internet connection", "loadingFailedTitle": "Loading failed", "loadingFailedSubtitle": "Please check your internet connection and try again", "tryAgain": "Try again", "updating": "Updating...", "installing": "Installing...", "compiling": "Compiling...", "cut": "Cut", "copy": "Copy", "paste": "Paste", "selectAll": "Select all", "loggingEnabled": "Debug logging is enabled" }, "appHeader": { "home": "Home", "explore": "Explore", "settings": "Settings", "about": "About" }, "mod": { "updateAvailable": "Update available", "installed": "Installed", "details": "Details", "update": "Update", "downgrade": "Downgrade", "install": "Install", "compile": "Compile", "disable": "Disable", "enable": "Enable", "edit": "Edit", "fork": "Fork", "remove": "Remove", "removeConfirm": "Are you sure you want to remove this mod?", "removeConfirmOk": "Remove mod", "removeConfirmCancel": "Cancel", "donate": "Donate", "notCompiled": "Mod needs to be compiled", "editedLocally": "Mod was edited locally", "noDescription": "(no description)", "users_one": "{{formattedCount}} user", "users_other": "{{formattedCount}} users", "notRated": "This mod hasn't been rated yet", "loggingEnabledInAdvancedTab": "Debug logging is enabled in the mod's advanced tab" }, "modDetails": { "header": { "installedVersion": "Installed version", "latestVersion": "Latest version", "loading": "loading...", "loadingFailed": "loading failed", "otherVersions": "Other versions", "selectedVersion": "Selected: {{version}}", "modId": "Mod identifier", "modVersion": "Mod version", "lastUpdated": "Last updated", "modAuthor": { "title": "Mod author", "homepage": "Homepage", "github": "GitHub", "twitter": "X (Twitter)" }, "processes": { "all": "All processes", "allBut": "All but {{list}}", "except": "{{included}} except {{excluded}}", "tooltip": { "targets": "Target processes", "excluded": "Excluded", "customListsNote": "Custom process inclusion/exclusion lists were defined for this mod in the Advanced tab", "patternsMatchCriticalSystemProcessesNote": "Inclusion list patterns are considered for critical system processes for this mod (set in the Advanced tab)" } }, "updateNotNeeded": "The installed version is identical to the selected version" }, "version": { "preRelease": "pre-release", "select": "Select", "cancel": "Cancel", "chooseVersion": "Choose a version..." }, "details": { "title": "Details", "noData": "Mod details are missing." }, "settings": { "title": "Settings", "noData": "No mod settings are available.", "saveButton": "Save settings", "sampleValue": "Sample value", "arrayItemAdd": "Add new item", "arrayItemRemove": "Remove item", "yamlMode": "Textual mode", "uiMode": "Visual mode", "yamlInvalid": "Invalid YAML format", "yamlParseError": "YAML Parse Error: {{error}}", "yamlInvalidKey": "Invalid key in YAML: '{{key}}' is not a valid setting", "yamlTypeMismatch": "Type mismatch for '{{key}}': expected {{expected}}, but got {{actual}}", "unsavedChangesTitle": "Unsaved Settings", "unsavedChangesMessage": "You have unsaved settings changes. Are you sure you want to discard them?", "unsavedChangesLeave": "Discard Changes", "unsavedChangesStay": "Keep Editing" }, "code": { "title": "Source Code", "noData": "Mod source is missing.", "collapseExtra": "Collapse Readme and Settings" }, "changelog": { "title": "Changelog", "loadingFailed": "Failed to load changelog. <0>Click here to view it on GitHub." }, "advanced": { "title": "Advanced", "debugLogging": { "title": "Debug logging", "description": "Can be useful for troubleshooting issues with the mod.", "none": "None", "modLogs": "Mod logs", "detailedLogs": "Detailed debug logs", "showLogButton": "Show log output" }, "modSettings": { "title": "Mod settings", "description": "Easily export or share your mod settings.", "loadButton": "Load", "loadFormattedButton": "Load formatted", "saveButton": "Save", "invalidData": "Invalid JSON data" }, "customList": { "titleInclusion": "Custom process inclusion list", "descriptionInclusion": "A custom list of additional executable file names/paths that the mod will target. The list is added to the inclusion list that the mod author defined to determine which processes to target. For each process, the mod is loaded if the executable file path matches one of the include entries and doesn't match any exclude entry.", "titleExclusion": "Custom process exclusion list", "descriptionExclusion": "A custom list of additional executable file names/paths that the mod will exclude from targeting. The list is added to the exclusion list that the mod author defined to determine which processes to target. For each process, the mod is loaded if the executable file path matches one of the include entries and doesn't match any exclude entry.", "processListPlaceholder": "Process names/paths, one per line, for example:", "invalidCharactersWarning": "The process list contains invalid characters which will be stripped: {{invalidCharacters}}", "saveButton": "Save" }, "includeExcludeCustomOnly": { "title": "Ignore mod inclusion/exclusion lists", "description": "Ignore the mod's process inclusion/exclusion lists and use only the custom lists above." }, "patternsMatchCriticalSystemProcesses": { "title": "Consider inclusion list patterns for critical system processes", "description": "By default, Windhawk only loads mods in critical system processes if the process is included without patterns, e.g. <0>critical.exe, not <0>* or <0>*.exe. For more details about critical system processes, please refer to <1>the documentation." } }, "changes": { "title": "Source Code Changes", "noData": "The installed version is identical to the selected version.", "splitView": "Split view", "expandLines_one": "Expand one hidden line", "expandLines_other": "Expand {{count}} hidden lines" } }, "modSearch": { "placeholder": "Search for mods...", "noResults": "No mods match the current filters" }, "home": { "browse": "Browse for Mods", "filter": { "enabled": "Enabled mods", "disabled": "Disabled mods", "updateAvailable": "Mods with update available", "clearFilters": "Clear filters" }, "installedMods": { "title": "Installed Mods", "noMods": "No mods are installed", "grid": { "name": "Name", "description": "Description", "author": "Author", "version": "Version", "status": "Status" } }, "featuredMods": { "title": "Featured Mods", "noMods": "There are no featured mods that haven't been installed yet", "explore": "Explore Other Mods" } }, "explore": { "search": { "popularAndTopRated": "Popular and top rated", "popular": "Popular", "topRated": "Top rated", "newest": "Newest", "lastUpdated": "Last updated", "alphabeticalOrder": "Alphabetical order" }, "filter": { "installationStatus": "Installation status", "installed": "Installed", "notInstalled": "Not installed", "author": "Mod author", "process": "Target process", "showMore": "Show more...", "clearFilters": "Clear filters" } }, "settings": { "language": { "title": "Language", "description": "Select your preferred display language for Windhawk.", "contribute": "<0>Contribute a new translation.", "credits": "English translation by <0>John Smith.", "creditsLink": "mailto:john.smith@example.com" }, "updates": { "title": "Check for updates", "description": "Automatically check for and notify about new versions of Windhawk and installed mods." }, "devMode": { "title": "Developer mode", "description": "Show actions for developers, such as creating and modifying mods." }, "advancedSettings": "Advanced settings", "hideTrayIcon": { "title": "Hide tray icon", "description": "You will have to disable this option to exit Windhawk." }, "alwaysCompileModsLocally": { "title": "Always compile mods locally", "description": "By default, Windhawk downloads pre-compiled mod binaries from the Windhawk server when available. Enable this option to always compile mods locally instead." }, "requireElevation": { "title": "Require UAC elevation for running Windhawk", "description": "Windhawk requires administrator rights, but for a single-user computer, getting the UAC prompt every time can be annoying, so Windhawk bypasses it. Enable this option to require UAC elevation for running Windhawk." }, "dontAutoShowToolkit": { "title": "Don't automatically show the toolkit dialog", "description": "By default, Windhawk automatically shows the toolkit dialog when it detects that the taskbar isn't accessible, either due to system instability or for another reason. The toolkit dialog allows you to exit Windhawk, switch to safe mode, and perform other operations. The toolkit dialog can also be shown with the Ctrl+Win+W keyboard shortcut." }, "modInitDialogDelay": { "title": "Mod initialization dialog delay", "description": "Number of milliseconds to wait before showing the progress dialog for mod initialization." }, "moreAdvancedSettings": { "title": "More advanced settings", "restartNotice": "Windhawk will be restarted to apply the settings.", "saveButton": "Save and restart Windhawk", "cancelButton": "Cancel" }, "loggingVerbosity": { "appLoggingTitle": "Windhawk logging verbosity", "engineLoggingTitle": "Windhawk engine logging verbosity", "description": "Logs can be viewed with a tool such as DebugView.", "none": "None", "error": "Error", "verbose": "Verbose" }, "processList": { "titleExclusion": "Process exclusion list", "descriptionExclusion": "A list of process names/paths that Windhawk won't inject into. Can be useful for cases when Windhawk isn't compatible with a specific program. Note that adding a process to this list will not only prevent Windhawk from customizing it, but will also prevent Windhawk from intercepting child processes that this process creates. This will cause Windhawk to load mods with a slight delay in such cases.", "descriptionExclusionWiki": "For more details about excluding processes and about the built-in process exclusion lists, please refer to <0>the documentation.", "excludeCriticalProcesses": "Exclude critical system processes", "excludeIncompatiblePrograms": "Exclude known incompatible programs", "excludeGames": "Exclude well-known games", "titleInclusion": "Process inclusion list", "descriptionInclusion": "A list of process names/paths that Windhawk will inject into, even if they're in the exclusion list.", "inclusionWithoutExclusionNotice": "The process inclusion list has no effect with an empty process exclusion list.", "inclusionWithoutTotalExclusionNotice": "If you meant to exclude all processes but these, you can set \"*\" in the process exclusion list.", "processListPlaceholder": "Process names/paths, one per line, for example:", "invalidCharactersWarning": "The process list contains invalid characters which will be stripped: {{invalidCharacters}}" } }, "about": { "title": "Windhawk v{{version}}", "beta": "beta", "subtitle": "The customization marketplace for Windows and programs", "credit": "By <0>{{author}}", "update": { "title": "An update is available", "subtitle": "Consider updating Windhawk to get the latest features and bug fixes", "changelogButton": "Changelog", "updateButton": "Update", "modal": { "title": "Updating Windhawk", "downloading": "Downloading update...", "installing": "Installing update...", "installingNote": "Windhawk will restart automatically when the installation is complete.", "failed": "Update failed", "cancel": "Cancel" } }, "changelog": { "title": "What's New in Windhawk", "close": "Close" }, "links": { "title": "Links", "homepage": "Homepage", "documentation": "Documentation" }, "builtWith": { "title": "Built with", "vscodium": "A community-driven distribution of Microsoft's VSCode editor", "llvmMingw": "An LLVM/Clang/LLD based mingw-w64 toolchain", "minHook": "The minimalistic API hooking library for Windows", "others": "Other tools and libraries, and a bit of code" } }, "installModal": { "title": "Install {{mod}}", "warningTitle": "Proceed with care", "warningDescription": "Malicious mods can damage your computer or violate your privacy. Install mods only from authors whom you trust.", "modAuthor": "Mod author", "homepage": "Homepage", "github": "GitHub", "twitter": "X (Twitter)", "verified": "verified", "verifiedTooltip": "We verified that this profile belongs to the mod author, but note that <0>verified is not the same as <0>trusted. Make sure you trust the mod author, or carefully inspect the source code before installing.", "acceptButton": "Accept Risk and Install", "cancelButton": "Cancel" }, "createNewModButton": { "title": "Create a New Mod" }, "devModeAction": { "message": "Creating and modifying mods requires some knowledge of C/C++ development for Windows. If you're not sure what that means, perhaps these options are not for you.", "hideOptionsCheckbox": "Hide all development-related options", "hideOptionsButton": "Hide options", "beginCodingButton": "Begin coding", "cancelButton": "Cancel" }, "safeMode": { "alert": "Windhawk is running in safe mode, all code injection functionality is turned off.", "offButton": "Turn off safe mode", "offConfirm": "Windhawk will be restarted to apply the settings.", "offConfirmOk": "Restart Windhawk", "offConfirmCancel": "Cancel" }, "modPreview": { "actionUnavailable": "Action is not available in preview mode", "notCompiled": "Mod needs to be compiled before it can be previewed" }, "sidebar": { "modId": "Mod identifier", "enableMod": "Enable mod", "enableLogging": "Enable logging", "notCompiled": "Mod needs to be compiled", "compile": "Compile Mod", "compilationFailed": "Compilation failed", "stopCompilation": "Stop compilation", "preview": "Preview Mod", "showLogOutput": "Show Log Output", "exit": "Exit Editing Mode", "exitConfirmation": "Changes since the last successful compilation will be lost", "exitButtonOk": "Exit", "exitButtonCancel": "Stay" } } ================================================ FILE: src/vscode-windhawk-ui/apps/vscode-windhawk-ui/src/locales/es/DO_NOT_EDIT.txt ================================================ The files in this folder are generated automatically. To submit a translation update, please refer to this repository: https://github.com/ramensoftware/windhawk-translate ================================================ FILE: src/vscode-windhawk-ui/apps/vscode-windhawk-ui/src/locales/es/translation.json ================================================ { "general": { "loading": "Cargando...", "loadingFailed": "La carga falló, verifique su conexión a Internet", "loadingFailedTitle": "Carga fallida", "loadingFailedSubtitle": "Comprueba tu conexión a Internet e inténtalo de nuevo", "tryAgain": "Intentar otra vez", "updating": "Actualizando...", "installing": "Instalando...", "compiling": "Compilando...", "cut": "Cortar", "copy": "Copiar", "paste": "Pegar", "selectAll": "Seleccionar todo" }, "appHeader": { "home": "Inicio", "explore": "Explorar", "settings": "Ajustes", "about": "Acerca de" }, "mod": { "updateAvailable": "Actualización disponible", "installed": "Instalado", "details": "Detalles", "update": "Actualizar", "install": "Instalar", "compile": "Compilar", "disable": "Desactivar", "enable": "Activar", "edit": "Editar", "fork": "Bifurcación", "remove": "Eliminar", "removeConfirm": "¿Estás seguro de que quieres eliminar este mod?", "removeConfirmOk": "Quitar mod", "removeConfirmCancel": "Cancelar", "notCompiled": "Mod necesita ser compilado", "editedLocally": "Mod fue editado localmente", "noDescription": "(Sin descripción)", "users_one": "{{formattedCount}} usuario", "users_many": "{{formattedCount}} usuarios", "users_other": "{{formattedCount}} usuarios" }, "modDetails": { "header": { "installedVersion": "Versión instalada", "latestVersion": "Ultima versión", "loading": "cargando...", "loadingFailed": "carga fallida", "modId": "Identificador del mod", "modVersion": "Versión del mod", "modAuthor": { "title": "Autor del mod", "homepage": "Página principal", "github": "GitHub", "twitter": "X (Twitter)" }, "processes": { "all": "Todos los procesos", "allBut": "Todo pero {{list}}", "except": "{{included}} excepto {{excluded}}", "tooltip": { "targets": "Procesos de destino", "excluded": "Excluido" } }, "updateNotNeeded": "La versión instalada es idéntica a la última versión" }, "details": { "title": "Detalles", "noData": "Faltan detalles de mod." }, "settings": { "title": "Ajustes", "noData": "No hay configuraciones de mod disponibles.", "saveButton": "Guardar ajustes", "sampleValue": "Valor de muestra", "arrayItemAdd": "Agregar nuevo elemento", "arrayItemRemove": "Remover elemento" }, "code": { "title": "Código fuente", "noData": "Falta la fuente de mod.", "collapseExtra": "Ocultar Léame y Configuración" }, "changelog": { "title": "Registro de cambios", "loadingFailed": "Error al cargar el registro de cambios. <0>Haga clic aquí para verlo en GitHub." }, "advanced": { "title": "Avanzado", "debugLogging": { "title": "Registro de depuración", "description": "Puede ser útil para solucionar problemas con el mod.", "none": "Ninguno", "modLogs": "Registros de mod", "detailedLogs": "Registros de depuración detallados", "showLogButton": "Mostrar salida de registro" }, "modSettings": { "title": "Configuración de mod", "description": "Exporte o comparta fácilmente la configuración de su mod.", "loadButton": "Cargar", "loadFormattedButton": "Cargar formateado", "saveButton": "Guardar", "invalidData": "Datos JSON no válidos" }, "customList": { "titleInclusion": "Lista de inclusión de procesos personalizados", "descriptionInclusion": "Una lista personalizada de nombres/rutas de archivos ejecutables adicionales a los que apuntará el mod. La lista se agrega a la lista de inclusión que el autor de la modificación definió para determinar a qué procesos apuntar. Para cada proceso, el mod se carga si la ruta del archivo ejecutable coincide con una de las entradas de inclusión y no coincide con ninguna entrada de exclusión.", "titleExclusion": "Lista de exclusión de procesos personalizados", "descriptionExclusion": "Una lista personalizada de nombres/rutas de archivos ejecutables adicionales que el mod excluirá de la orientación. La lista se agrega a la lista de exclusión que el autor del mod definió para determinar a qué procesos apuntar. Para cada proceso, el mod se carga si la ruta del archivo ejecutable coincide con una de las entradas de inclusión y no coincide con ninguna entrada de exclusión.", "processListPlaceholder": "Nombres/rutas de procesos, uno por línea, por ejemplo:", "invalidCharactersWarning": "La lista de procesos incluye caracteres inválidos, que serán eliminados: {{invalidCharacters}}", "saveButton": "Guardar" }, "includeExcludeCustomOnly": { "title": "Ignorar listas de inclusión/exclusión del mod.", "description": "Ignora las listas de inclusión/exclusión del mod y utiliza solo las listas personalizadas anteriores." }, "patternsMatchCriticalSystemProcesses": { "title": "Considerar patrones de lista de inclusión para procesos críticos del sistema.", "description": "Por defecto, Windhawk solo carga mods en procesos críticos si solo está incluido en la lista sin *, por ejemplo: <0>critico.exe pero no <0>* o <0>*.exe. Para más detalles, por favor consulte <1>la documentación." } }, "changes": { "title": "Últimos cambios de versión", "noData": "La versión instalada es idéntica a la última versión.", "splitView": "Vista dividida", "expandLines_one": "Expandir una línea oculta", "expandLines_many": "Expandir {{count}} líneas ocultas", "expandLines_other": "Expandir {{count}} líneas ocultas" } }, "modSearch": { "placeholder": "Buscar en mods..." }, "home": { "browse": "Explorar los Mod", "installedMods": { "title": "Mods instalados", "noMods": "No hay mods instalados" }, "featuredMods": { "title": "Mods en primer plano", "noMods": "No hay mods destacados que aún no se hayan instalado.", "explore": "Explorar otros Mods" } }, "explore": { "search": { "popularAndTopRated": "Popular y mejor valorado", "popular": "Popular", "topRated": "Los más valorados", "newest": "El más nuevo", "lastUpdated": "Última actualización", "alphabeticalOrder": "Orden alfabetico" } }, "settings": { "language": { "title": "Idioma", "description": "Seleccione su idioma de visualización preferido para Windhawk.", "contribute": "<0>Aportar una nueva traducción.", "credits": "Traducción al español por <0>Alfonso Guerrero.", "creditsLink": "mailto:alfonsoguerrerogallego@gmail.com" }, "updates": { "title": "Buscar actualizaciones", "description": "Compruebe periódicamente si hay nuevas versiones de Windhawk y de los mods instalados." }, "devMode": { "title": "Modo desarrollador", "description": "Mostrar acciones para desarrolladores, como crear y modificar mods." }, "advancedSettings": "Ajustes avanzados", "hideTrayIcon": { "title": "Ocultar el icono del área de notificaciones", "description": "Tendrás que deshabilitar esta opción para salir de Windhawk." }, "requireElevation": { "title": "Requerir elevación de UAC para ejecutar Windhawk", "description": "Windhawk requiere derechos de administrador, pero para una computadora de un solo usuario, obtener el aviso de UAC cada vez puede ser molesto, por lo que Windhawk lo omite. Habilite esta opción para requerir la elevación de UAC para ejecutar Windhawk." }, "dontAutoShowToolkit": { "title": "No mostrar automáticamente el cuadro de diálogo del kit de herramientas", "description": "De forma predeterminada, Windhawk muestra automáticamente el cuadro de diálogo del kit de herramientas cuando detecta que no se puede acceder a la barra de tareas, ya sea debido a la inestabilidad del sistema o por otra razón. El cuadro de diálogo del kit de herramientas permite salir de Windhawk, cambiar al modo seguro y realizar otras operaciones. El cuadro de diálogo del kit de herramientas también se puede mostrar con el atajo de teclado Ctrl+Win+W." }, "modInitDialogDelay": { "title": "Retraso en la inicialización del diálogo de Mod", "description": "Cantidad de milisegundos a esperar antes de mostrar el diálogo de progreso para la inicialización del mod." }, "moreAdvancedSettings": { "title": "Más configuraciones avanzadas", "restartNotice": "Windhawk se reiniciará para aplicar la configuración.", "saveButton": "Guardar y reiniciar Windhawk", "cancelButton": "Cancelar" }, "loggingVerbosity": { "appLoggingTitle": "Detalle de registro de Windhawk", "engineLoggingTitle": "Detalle de registro del motor Windhawk", "description": "Los registros se pueden ver con una herramienta como DebugView.", "none": "Ninguno", "error": "Error", "verbose": "Detallado" }, "processList": { "titleExclusion": "Lista de exclusión de procesos", "descriptionExclusion": "Una lista de nombres de procesos/rutas en las que Windhawk no inyectará. Puede ser útil para casos en los que Windhawk no es compatible con un programa específico. Tenga en cuenta que agregar un proceso a esta lista no solo evitará que Windhawk lo personalice, sino que también evitará que Windhawk intercepte los procesos secundarios que crea este proceso. Esto hará que Windhawk cargue mods con un ligero retraso en tales casos.", "descriptionExclusionWiki": "Para más detalles sobre la exclusión de procesos y sobre las listas de exclusión de procesos integradas, consulte <0>la documentación.", "excludeCriticalProcesses": "Excluir programas críticos del sistema", "excludeIncompatiblePrograms": "Excluir programas incompatibles conocidos", "excludeGames": "Excluir juegos conocidos", "titleInclusion": "Lista de inclusión de procesos", "descriptionInclusion": "Una lista de nombres de procesos/rutas en las que se inyectará Windhawk, incluso si están en la lista de exclusión.", "inclusionWithoutExclusionNotice": "La lista de inclusión de procesos no tiene efecto con una lista de exclusión de procesos vacía.", "inclusionWithoutTotalExclusionNotice": "Si pretendía excluir todos los procesos menos estos, puede establecer \"*\" en la lista de exclusión de procesos.", "processListPlaceholder": "Nombres/rutas de procesos, uno por línea, por ejemplo:", "invalidCharactersWarning": "La lista de procesos contiene caracteres no válidos que serán eliminados: {{invalidCharacters}}" } }, "about": { "title": "Windhawk v{{version}}", "beta": "beta", "subtitle": "El mercado de personalización para programas de Windows", "credit": "Por <0>{{author}}", "update": { "title": "Hay disponible una actualización", "subtitle": "Considere actualizar Windhawk para obtener las últimas funciones y correcciones de errores", "updateButton": "Más detalles" }, "links": { "title": "Links", "homepage": "Página de inicio", "documentation": "Documentación" }, "builtWith": { "title": "Diseñado con", "vscodium": "Una distribución impulsada por la comunidad del editor VSCode de Microsoft", "llvmMingw": "Una cadena de herramientas mingw-w64 basada en LLVM/Clang/LLD", "minHook": "La biblioteca minimalista de conexión de API para Windows", "others": "Otras herramientas y bibliotecas, y un poco de código." } }, "installModal": { "title": "Instalar {{mod}}", "warningTitle": "Proceder con cuidado", "warningDescription": "Las modificaciones maliciosas pueden dañar su computadora o violar su privacidad. Instale mods solo de autores en los que confíe.", "modAuthor": "Autor de mod", "homepage": "Página principal", "github": "GitHub", "twitter": "X (Twitter)", "verified": "Verificado", "verifiedTooltip": "Verificamos que este perfil pertenece al autor del mod, pero tenga en cuenta que <0>verificado no es lo mismo que <0>de confianza. Asegúrese de confiar en el autor del mod o inspeccione cuidadosamente el código fuente antes de instalar.", "acceptButton": "Aceptar el riesgo e instalar", "cancelButton": "Cancelar" }, "createNewModButton": { "title": "Crear un nuevo Mod" }, "devModeAction": { "message": "La creación y modificación de mods requiere cierto conocimiento del desarrollo de C/C++ para Windows. Si no está seguro de lo que eso significa, tal vez estas opciones no sean para usted.", "hideOptionsCheckbox": "Ocultar todas las opciones relacionadas con el desarrollo", "hideOptionsButton": "Ocultar opciones", "beginCodingButton": "Empezar a codificar", "cancelButton": "Cancelar" }, "safeMode": { "alert": "Windhawk se está ejecutando en modo seguro, todas las funciones de inyección de código están desactivadas.", "offButton": "Desactivar modo seguro", "offConfirm": "Windhawk se actualizará para aplicar la configuración.", "offConfirmOk": "Reiniciar Windhawk", "offConfirmCancel": "Cancelar" }, "modPreview": { "actionUnavailable": "La acción no está disponible en el modo de vista previa", "notCompiled": "El mod debe compilarse antes de que pueda obtener una vista previa" }, "sidebar": { "modId": "Identificador de mod", "enableMod": "Habilitar mod", "enableLogging": "Habilitar el registro", "notCompiled": "Mod necesita ser compilado", "compile": "Compilar el mod", "compilationFailed": "Compilación fallida", "preview": "Previsualizar el Mod", "showLogOutput": "Mostrar salida de registro", "exit": "Salir del modo de edición", "exitConfirmation": "Los cambios desde la última compilación exitosa se perderán", "exitButtonOk": "Salir", "exitButtonCancel": "Permanecer" } } ================================================ FILE: src/vscode-windhawk-ui/apps/vscode-windhawk-ui/src/locales/fr/DO_NOT_EDIT.txt ================================================ The files in this folder are generated automatically. To submit a translation update, please refer to this repository: https://github.com/ramensoftware/windhawk-translate ================================================ FILE: src/vscode-windhawk-ui/apps/vscode-windhawk-ui/src/locales/fr/translation.json ================================================ { "general": { "loading": "Chargement...", "loadingFailed": "Le chargement a échoué, vérifiez votre connexion à internet", "loadingFailedTitle": "Le chargement a échoué", "loadingFailedSubtitle": "Vérifiez votre connexion à internet et réessayez", "tryAgain": "Réessayer", "updating": "Mise à jour en cours...", "installing": "Installation en cours...", "compiling": "Compilation en cours...", "cut": "Couper", "copy": "Copier", "paste": "Coller", "selectAll": "Tout sélectionner" }, "appHeader": { "home": "Accueil", "explore": "Explorer", "settings": "Paramètres", "about": "À propos" }, "mod": { "updateAvailable": "Mise à jour disponible", "installed": "Installé", "details": "Détails", "update": "Mettre à jour", "install": "Installer", "compile": "Compiler", "disable": "Désactiver", "enable": "Activer", "edit": "Éditer", "fork": "Fork", "remove": "Supprimer", "removeConfirm": "Êtes-vous sûr de vouloir supprimer ce mod ?", "removeConfirmOk": "Supprimer le mod", "removeConfirmCancel": "Annuler", "notCompiled": "Le mod a besoin d'être compilé", "editedLocally": "Le mod a été édité localement", "noDescription": "(aucune description)", "users_one": "{{formattedCount}} utilisateur", "users_many": "{{formattedCount}} utilisateurs", "users_other": "{{formattedCount}} utilisateurs" }, "modDetails": { "header": { "installedVersion": "Version installée", "latestVersion": "Dernière version", "loading": "Chargement...", "loadingFailed": "Le chargement a échoué", "modId": "Identifiant du mod", "modVersion": "Version du mod", "modAuthor": { "title": "Auteur du mod", "homepage": "Page d'accueil", "github": "GitHub", "twitter": "X (Twitter)" }, "processes": { "all": "Tous les processus", "allBut": "Tous sauf {{list}}", "except": "{{included}} excepté {{excluded}}", "tooltip": { "targets": "Processus ciblés", "excluded": "Exclus" } }, "updateNotNeeded": "La version installée est identique à la dernière version" }, "details": { "title": "Détails", "noData": "Les détails du mod sont manquants." }, "settings": { "title": "Paramètres", "noData": "Aucun paramètre disponible pour ce mod", "saveButton": "Sauvegarder les paramètres", "sampleValue": "Valeur de l'échantillon", "arrayItemAdd": "Ajouter un nouvel élément", "arrayItemRemove": "Supprimer l'élément" }, "code": { "title": "Code source", "noData": "Les sources du mod sont manquantes.", "collapseExtra": "Réduire les Readme et les paramètres" }, "changelog": { "title": "Journal des modifications", "loadingFailed": "Impossible de charger le journal des modifications. <0>Cliquez ici pour le voir sur GitHub." }, "advanced": { "title": "Avancé", "debugLogging": { "title": "Journalisation du débogage", "description": "Peut-être utile pour résoudre les problèmes avec le mod.", "none": "Aucun", "modLogs": "Logs du mod", "detailedLogs": "Logs de débug détaillés", "showLogButton": "Montrer les sorties de log" }, "modSettings": { "title": "Paramètres du mod", "description": "Exportez ou partagez facilement vos paramètres de mod.", "loadButton": "Charger", "loadFormattedButton": "Chargement formaté", "saveButton": "Sauvegarder", "invalidData": "Data JSON invalide" }, "customList": { "titleInclusion": "Liste d'inclusion de processus personnalisés", "descriptionInclusion": "Une liste personnalisée de noms/chemins de processus supplémentaires que le mod va inclure dans le ciblage. Cette liste est ajoutée à la liste d'inclusion que l'auteur du mod a défini pour déterminer quel processus cibler. Pour chaque processus, le mod est chargé si le chemin du processus correspond à l'un de ceux inclus et ne correspond à aucune de ceux exclus.", "titleExclusion": "Liste de processus exclus personnalisée", "descriptionExclusion": "Une liste personnalisée de noms/chemins de processus supplémentaires que le mod va exclure du ciblage. Cete liste est ajoutée à la liste d'exclusion que l'auteur du mod a défini pour déterminer quel processus cibler. Pour chaque processus, le mod est chargé si le chemin du processus correspond à l'un de ceux inclus et ne correspond à aucune de ceux exclus.", "processListPlaceholder": "Noms/chemins des processus, un par ligne, par exemple :", "saveButton": "Sauvegarder" }, "includeExcludeCustomOnly": { "title": "Ignorer les listes d'inclusion/exclusion de mods", "description": "Ignorer les listes d'inclusion/exclusion de processus de mods et n'utiliser que les listes personnalisées ci-dessus." } }, "changes": { "title": "Changements de la dernière version", "noData": "La version installée est identique à la dernière version.", "splitView": "Séparer les vues", "expandLines_one": "Développer une ligne cachée", "expandLines_many": "Développer {{count}} lignes cachées", "expandLines_other": "Développer {{count}} lignes cachées" } }, "modSearch": { "placeholder": "Rechercher des mods..." }, "home": { "browse": "Parcourir les mods", "installedMods": { "title": "Mods installés", "noMods": "Aucun mod n'est installé" }, "featuredMods": { "title": "Mods mis en avant", "noMods": "Il n'y a aucun mod mis en avant qui n'ait pas été installé", "explore": "Explorer les autres mods" } }, "explore": { "search": { "popularAndTopRated": "Populaires et mieux notés", "popular": "Populaires", "topRated": "Mieux notés", "newest": "Récents", "lastUpdated": "Mis a jour récemment", "alphabeticalOrder": "Ordre alphabétique" } }, "settings": { "language": { "title": "Langue", "description": "Sélectionnez votre langue préférée pour Windhawk.", "contribute": "<0>Contribuer pour une nouvelle traduction.", "credits": "Traduction française par <0>Jeremy Clement.", "creditsLink": "mailto:clement.jeremy.pro@outlook.fr" }, "updates": { "title": "Vérifier les mises à jours", "description": "Vérifier périodiquement l'existance de nouvelles versions de Windhawk et des mods installés." }, "devMode": { "title": "Mode développeur", "description": "Afficher les actions pour développeur, comme créer et modifier des mods." }, "advancedSettings": "Paramètres avancés", "hideTrayIcon": { "title": "Cacher l'icône de la zone de notification", "description": "Vous devez désactiver cette option pour quitter Windhawk." }, "requireElevation": { "title": "Contrôle de compte d’utilisateur (UAC) requis pour lancer Windhawk", "description": "Windhawk requière des droits administrateur, mais pour un ordinateur ne comportant qu'un seul utilisateur, avoir l'invite de contrôle du compte d'utilisateur (UAC) à chaque fois peut être ennuyant, donc Windhawk la contourne. Activer cette option pour exiger l'invite de contrôle du compte de l'utilisateur (UAC) pour lancer Windhawk." }, "dontAutoShowToolkit": { "title": "Ne pas automatiquement afficher la boîte de dialogue de la boîte à outils", "description": "Par défaut, Windhawk affiche automatiquement la boîte de dialogue de la boîte à outils quand il détecte que la barre des tâches n'est pas accessible, à cause d'instabilités système ou autre. La boîte de dialogue permet de quitter Windhawk, passer en mode sans échec et effectuer d'autres opérations. Elle peut également être afficher à l'aide du raccourci clavier Ctrl+Win+W." }, "modInitDialogDelay": { "title": "Délai de dialogue d'initialisation du mod", "description": "Temps en millisecondes à attendre avant d'afficher les boîte de dialogue de progression pour initialiser le mod." }, "moreAdvancedSettings": { "title": "Plus de paramètres avancés", "restartNotice": "Windhawk va être redémarré pour appliquer les paramètres.", "saveButton": "Sauvegarder et redémarrer Windhawk", "cancelButton": "Annuler" }, "loggingVerbosity": { "appLoggingTitle": "Verbosité de la journalisation Windhawk", "engineLoggingTitle": "Verbosité de la journalisation du moteur Windhawk", "description": "Les journaux peuvent être visualisés avec un outil tel que DebugView.", "none": "Aucun", "error": "Erreur", "verbose": "Verbosité" }, "processList": { "titleExclusion": "Liste d'exclusion de processus", "descriptionExclusion": "Une liste personnalisée de noms/chemins de processus dans lesquels Windhawk ne pourra rien injecter. Cela peut être utile dans les cas où Windhawk n'est pas compatible avec un programme spécifique. Notez qu'ajouter un processus à cette liste empêchera non seulement Windhawk de le personnaliser, mais empêchera également Windhawk d'intercepter les processus enfants créés par ce processus. Cela forcera Windhawk à charger les mods avec un léger retardement dans de tels cas.", "titleInclusion": "Liste d'exclusion de processus", "descriptionInclusion": "Une liste de noms/chemins de processus dans lesquels Windhawk vas pouvoir injecter du code, même s'ils sont dans la liste d'exclusion.", "inclusionWithoutExclusionNotice": "La liste d'inclusion de processus n'a aucun effet avec une liste d'exclusion de processus vide.", "inclusionWithoutTotalExclusionNotice": "Si vous vouliez exclure tous les processus sauf ceux-ci, vous pouvez définir \"*\" dans la liste des processus exclus.", "processListPlaceholder": "Noms/chemins des processus, un par ligne, par exemple :" } }, "about": { "title": "Windhawk v{{version}}", "beta": "bêta", "subtitle": "La place du marché de customisations pour les programmes Windows", "credit": "Par <0>{{author}}", "update": { "title": "Une mise à jour est disponible", "subtitle": "Mettez à jour Windhawk pour obtenir les dernières fonctionnalités et les corrections de bugs", "updateButton": "Plus de détails" }, "links": { "title": "Liens", "homepage": "Page d'accueil", "documentation": "Documentation" }, "builtWith": { "title": "Conçu avec", "vscodium": "Une distribution de l'IDE Microsoft VSCode gérée par la communauté", "llvmMingw": "Une chaîne d'outils mingw-w64 basée sur LLVM/Clang/LLD", "minHook": "La bibliothèque de raccordement d'API minimaliste pour Windows", "others": "Autres outils et librairies, et un peu de code" } }, "installModal": { "title": "Installer {{mod}}", "warningTitle": "Procéder avec soin", "warningDescription": "Des mods malicieux peuvent endommager votre ordinateur et violer votre vie privée. Installez des mods provenant seulement d'auteurs en qui vous avez confiance.", "modAuthor": "Auteur du mod", "homepage": "Page d'accueil", "github": "GitHub", "twitter": "X (Twitter)", "verified": "vérifié", "verifiedTooltip": "Nous avons vérifié que ce profil appartient à l'auteur du mod, mais prenez en considération que la note <0>verifié n'est pas la même que la note <0>créateur de confiance. Soyez sûr d'avoir confiance en l'auteur du mod, or d'analyser avec soin le code source du mod avant de l'installer.", "acceptButton": "Accepter les risques et installer", "cancelButton": "Annuler" }, "createNewModButton": { "title": "Créer un nouveau mod" }, "devModeAction": { "message": "Créer et modifier des mods demande quelques connaissances de développement dans les langages C/C++ pour Windows. Si vous n'êtes pas sûr de ce que cela signfie, ces options ne sont peut-être pas pour vous.", "hideOptionsCheckbox": "Cacher toutes les options de développement", "hideOptionsButton": "Cacher les options", "beginCodingButton": "Commencer le développement", "cancelButton": "Annuler" }, "safeMode": { "alert": "Windhawk s'exécute en mode sans échec, toutes les fonctions d'injection de code sont désactivées", "offButton": "Désactiver le mode sans échec", "offConfirm": "Windhawk va devoir redémarrer pour appliquer les paramètres", "offConfirmOk": "Redémarrer Windhawk", "offConfirmCancel": "Annuler" }, "modPreview": { "actionUnavailable": "L'action n'est pas disponible en mode prévisualisation", "notCompiled": "Le mod a besoin d'être compilé avant d'être prévisualisé" }, "sidebar": { "modId": "Identifiant du mod", "enableMod": "Activer le mod", "enableLogging": "Activer la journalisation", "notCompiled": "Le mod a besoin d'être compilé", "compile": "Compiler le mod", "compilationFailed": "La compilation a échoué", "preview": "Prévisualier le mod", "showLogOutput": "Montrer les sorties de log", "exit": "Quitter le mode d'édition", "exitConfirmation": "Les changements effectués depuis la dernière compilation réussie vont être perdus", "exitButtonOk": "Quitter", "exitButtonCancel": "Rester" } } ================================================ FILE: src/vscode-windhawk-ui/apps/vscode-windhawk-ui/src/locales/hi/DO_NOT_EDIT.txt ================================================ The files in this folder are generated automatically. To submit a translation update, please refer to this repository: https://github.com/ramensoftware/windhawk-translate ================================================ FILE: src/vscode-windhawk-ui/apps/vscode-windhawk-ui/src/locales/hi/translation.json ================================================ { "general": { "loading": "लोड हो रहा है...", "loadingFailed": "लोडिंग विफल, कृपया अपना इंटरनेट कनेक्शन जांचें", "loadingFailedTitle": "लोडिंग विफल", "loadingFailedSubtitle": "कृपया अपना इंटरनेट कनेक्शन जांचें और फिर से प्रयास करें", "tryAgain": "फिर से प्रयास करें", "updating": "अपडेट हो रहा है...", "installing": "स्थापित हो रहा है...", "compiling": "कंपाइल हो रहा है...", "cut": "काटें", "copy": "कॉपी करें", "paste": "पेस्ट करें", "selectAll": "सभी चुनें" }, "appHeader": { "home": "होम", "explore": "अन्वेषण करें", "settings": "सेटिंग्स", "about": "बारे में" }, "mod": { "updateAvailable": "अपडेट उपलब्ध है", "installed": "स्थापित", "details": "विवरण", "update": "अपडेट करें", "install": "स्थापित करें", "compile": "कंपाइल करें", "disable": "अक्षम करें", "enable": "सक्षम करें", "edit": "संपादित करें", "fork": "फोर्क करें", "remove": "हटाएँ", "removeConfirm": "क्या आप वाकई इस मॉड को हटाना चाहते हैं?", "removeConfirmOk": "मॉड हटाएँ", "removeConfirmCancel": "रद्द करें", "notCompiled": "मॉड को कंपाइल करने की आवश्यकता है", "editedLocally": "मॉड को स्थानीय रूप से संपादित किया गया था", "noDescription": "(कोई विवरण नहीं)", "users_one": "{{formattedCount}} उपयोगकर्ता", "users_other": "{{formattedCount}} उपयोगकर्ता" }, "modDetails": { "header": { "installedVersion": "स्थापित संस्करण", "latestVersion": "नवीनतम संस्करण", "loading": "लोड हो रहा है...", "loadingFailed": "लोडिंग विफल", "modId": "मॉड पहचानकर्ता", "modVersion": "मॉड संस्करण", "modAuthor": { "title": "मॉड लेखक", "homepage": "होमपेज", "github": "GitHub", "twitter": "X (ट्विटर)" }, "processes": { "all": "सभी प्रक्रियाएँ", "allBut": "{{list}} को छोड़कर सभी", "except": "{{included}} को छोड़कर {{excluded}}", "tooltip": { "targets": "लक्ष्य प्रक्रियाएँ", "excluded": "बहिष्कृत" } }, "updateNotNeeded": "स्थापित संस्करण नवीनतम संस्करण के समान है" }, "details": { "title": "विवरण", "noData": "मॉड विवरण अनुपस्थित हैं।" }, "settings": { "title": "सेटिंग्स", "noData": "कोई मॉड सेटिंग्स उपलब्ध नहीं हैं।", "saveButton": "सेटिंग्स सहेजें", "sampleValue": "नमूना मान", "arrayItemAdd": "नया आइटम जोड़ें", "arrayItemRemove": "आइटम हटाएँ" }, "code": { "title": "स्रोत कोड", "noData": "मॉड स्रोत अनुपस्थित है।", "collapseExtra": "रीडमी और सेटिंग्स संक्षिप्त करें" }, "changelog": { "title": "परिवर्तन लॉग", "loadingFailed": "परिवर्तन लॉग लोड करने में विफल। <0>GitHub पर देखने के लिए यहाँ क्लिक करें।" }, "advanced": { "title": "उन्नत", "debugLogging": { "title": "डीबग लॉगिंग", "description": "मॉड के साथ समस्याओं के निवारण के लिए उपयोगी हो सकता है।", "none": "कोई नहीं", "modLogs": "मॉड लॉग", "detailedLogs": "विस्तृत डीबग लॉग", "showLogButton": "लॉग आउटपुट दिखाएँ" }, "modSettings": { "title": "मॉड सेटिंग्स", "description": "अपनी मॉड सेटिंग्स को आसानी से निर्यात या साझा करें।", "loadButton": "लोड करें", "loadFormattedButton": "स्वरूपित लोड करें", "saveButton": "सहेजें", "invalidData": "अमान्य JSON डेटा" }, "customList": { "titleInclusion": "कस्टम प्रक्रिया समावेशन सूची", "descriptionInclusion": "अतिरिक्त निष्पादन योग्य फ़ाइल नामों/पथों की एक कस्टम सूची, जिन्हें मॉड लक्षित करेगा। यह सूची मॉड लेखक द्वारा परिभाषित समावेशन सूची में जोड़ी जाती है ताकि यह निर्धारित किया जा सके कि किन प्रक्रियाओं को लक्षित करना है। प्रत्येक प्रक्रिया के लिए, मॉड तब लोड होता है जब निष्पादन योग्य फ़ाइल पथ समावेशन प्रविष्टियों में से किसी एक से मेल खाता है और किसी भी बहिष्करण प्रविष्टि से मेल नहीं खाता।", "titleExclusion": "कस्टम प्रक्रिया बहिष्करण सूची", "descriptionExclusion": "अतिरिक्त निष्पादन योग्य फ़ाइल नामों/पथों की एक कस्टम सूची, जिन्हें मॉड लक्षित करने से बहिष्कृत करेगा। यह सूची मॉड लेखक द्वारा परिभाषित बहिष्करण सूची में जोड़ी जाती है ताकि यह निर्धारित किया जा सके कि किन प्रक्रियाओं को लक्षित करना है। प्रत्येक प्रक्रिया के लिए, मॉड तब लोड होता है जब निष्पादन योग्य फ़ाइल पथ समावेशन प्रविष्टियों में से किसी एक से मेल खाता है और किसी भी बहिष्करण प्रविष्टि से मेल नहीं खाता।", "processListPlaceholder": "प्रक्रिया नाम/पथ, प्रति पंक्ति एक, उदाहरण के लिए:", "invalidCharactersWarning": "प्रक्रिया सूची में अमान्य वर्ण हैं जो हटा दिए जाएंगे: {{invalidCharacters}}", "saveButton": "सहेजें" }, "includeExcludeCustomOnly": { "title": "मॉड समावेशन/बहिष्करण सूचियों को अनदेखा करें", "description": "मॉड की प्रक्रिया समावेशन/बहिष्करण सूचियों को अनदेखा करें और केवल उपरोक्त कस्टम सूचियों का उपयोग करें।" }, "patternsMatchCriticalSystemProcesses": { "title": "महत्वपूर्ण सिस्टम प्रक्रियाओं के लिए समावेशन सूची पैटर्न पर विचार करें", "description": "डिफ़ॉल्ट रूप से, विंडहॉक केवल तभी महत्वपूर्ण सिस्टम प्रक्रियाओं में मॉड्स लोड करता है जब प्रक्रिया बिना पैटर्न के शामिल हो, जैसे <0>critical.exe, न कि <0>* या <0>*.exe। महत्वपूर्ण सिस्टम प्रक्रियाओं के बारे में अधिक जानकारी के लिए, कृपया <1>दस्तावेज़ीकरण देखें।" } }, "changes": { "title": "नवीनतम संस्करण परिवर्तन", "noData": "स्थापित संस्करण नवीनतम संस्करण के समान है।", "splitView": "विभाजित दृश्य", "expandLines_one": "एक छिपी पंक्ति विस्तार करें", "expandLines_other": "{{count}} छिपी पंक्तियाँ विस्तार करें" } }, "modSearch": { "placeholder": "मॉड्स खोजें..." }, "home": { "browse": "मॉड्स के लिए ब्राउज़ करें", "installedMods": { "title": "स्थापित मॉड्स", "noMods": "कोई मॉड्स स्थापित नहीं हैं" }, "featuredMods": { "title": "विशेष मॉड्स", "noMods": "ऐसे कोई विशेष मॉड्स नहीं हैं जो अभी तक स्थापित नहीं किए गए हों", "explore": "अन्य मॉड्स का अन्वेषण करें" } }, "explore": { "search": { "popularAndTopRated": "लोकप्रिय और शीर्ष रेटेड", "popular": "लोकप्रिय", "topRated": "शीर्ष रेटेड", "newest": "नवीनतम", "lastUpdated": "अंतिम अपडेट", "alphabeticalOrder": "वर्णमाला क्रम" } }, "settings": { "language": { "title": "भाषा", "description": "विंडहॉक के लिए अपनी पसंदीदा प्रदर्शन भाषा चुनें।", "contribute": "<0>नया अनुवाद योगदान करें।", "credits": "<0>Sathwik Hejamady Bhat द्वारा अंग्रेजी अनुवाद।", "creditsLink": "mailto:sathwikhbhat@gmail.com" }, "updates": { "title": "अपडेट के लिए जांचें", "description": "विंडहॉक और स्थापित मॉड्स के नए संस्करणों की समय-समय पर जांच करें।" }, "devMode": { "title": "डेवलपर मोड", "description": "डेवलपर्स के लिए कार्रवाइयाँ दिखाएँ, जैसे मॉड्स बनाना और संशोधित करना।" }, "advancedSettings": "उन्नत सेटिंग्स", "hideTrayIcon": { "title": "ट्रे आइकन छिपाएँ", "description": "विंडहॉक को बंद करने के लिए आपको इस विकल्प को अक्षम करना होगा।" }, "requireElevation": { "title": "विंडहॉक चलाने के लिए UAC उन्नयन की आवश्यकता", "description": "विंडहॉक को व्यवस्थापक अधिकारों की आवश्यकता होती है, लेकिन एकल-उपयोगकर्ता कंप्यूटर के लिए, हर बार UAC प्रॉम्प्ट प्राप्त करना परेशान कर सकता है, इसलिए विंडहॉक इसे बायपास करता है। विंडहॉक चलाने के लिए UAC उन्नयन की आवश्यकता के लिए इस विकल्प को सक्षम करें।" }, "dontAutoShowToolkit": { "title": "टूलकिट डायलॉग स्वचालित रूप से न दिखाएँ", "description": "डिफ़ॉल्ट रूप से, विंडहॉक स्वचालित रूप से टूलकिट डायलॉग दिखाता है जब यह पता लगाता है कि टास्कबार सुलभ नहीं है, चाहे सिस्टम अस्थिरता के कारण हो या किसी अन्य कारण से। टूलकिट डायलॉग विंडहॉक को बंद करने, सुरक्षित मोड में स्विच करने और अन्य ऑपरेशनों की अनुमति देता है। टूलकिट डायलॉग को Ctrl+Win+W कीबोर्ड शॉर्टकट के साथ भी दिखाया जा सकता है।" }, "modInitDialogDelay": { "title": "मॉड प्रारंभिक डायलॉग विलंब", "description": "मॉड प्रारंभिक के लिए प्रगति डायलॉग दिखाने से पहले प्रतीक्षा करने के लिए मिलीसेकंड की मात्रा।" }, "moreAdvancedSettings": { "title": "अधिक उन्नत सेटिंग्स", "restartNotice": "सेटिंग्स लागू करने के लिए विंडहॉक पुनः शुरू होगा।", "saveButton": "सहेजें और विंडहॉक पुनः शुरू करें", "cancelButton": "रद्द करें" }, "loggingVerbosity": { "appLoggingTitle": "विंडहॉक लॉगिंग वर्बोसिटी", "engineLoggingTitle": "विंडहॉक इंजन लॉगिंग वर्बोसिटी", "description": "लॉग्स को डीबगव्यू जैसे टूल के साथ देखा जा सकता है।", "none": "कोई नहीं", "error": "त्रुटि", "verbose": "वर्बोस" }, "processList": { "titleExclusion": "प्रक्रिया बहिष्करण सूची", "descriptionExclusion": "प्रक्रिया नामों/पथों की एक सूची जिनमें विंडहॉक इंजेक्ट नहीं करेगा। यह उन मामलों में उपयोगी हो सकता है जब विंडहॉक किसी विशिष्ट प्रोग्राम के साथ संगत नहीं है। ध्यान दें कि इस सूची में प्रक्रिया जोड़ने से न केवल विंडहॉक को इसे अनुकूलित करने से रोका जाएगा, बल्कि यह विंडहॉक को इस प्रक्रिया द्वारा बनाई गई चाइल्ड प्रक्रियाओं को इंटरसेप्ट करने से भी रोकेगा। इससे विंडहॉक ऐसे मामलों में मॉड्स को थोड़ी देरी से लोड करेगा।", "descriptionExclusionWiki": "प्रक्रियाओं को बहिष्कृत करने और अंतर्निहित प्रक्रिया बहिष्करण सूचियों के बारे में अधिक जानकारी के लिए, कृपया <0>दस्तावेज़ीकरण देखें।", "excludeCriticalProcesses": "महत्वपूर्ण सिस्टम प्रक्रियाओं को बहिष्कृत करें", "excludeIncompatiblePrograms": "ज्ञात असंगत प्रोग्राम्स को बहिष्कृत करें", "excludeGames": "प्रसिद्ध गेम्स को बहिष्कृत करें", "titleInclusion": "प्रक्रिया समावेशन सूची", "descriptionInclusion": "प्रक्रिया नामों/पथों की एक सूची जिनमें विंडहॉक इंजेक्ट करेगा, भले ही वे बहिष्करण सूची में हों।", "inclusionWithoutExclusionNotice": "खाली प्रक्रिया बहिष्करण सूची के साथ प्रक्रिया समावेशन सूची का कोई प्रभाव नहीं पड़ता।", "inclusionWithoutTotalExclusionNotice": "यदि आपका मतलब सभी प्रक्रियाओं को इनके अलावा बहिष्कृत करना था, तो आप प्रक्रिया बहिष्करण सूची में \"*\" सेट कर सकते हैं।", "processListPlaceholder": "प्रक्रिया नाम/पथ, प्रति पंक्ति एक, उदाहरण के लिए:", "invalidCharactersWarning": "प्रक्रिया सूची में अमान्य वर्ण हैं जो हटा दिए जाएंगे: {{invalidCharacters}}" } }, "about": { "title": "विंडहॉक v{{version}}", "beta": "बीटा", "subtitle": "विंडोज और प्रोग्राम्स के लिए अनुकूलन मार्केटप्लेस", "credit": "<0>{{author}} द्वारा", "update": { "title": "एक अपडेट उपलब्ध है", "subtitle": "नवीनतम सुविधाओं और बग फिक्स प्राप्त करने के लिए विंडहॉक को अपडेट करने पर विचार करें", "updateButton": "अधिक विवरण" }, "links": { "title": "लिंक्स", "homepage": "होमपेज", "documentation": "दस्तावेज़ीकरण" }, "builtWith": { "title": "के साथ निर्मित", "vscodium": "माइक्रोसॉफ्ट के VSCode संपादक का एक सामुदायिक-संचालित वितरण", "llvmMingw": "एक LLVM/Clang/LLD आधारित mingw-w64 टूलचेन", "minHook": "विंडोज के लिए न्यूनतम API हुकिंग लाइब्रेरी", "others": "अन्य टूल और लाइब्रेरीज़, और थोड़ा सा कोड" } }, "installModal": { "title": "{{mod}} स्थापित करें", "warningTitle": "सावधानी से आगे बढ़ें", "warningDescription": "दुर्भावनापूर्ण मॉड्स आपके कंप्यूटर को नुकसान पहुंचा सकते हैं या आपकी गोपनीयता का उल्लंघन कर सकते हैं। केवल उन लेखकों से मॉड्स स्थापित करें जिन पर आप भरोसा करते हैं।", "modAuthor": "मॉड लेखक", "homepage": "होमपेज", "github": "GitHub", "twitter": "X (ट्विटर)", "verified": "सत्यापित", "verifiedTooltip": "हमने सत्यापित किया है कि यह प्रोफाइल मॉड लेखक की है, लेकिन ध्यान दें कि <0>सत्यापित का मतलब <0>विश्वसनीय नहीं है। सुनिश्चित करें कि आप मॉड लेखक पर भरोसा करते हैं, या स्थापना से पहले स्रोत कोड का सावधानीपूर्वक निरीक्षण करें।", "acceptButton": "जोखिम स्वीकार करें और स्थापित करें", "cancelButton": "रद्द करें" }, "createNewModButton": { "title": "नया मॉड बनाएँ" }, "devModeAction": { "message": "मॉड्स बनाना और संशोधित करना विंडोज के लिए C/C++ विकास के कुछ ज्ञान की आवश्यकता रखता है। यदि आपको यकीन नहीं है कि इसका मतलब क्या है, तो शायद ये विकल्प आपके लिए नहीं हैं।", "hideOptionsCheckbox": "सभी विकास-संबंधी विकल्प छिपाएँ", "hideOptionsButton": "विकल्प छिपाएँ", "beginCodingButton": "कोडिंग शुरू करें", "cancelButton": "रद्द करें" }, "safeMode": { "alert": "विंडहॉक सुरक्षित मोड में चल रहा है, सभी कोड इंजेक्शन कार्यक्षमता बंद है।", "offButton": "सुरक्षित मोड बंद करें", "offConfirm": "सेटिंग्स लागू करने के लिए विंडहॉक पुनः शुरू होगा।", "offConfirmOk": "विंडहॉक पुनः शुरू करें", "offConfirmCancel": "रद्द करें" }, "modPreview": { "actionUnavailable": "पूर्वावलोकन मोड में कार्रवाई उपलब्ध नहीं है", "notCompiled": "पूर्वावलोकन करने से पहले मॉड को कंपाइल करने की आवश्यकता है" }, "sidebar": { "modId": "मॉड पहचानकर्ता", "enableMod": "मॉड सक्षम करें", "enableLogging": "लॉगिंग सक्षम करें", "notCompiled": "मॉड को कंपाइल करने की आवश्यकता है", "compile": "मॉड कंपाइल करें", "compilationFailed": "कंपाइलेशन विफल", "preview": "मॉड पूर्वावलोकन करें", "showLogOutput": "लॉग आउटपुट दिखाएँ", "exit": "संपादन मोड से बाहर निकलें", "exitConfirmation": "आखिरी सफल कंपाइलेशन के बाद के परिवर्तन खो जाएंगे", "exitButtonOk": "बाहर निकलें", "exitButtonCancel": "रहें" } } ================================================ FILE: src/vscode-windhawk-ui/apps/vscode-windhawk-ui/src/locales/hr/DO_NOT_EDIT.txt ================================================ The files in this folder are generated automatically. To submit a translation update, please refer to this repository: https://github.com/ramensoftware/windhawk-translate ================================================ FILE: src/vscode-windhawk-ui/apps/vscode-windhawk-ui/src/locales/hr/translation.json ================================================ { "general": { "loading": "Učitavam...", "loadingFailed": "Učitavanje nije uspjelo, provjerite internetsku vezu", "loadingFailedTitle": "Učitavanje nije uspjelo", "loadingFailedSubtitle": "Provjerite internetsku vezu i pokušajte ponovno", "tryAgain": "Pokušaj ponovno", "updating": "Ažuriram...", "installing": "Instaliram...", "compiling": "Kompiliram...", "cut": "Izreži", "copy": "Kopiraj", "paste": "Zalijepi", "selectAll": "Odaberi sve" }, "appHeader": { "home": "Početna", "explore": "Istraži", "settings": "Postavke", "about": "O programu" }, "mod": { "updateAvailable": "Dostupno ažuriranje", "installed": "Instalirano", "details": "Detalji", "update": "Ažuriraj", "install": "Instaliraj", "compile": "Kompiliraj", "disable": "Onemogući", "enable": "Omogući", "edit": "Uredi", "fork": "Fork", "remove": "Ukloni", "removeConfirm": "Jeste li sigurni da želite ukloniti ovaj dodatak?", "removeConfirmOk": "Ukloni dodatak", "removeConfirmCancel": "Odustani", "notCompiled": "Dodatak je potrebno kompilirati", "editedLocally": "Dodatak je lokalno izmijenjen", "noDescription": "(nema opisa)", "users_one": "{{formattedCount}} korisnik", "users_few": "{{formattedCount}} korisnika", "users_other": "{{formattedCount}} korisnika" }, "modDetails": { "header": { "installedVersion": "Instalirana verzija", "latestVersion": "Najnovija verzija", "loading": "učitavam...", "loadingFailed": "učitavanje nije uspjelo", "modId": "Identifikator dodatka", "modVersion": "Verzija dodatka", "modAuthor": { "title": "Autor dodatka", "homepage": "Početna stranica", "github": "GitHub", "twitter": "X (Twitter)" }, "processes": { "all": "Svi procesi", "allBut": "Svi osim {{list}}", "except": "{{included}} osim {{excluded}}", "tooltip": { "targets": "Ciljni procesi", "excluded": "Isključeno" } }, "updateNotNeeded": "Instalirana verzija jednaka je najnovijoj" }, "details": { "title": "Detalji", "noData": "Nedostaju detalji o dodatku." }, "settings": { "title": "Postavke", "noData": "Nema dostupnih postavki za ovaj dodatak.", "saveButton": "Spremi postavke", "sampleValue": "Uzorak vrijednosti", "arrayItemAdd": "Dodaj novu stavku", "arrayItemRemove": "Ukloni stavku" }, "code": { "title": "Izvorni kôd", "noData": "Nedostaje izvorni kôd dodatka.", "collapseExtra": "Sažmi Readme i Postavke" }, "changelog": { "title": "Povijest promjena", "loadingFailed": "Učitavanje povijesti promjena nije uspjelo. <0>Kliknite ovdje za pregled na GitHubu." }, "advanced": { "title": "Napredno", "debugLogging": { "title": "Debug zapisivanje", "description": "Može pomoći u otklanjanju poteškoća s dodatkom.", "none": "Isključeno", "modLogs": "Zapisivanje dodatka", "detailedLogs": "Detaljno debug zapisivanje", "showLogButton": "Prikaži izlaz zapisivanja" }, "modSettings": { "title": "Postavke dodatka", "description": "Jednostavno izvozite ili dijelite postavke ovog dodatka.", "loadButton": "Učitaj", "loadFormattedButton": "Učitaj formatirano", "saveButton": "Spremi", "invalidData": "Nevažeći JSON podaci" }, "customList": { "titleInclusion": "Prilagođeni popis procesa za uključivanje", "descriptionInclusion": "Prilagođeni popis dodatnih naziva ili putanja izvršnih datoteka koje će dodatak ciljati. Popis se dodaje popisu uključivanja koji je autor definirao za određivanje ciljanih procesa. Za svaki proces, dodatak se učitava ako putanja izvršne datoteke odgovara nekom unosu za uključivanje i ne poklapa se ni s jednim unosom za isključivanje.", "titleExclusion": "Prilagođeni popis procesa za isključivanje", "descriptionExclusion": "Prilagođeni popis dodatnih naziva ili putanja izvršnih datoteka koje se isključuju iz ciljanja. Ovaj se popis dodaje popisu isključivanja koji je autor definirao. Za svaki proces, dodatak se učitava ako putanja odgovara nekom unosu za uključivanje i ne odgovara nijednom unosu za isključivanje.", "processListPlaceholder": "Nazivi/putanje procesa, jedan po retku, primjerice:", "invalidCharactersWarning": "Popis procesa sadrži nevažeće znakove koji će biti uklonjeni: {{invalidCharacters}}", "saveButton": "Spremi" }, "includeExcludeCustomOnly": { "title": "Zanemari popise uključivanja/isključivanja dodatka", "description": "Zanemari popise uključivanja/isključivanja procesa definirane u dodatku i upotrebljavaj samo prilagođene popise iznad." }, "patternsMatchCriticalSystemProcesses": { "title": "Uvažavaj obrasce za kritične procese sustava", "description": "Po zadanom, Windhawk učitava dodatke u kritične procese sustava samo ako su izravno navedeni (npr. <0>critical.exe), a ne putem uzoraka (<0>* ili <0>*.exe). Za više detalja o kritičnim procesima sustava, pogledajte <1>dokumentaciju." } }, "changes": { "title": "Promjene u najnovijoj verziji", "noData": "Instalirana verzija jednaka je najnovijoj.", "splitView": "Podijeljeni prikaz", "expandLines_one": "Proširi jednu skrivenu liniju", "expandLines_few": "Proširi {{count}} skrivene linije", "expandLines_other": "Proširi {{count}} skrivenih linija" } }, "modSearch": { "placeholder": "Pretraži dodatke..." }, "home": { "browse": "Pretraži dodatke", "installedMods": { "title": "Instalirani dodaci", "noMods": "Nema instaliranih dodataka" }, "featuredMods": { "title": "Istaknuti dodaci", "noMods": "Nema istaknutih dodataka koji još nisu instalirani", "explore": "Istraži ostale dodatke" } }, "explore": { "search": { "popularAndTopRated": "Popularni i najbolje ocijenjeni", "popular": "Popularni", "topRated": "Najbolje ocijenjeni", "newest": "Najnoviji", "lastUpdated": "Nedavno ažurirani", "alphabeticalOrder": "Abecedni red" } }, "settings": { "language": { "title": "Jezik", "description": "Odaberite željeni jezik sučelja za Windhawk.", "contribute": "<0>Doprinesite novom prijevodu.", "credits": "Engleski prijevod: <0>Davor Kustec.", "creditsLink": "mailto:dkustec@gmail.com" }, "updates": { "title": "Provjera ažuriranja", "description": "Povremeno provjerava dostupnost novih verzija Windhawka i instaliranih dodataka." }, "devMode": { "title": "Način za razvojne programere", "description": "Prikazuje radnje za razvojne programere, poput izrade i uređivanja dodataka." }, "advancedSettings": "Napredne postavke", "hideTrayIcon": { "title": "Sakrij ikonu u sistemskoj traci", "description": "Morat ćete isključiti ovu opciju ako želite izaći iz Windhawka." }, "requireElevation": { "title": "Zahtijevaj administratorska prava za pokretanje Windhawka", "description": "Windhawku su potrebna administratorska prava, no za jednokorisničko računalo stalni UAC upiti mogu biti zamorni, pa se Windhawk podrazumijevano pokreće bez njih. Omogućite ovu opciju ako želite da Windhawk svaki put traži administratorska prava." }, "dontAutoShowToolkit": { "title": "Ne prikazuj automatski alatni komplet", "description": "Windhawk prema zadanim postavkama automatski prikazuje dijalog alatnog kompleta kad otkrije da programska traka nije dostupna, primjerice zbog nestabilnosti sustava. Alatni komplet omogućuje izlazak iz Windhawka, prijelaz u sigurni način rada i druge radnje. Možete ga prikazati i pomoću prečaca Ctrl+Win+W." }, "modInitDialogDelay": { "title": "Kašnjenje dijaloga pri inicijalizaciji dodataka", "description": "Vrijednost (u milisekundama) prije nego što se prikaže dijalog napretka prilikom inicijalizacije dodataka." }, "moreAdvancedSettings": { "title": "Još naprednih postavki", "restartNotice": "Windhawk će se ponovno pokrenuti kako bi primijenio postavke.", "saveButton": "Spremi i ponovno pokreni Windhawk", "cancelButton": "Odustani" }, "loggingVerbosity": { "appLoggingTitle": "Razina zapisivanja u Windhawku", "engineLoggingTitle": "Razina zapisivanja u Windhawk pogonu", "description": "Zapisnike možete pregledavati u alatima kao što je DebugView.", "none": "Isključeno", "error": "Pogreške", "verbose": "Detaljno" }, "processList": { "titleExclusion": "Popis procesa za isključivanje", "descriptionExclusion": "Popis naziva/putanja procesa u koje se Windhawk neće ubrizgavati. To može biti korisno ako Windhawk nije kompatibilan s određenim programom. Dodavanje procesa ovdje onemogućuje prilagodbu tog procesa i sprječava presretanje child procesa koje taj proces pokreće, što može uzrokovati malo kašnjenje pri učitavanju dodataka.", "descriptionExclusionWiki": "Više informacija o isključivanju procesa i ugrađenim popisima za isključivanje potražite u <0>dokumentaciji.", "excludeCriticalProcesses": "Isključi kritične procese sustava", "excludeIncompatiblePrograms": "Isključi poznate nekompatibilne programe", "excludeGames": "Isključi dobro poznate igre", "titleInclusion": "Popis procesa za uključivanje", "descriptionInclusion": "Popis naziva/putanja procesa u koje će se Windhawk ubrizgavati, čak i ako su na popisu za isključivanje.", "inclusionWithoutExclusionNotice": "Popis za uključivanje procesa nema učinka dok je popis za isključivanje prazan.", "inclusionWithoutTotalExclusionNotice": "Ako želite isključiti sve procese osim ovih, dodajte \"*\" u popis za isključivanje.", "processListPlaceholder": "Nazivi/putanje procesa, jedan po retku, primjerice:", "invalidCharactersWarning": "Popis procesa sadrži nevažeće znakove koji će biti uklonjeni: {{invalidCharacters}}" } }, "about": { "title": "Windhawk v{{version}}", "beta": "beta", "subtitle": "Tržište prilagodbi za Windows i programe", "credit": "Autor: <0>{{author}}", "update": { "title": "Dostupno je ažuriranje", "subtitle": "Preporučuje se ažuriranje Windhawka radi najnovijih značajki i ispravki", "updateButton": "Više detalja" }, "links": { "title": "Poveznice", "homepage": "Početna stranica", "documentation": "Dokumentacija" }, "builtWith": { "title": "Izrađeno uz pomoć", "vscodium": "Distribucija Microsoftova VSCode editora koju podržava zajednica", "llvmMingw": "LLVM/Clang/LLD-based mingw-w64 alatni lanac", "minHook": "Minimalistička biblioteka za presretanje Windows API-ja", "others": "Ostali alati i biblioteke, te malo dodatnog koda" } }, "installModal": { "title": "Instaliraj {{mod}}", "warningTitle": "Postupajte oprezno", "warningDescription": "Zlonamjerni dodaci mogu prouzročiti štetu vašem računalu ili narušiti vašu privatnost. Instalirajte dodatke samo od autora kojima vjerujete.", "modAuthor": "Autor dodatka", "homepage": "Početna stranica", "github": "GitHub", "twitter": "X (Twitter)", "verified": "provjereno", "verifiedTooltip": "Potvrdili smo da ovaj profil pripada autoru dodatka, no <0>provjereno nije isto što i <0>pouzdano. Pobrinite se da vjerujete autoru ili pažljivo pregledajte izvorni kôd prije instalacije.", "acceptButton": "Prihvati rizik i instaliraj", "cancelButton": "Odustani" }, "createNewModButton": { "title": "Kreiraj novi dodatak" }, "devModeAction": { "message": "Kreiranje i izmjena dodataka zahtijeva poznavanje C/C++ razvoja za Windows. Ako niste sigurni što to znači, možda ove opcije nisu za vas.", "hideOptionsCheckbox": "Sakrij sve opcije za razvoj", "hideOptionsButton": "Sakrij opcije", "beginCodingButton": "Započni s kodiranjem", "cancelButton": "Odustani" }, "safeMode": { "alert": "Windhawk radi u sigurnom načinu rada i svaka je funkcionalnost presretanja koda isključena.", "offButton": "Isključi sigurni način rada", "offConfirm": "Windhawk će se ponovno pokrenuti kako bi primijenio postavke.", "offConfirmOk": "Ponovno pokreni Windhawk", "offConfirmCancel": "Odustani" }, "modPreview": { "actionUnavailable": "Radnja nije dostupna u načinu pregleda", "notCompiled": "Dodatak je potrebno kompilirati prije pregleda" }, "sidebar": { "modId": "Identifikator dodatka", "enableMod": "Omogući dodatak", "enableLogging": "Omogući zapisivanje", "notCompiled": "Dodatak je potrebno kompilirati", "compile": "Kompiliraj dodatak", "compilationFailed": "Kompilacija nije uspjela", "preview": "Pregled dodatka", "showLogOutput": "Prikaži zapis", "exit": "Izađi iz načina uređivanja", "exitConfirmation": "Izmjene od zadnje uspješne kompilacije bit će izgubljene", "exitButtonOk": "Izađi", "exitButtonCancel": "Ostani" } } ================================================ FILE: src/vscode-windhawk-ui/apps/vscode-windhawk-ui/src/locales/hu/DO_NOT_EDIT.txt ================================================ The files in this folder are generated automatically. To submit a translation update, please refer to this repository: https://github.com/ramensoftware/windhawk-translate ================================================ FILE: src/vscode-windhawk-ui/apps/vscode-windhawk-ui/src/locales/hu/translation.json ================================================ { "general": { "loading": "Betöltés...", "loadingFailed": "Betöltés sikertelen, kérjük, ellenőrizze az internetkapcsolatát", "loadingFailedTitle": "Betöltés sikertelen", "loadingFailedSubtitle": "Ellenőrizze az internetkapcsolatot, és próbálja újra", "tryAgain": "Próbáld újra", "updating": "Frissítés...", "installing": "Telepítés...", "compiling": "Fordítás...", "cut": "Kivágás", "copy": "Másolás", "paste": "Beillesztés", "selectAll": "Összes kijelölése" }, "appHeader": { "home": "Főoldal", "explore": "Felfedezés", "settings": "Beállítások", "about": "Névjegy" }, "mod": { "updateAvailable": "Frissítés elérhető", "installed": "Telepítve", "details": "Részletek", "update": "Frissítés", "install": "Telepítés", "compile": "Fordítás", "disable": "Letiltás", "enable": "Engedélyezés", "edit": "Szerkesztés", "fork": "Fork", "remove": "Eltávolítás", "removeConfirm": "Biztosan eltávolítja ezt a modot?", "removeConfirmOk": "Mod eltávolítása", "removeConfirmCancel": "Mégse", "notCompiled": "A modot le kell fordítani", "editedLocally": "A mod helyben szerkesztve lett", "noDescription": "(nincs leírás)", "users_one": "{{formattedCount}} felhasználó", "users_other": "{{formattedCount}} felhasználók" }, "modDetails": { "header": { "installedVersion": "Telepített verzió", "latestVersion": "Legújabb verzió", "loading": "betöltés...", "loadingFailed": "betöltés sikertelen", "modId": "Mod azonosító", "modVersion": "Mod verzió", "modAuthor": { "title": "Mod szerző", "homepage": "Honlap", "github": "GitHub", "twitter": "X (Twitter)" }, "processes": { "all": "Minden folyamat", "allBut": "Minden, kivéve {{list}}", "except": "{{included}} kivéve {{excluded}}", "tooltip": { "targets": "Cél folyamatok", "excluded": "Kizárva" } }, "updateNotNeeded": "A telepített verzió megegyezik a legújabb verzióval" }, "details": { "title": "Részletek", "noData": "Hiányoznak a mod részletei." }, "settings": { "title": "Beállítások", "noData": "Nincsenek elérhető mod beállítások.", "saveButton": "Beállítások mentése", "sampleValue": "Minta érték", "arrayItemAdd": "Új elem hozzáadása", "arrayItemRemove": "Elem eltávolítása" }, "code": { "title": "Forráskód", "noData": "Hiányzik a mod forrása.", "collapseExtra": "Readme és Beállítások összecsukása" }, "changelog": { "title": "Változásnapló", "loadingFailed": "Változásnapló betöltése sikertelen. <0>Kattintson ide a GitHub megtekintéséhez." }, "advanced": { "title": "Haladó", "debugLogging": { "title": "Hibakeresési naplózás", "description": "Hasznos lehet a mod hibáinak elhárításához.", "none": "Nincs", "modLogs": "Mod naplók", "detailedLogs": "Részletes hibakeresési naplók", "showLogButton": "Napló kimenet megjelenítése" }, "modSettings": { "title": "Mod beállítások", "description": "Könnyen exportálhatja vagy megoszthatja mod beállításait.", "loadButton": "Betöltés", "loadFormattedButton": "Formázott betöltés", "saveButton": "Mentés", "invalidData": "Érvénytelen JSON adat" }, "customList": { "titleInclusion": "Egyéni folyamat bevonási lista", "descriptionInclusion": "Egy egyéni lista további végrehajtható fájlnevekről/útvonalakról, amelyeket a mod célozni fog. A lista hozzáadódik a szerző által meghatározott bevonási listához, hogy meghatározza, mely folyamatokat célozza meg. A mod betöltődik, ha a végrehajtható fájlútvonal egyezik az egyik bevonási elemmel és nem egyezik meg egy kizárási elemmel.", "titleExclusion": "Egyéni folyamat kizárási lista", "descriptionExclusion": "Egy egyéni lista további végrehajtható fájlnevekről/útvonalakról, amelyeket a mod kizár a célzásból.", "processListPlaceholder": "Folyamat nevek/útvonalak, egy sorban, például:", "saveButton": "Mentés" }, "includeExcludeCustomOnly": { "title": "Mod bevonási/kizárási listák figyelmen kívül hagyása", "description": "A mod folyamat bevonási/kizárási listáinak figyelmen kívül hagyása, és csak a fentiek használata." } }, "changes": { "title": "Legújabb verzió változásai", "noData": "A telepített verzió megegyezik a legújabb verzióval.", "splitView": "Megosztott nézet", "expandLines_one": "Egy rejtett sor kibontása", "expandLines_other": "{{count}} rejtett sor kibontása" } }, "modSearch": { "placeholder": "Modok keresése..." }, "home": { "browse": "Modok böngészése", "installedMods": { "title": "Telepített modok", "noMods": "Nincsenek telepített modok" }, "featuredMods": { "title": "Kiemelt modok", "noMods": "Nincsenek olyan kiemelt modok, amelyek még nincsenek telepítve", "explore": "Fedezd fel a többi modot" } }, "explore": { "search": { "popularAndTopRated": "Népszerű és legjobbra értékelt", "popular": "Népszerű", "topRated": "Legjobbra értékelt", "newest": "Legújabb", "lastUpdated": "Legutóbb frissítve", "alphabeticalOrder": "ABC sorrend" } }, "settings": { "language": { "title": "Nyelv", "description": "Válassza ki a Windhawk megjelenítési nyelvét.", "contribute": "<0>Járuljon hozzá egy új fordítással.", "credits": "Angol fordítás: <0>Polauf László.", "creditsLink": "mailto:aolauf35@gmail.com" }, "updates": { "title": "Frissítések keresése", "description": "Rendszeresen ellenőrzi a Windhawk és a telepített modok új verzióit." }, "devMode": { "title": "Fejlesztői mód", "description": "Megjeleníti a fejlesztők számára elérhető műveleteket, például a modok létrehozását és módosítását." }, "advancedSettings": "Haladó beállítások", "hideTrayIcon": { "title": "Tálcaikon elrejtése", "description": "A Windhawk bezárásához le kell tiltania ezt az opciót." }, "requireElevation": { "title": "UAC emelés szükséges a Windhawk futtatásához", "description": "A Windhawk adminisztrátori jogokat igényel, de egyetlen felhasználós számítógépen bosszantó lehet minden alkalommal megkapni az UAC kérdést, ezért a Windhawk ezt megkerüli. Engedélyezze ezt az opciót, ha szeretné, hogy a Windhawk adminisztrátori jogokkal induljon." }, "dontAutoShowToolkit": { "title": "Ne jelenítse meg automatikusan az eszközkészlet párbeszédablakot", "description": "Alapértelmezés szerint a Windhawk automatikusan megjeleníti az eszközkészlet párbeszédablakot, amikor azt észleli, hogy a tálca nem elérhető, akár rendszerinstabilitás, akár más ok miatt." }, "modInitDialogDelay": { "title": "Mod inicializálási párbeszédablak késleltetése", "description": "A várakozási idő milliszekundumban, mielőtt megjelenik a mod inicializálási folyamat párbeszédablaka." }, "moreAdvancedSettings": { "title": "További haladó beállítások", "restartNotice": "A beállítások alkalmazásához a Windhawk újraindul.", "saveButton": "Mentés és Windhawk újraindítása", "cancelButton": "Mégse" }, "loggingVerbosity": { "appLoggingTitle": "Windhawk naplózási részletesség", "engineLoggingTitle": "Windhawk motor naplózási részletesség", "description": "A naplókat meg lehet tekinteni egy olyan eszközzel, mint a DebugView.", "none": "Nincs", "error": "Hiba", "verbose": "Részletes" }, "processList": { "titleExclusion": "Folyamat kizárási lista", "descriptionExclusion": "A Windhawk nem fog injektálni az itt megadott folyamatokba.", "titleInclusion": "Folyamat bevonási lista", "descriptionInclusion": "Lista folyamatokról, amelyekbe a Windhawk injektál, még akkor is, ha azok a kizárási listán vannak.", "inclusionWithoutExclusionNotice": "A folyamat bevonási lista nem működik üres folyamat kizárási listával.", "inclusionWithoutTotalExclusionNotice": "Ha azt szeretné, hogy minden folyamat kizárva legyen, kivéve ezeket, állítsa be a „*” értéket a folyamat kizárási listában.", "processListPlaceholder": "Folyamat nevek/útvonalak, egy sorban, például:" } }, "about": { "title": "Windhawk v{{version}}", "beta": "béta", "subtitle": "Testreszabási piactér Windowsra és programokra", "credit": "Készítette: <0>{{author}}", "update": { "title": "Frissítés elérhető", "subtitle": "Fontolja meg a Windhawk frissítését az új funkciók és hibajavítások érdekében", "updateButton": "Részletek" }, "links": { "title": "Linkek", "homepage": "Honlap", "documentation": "Dokumentáció" }, "builtWith": { "title": "Készült:", "vscodium": "A Microsoft VSCode szerkesztőjének közösségvezérelt terjesztése", "llvmMingw": "LLVM/Clang/LLD alapú mingw-w64 eszközkészlet", "minHook": "Minimalista API hooking könyvtár Windowsra", "others": "Egyéb eszközök és könyvtárak, és egy kis kód" } }, "installModal": { "title": "{{mod}} telepítése", "warningTitle": "Óvatosan folytassa", "warningDescription": "A rosszindulatú modok károsíthatják a számítógépet vagy megsérthetik a magánéletét. Csak olyan modokat telepítsen, amelyek szerzőiben megbízik.", "modAuthor": "Mod szerzője", "homepage": "Honlap", "github": "GitHub", "twitter": "X (Twitter)", "verified": "ellenőrzött", "verifiedTooltip": "Ellenőriztük, hogy ez a profil a mod szerzőjéhez tartozik, de jegyezze meg, hogy az <0>ellenőrzés nem ugyanaz, mint a <0>bizalom. Győződjön meg róla, hogy megbízik a mod szerzőjében, vagy alaposan vizsgálja meg a forráskódot a telepítés előtt.", "acceptButton": "Kockázat elfogadása és telepítés", "cancelButton": "Mégse" }, "createNewModButton": { "title": "Új mod létrehozása" }, "devModeAction": { "message": "Modok létrehozása és módosítása némi Windows C/C++ fejlesztési ismeretet igényel. Ha nem biztos abban, mit jelent ez, talán ezek az opciók nem önnek valók.", "hideOptionsCheckbox": "Fejlesztéssel kapcsolatos opciók elrejtése", "hideOptionsButton": "Opciók elrejtése", "beginCodingButton": "Kódolás elkezdése", "cancelButton": "Mégse" }, "safeMode": { "alert": "A Windhawk biztonságos módban fut, minden kódbefecskendezési funkció ki van kapcsolva.", "offButton": "Biztonságos mód kikapcsolása", "offConfirm": "A Windhawk újraindul a beállítások alkalmazásához.", "offConfirmOk": "Windhawk újraindítása", "offConfirmCancel": "Mégse" }, "modPreview": { "actionUnavailable": "Ez a művelet nem érhető el előnézeti módban", "notCompiled": "A modot le kell fordítani, mielőtt előnézetet lehetne készíteni" }, "sidebar": { "modId": "Mod azonosító", "enableMod": "Mod engedélyezése", "enableLogging": "Naplózás engedélyezése", "notCompiled": "A modot le kell fordítani", "compile": "Mod fordítása", "compilationFailed": "Fordítás sikertelen", "preview": "Mod előnézete", "showLogOutput": "Napló kimenet megjelenítése", "exit": "Kilépés a szerkesztési módból", "exitConfirmation": "A legutóbbi sikeres fordítás óta történt változtatások elvesznek", "exitButtonOk": "Kilépés", "exitButtonCancel": "Mégse" } } ================================================ FILE: src/vscode-windhawk-ui/apps/vscode-windhawk-ui/src/locales/id/DO_NOT_EDIT.txt ================================================ The files in this folder are generated automatically. To submit a translation update, please refer to this repository: https://github.com/ramensoftware/windhawk-translate ================================================ FILE: src/vscode-windhawk-ui/apps/vscode-windhawk-ui/src/locales/id/translation.json ================================================ { "general": { "loading": "Memuat...", "loadingFailed": "Gagal dimuat, silahkan periksa koneksi internet anda", "loadingFailedTitle": "Gagal dimuat", "loadingFailedSubtitle": "Silahkan periksa koneksi internet anda dan coba lagi", "tryAgain": "Coba lagi", "updating": "Memperbarui...", "installing": "Memasang...", "compiling": "Kompiling...", "cut": "Potong", "copy": "Salin", "paste": "Tempel", "selectAll": "Pilih semua" }, "appHeader": { "home": "Beranda", "explore": "Jelajahi", "settings": "Pengaturan", "about": "Tentang" }, "mod": { "updateAvailable": "Pembaruan tersedia", "installed": "Terpasang", "details": "Rincian", "update": "Perbarui", "install": "Pasang", "compile": "Kompilasi", "disable": "Nonaktifkan", "enable": "Diaktifkan", "edit": "Sunting", "fork": "Fork", "remove": "Hapus", "removeConfirm": "Apakah anda yakin ingin menghapus mod ini?", "removeConfirmOk": "Hapus mod", "removeConfirmCancel": "Batal", "notCompiled": "Mod needs to be compiled", "editedLocally": "Mod sudah disunting lokal", "noDescription": "(tidak ada deskripsi)", "users_one": "{{formattedCount}} pengguna", "users_other": "{{formattedCount}} pengguna" }, "modDetails": { "header": { "installedVersion": "Versi yang terpasang", "latestVersion": "Versi saat ini", "loading": "memuat...", "loadingFailed": "gagal dimuat", "modId": "Pengenal Mod", "modVersion": "Versi Mod", "modAuthor": { "title": "Pembuat Mod", "homepage": "Homepage", "github": "GitHub", "twitter": "X (Twitter)" }, "processes": { "all": "Semua proses", "allBut": "Semua tapi {{list}}", "except": "{{included}} kecuali {{excluded}}", "tooltip": { "targets": "Target pemrosesan", "excluded": "Dikecualikan" } }, "updateNotNeeded": "Versi yang terinstal identik dengan versi terbaru" }, "details": { "title": "Rincian", "noData": "Rincian mod hilang." }, "settings": { "title": "Pengaturan", "noData": "Tidak ada pengaturan mod yang tersedia.", "saveButton": "Simpan pengaturan", "sampleValue": "Contoh value", "arrayItemAdd": "Tambahkan item baru", "arrayItemRemove": "Hapus item" }, "code": { "title": "Kode Sumber", "noData": "Sumber mod hilang.", "collapseExtra": "Perluas Readme dan Pengaturan" }, "changelog": { "title": "Catatan perubahan", "loadingFailed": "Gagal memuat catatan perubahan. <0>Klik disini untuk melihat di Github." }, "advanced": { "title": "Lanjutan", "debugLogging": { "title": "Debug logging", "description": "Dapat berguna untuk memecahkan masalah dengan mod.", "none": "Tidak ada", "modLogs": "Catatan mod", "detailedLogs": "Rincian catatan debug", "showLogButton": "Tampilkan output catatan" }, "modSettings": { "title": "Pengaturan Mod", "description": "Ekspor mudah atau bagikan pengaturan mod anda.", "loadButton": "Muat", "loadFormattedButton": "Muat diformat", "saveButton": "Simpan", "invalidData": "Data JSON tidak valid" }, "customList": { "titleInclusion": "Kustom proses daftar penyertaan", "descriptionInclusion": "Daftar kustomisasi nama berkas/jalur tambahan yang dapat dieksekusi yang akan ditargetkan oleh mod. Daftar yang ditambahkan ke daftar penyertaan itu penulis mod didefinisikan untuk menentukan proses mana yang akan ditargetkan. Untuk setiap proses, mod dimuat jika jalur berkas yang dapat dieksekusi cocok dengan salah satu entri penyertaan dan tidak cocok dengan entri pengecualian apa pun.", "titleExclusion": "Daftar proses pengecualian kustom", "descriptionExclusion": "Daftar kustom berisi nama/jalur file yang dapat dieksekusi tambahan yang akan dikecualikan mod dari penargetan. Daftar tersebut ditambahkan ke daftar pengecualian yang ditetapkan oleh pembuat mod untuk menentukan proses mana yang akan ditargetkan. Untuk setiap proses, mod dimuat jika jalur file yang dapat dieksekusi cocok dengan salah satu entri yang disertakan dan tidak cocok dengan entri yang dikecualikan.", "processListPlaceholder": "Nama proses/jalur, satu per baris, sebagai contoh:", "saveButton": "Simpan" }, "includeExcludeCustomOnly": { "title": "Abaikan daftar penyertaan/pengecualian mod", "description": "Abaikan daftar penyertaan/pengecualian proses mod dan gunakan hanya daftar kustom di atas." } }, "changes": { "title": "Perubahan Versi Saat Ini", "noData": "Versi yang terpasang identik dengan versi saat ini.", "splitView": "Tampilan berbagi", "expandLines_one": "Perlebar salah satu baris tersembunyi", "expandLines_other": "Perlebar {{count}} baris tersembunyi" } }, "modSearch": { "placeholder": "Cari untuk mod..." }, "home": { "browse": "Jelajahi untuk Mod", "installedMods": { "title": "Mod yang terpasang", "noMods": "Tidak ada mod yang terpasang" }, "featuredMods": { "title": "Mod yang difiturkan", "noMods": "Tidak ada mod difiturkan yang belum terpasang", "explore": "Jelajahi Mod Lainnya" } }, "explore": { "search": { "popularAndTopRated": "Populer dan rating tertinggi", "popular": "Populer", "topRated": "Rating tertinggi", "newest": "Terbaru", "lastUpdated": "Terakhir diperbarui", "alphabeticalOrder": "Urutkan secara abjad" } }, "settings": { "language": { "title": "Bahasa", "description": "Pilih bahasa tampilan yang anda sukai untuk Windhawk.", "contribute": "<0>Kontribusi untuk terjemahan Baru.", "credits": "Indonesian translation by <0>Lieba Natur Brilian (naturbrilian).", "creditsLink": "https://github.com/naturbrilian" }, "updates": { "title": "Periksa pembaruan", "description": "Periksa secara berkala versi baru Windhawk dan mod yang terinstal." }, "devMode": { "title": "Mode pengembang", "description": "Lihat tindakan untuk pengembang, termasuk pembuatan dan memodifikasi mod." }, "advancedSettings": "Pengaturan lanjutan", "hideTrayIcon": { "title": "Sembunyikan ikon baki", "description": "Anda akan menonaktifkan opsi ini untuk menutup Windhawk." }, "requireElevation": { "title": "Dibutuhkan elevasi UAC untuk menjalankan Windhawk", "description": "Windhawk memerlukan administrator, tetapi untuk komputer single-user, mendapatkan prompt UAC setiap waktu bisa jadi menyebalkan, jadi Windhawk melewatinya. Aktifkan opsi ini untuk membutuhkan elevasi UAC untuk menjalankan Windhawk." }, "dontAutoShowToolkit": { "title": "Otomatis jangan tampilkan dialog toolkit", "description": "Secara default, Windhawk otomatis menampilkan dialog toolkit ketika mendeteksi taskbar tidak bisa diakses, baik karena ketidakstabilan sistem atau karena alasan lain. Dialog toolkit mengizinkan untuk menutup Windhawk, beralih ke mode aman, dan buat tindakan lain. Dialog toolkit juga bisa ditampilkan dengan pintasan keyboard Ctrl+Win+W." }, "modInitDialogDelay": { "title": "Penundaan dialog inisialisasi mod", "description": "Jumlah milidetik untuk menunggu sebelum menampilkan dialog kemajuan untuk inisialisasi mod." }, "moreAdvancedSettings": { "title": "Pengaturan lanjutan lainnya", "restartNotice": "Windhawk akan dimulai ulang untuk menerapkan pengaturan.", "saveButton": "Simpan dan memulai ulang Windhawk", "cancelButton": "Batal" }, "loggingVerbosity": { "appLoggingTitle": "Verbositas pencatatan Windhawk", "engineLoggingTitle": "Verbositas pencatatan mesin Windhawk", "description": "Logs bisa dilihat dengan tool seperti DebugView.", "none": "Tidak ada", "error": "Kesalahan", "verbose": "Verbositas" }, "processList": { "titleExclusion": "Proses pengecualian daftar", "descriptionExclusion": "Daftar nama/jalur proses yang tidak akan diinject Windhawk. Dapat berguna untuk kasus-kasus ketika Windhawk tidak kompatibel dengan program tertentu. Perhatikan bahwa menambahkan proses ke daftar ini tidak hanya akan mencegah Windhawk untuk menyesuaikannya, tetapi juga akan mencegah Windhawk untuk mencegat proses anak yang dibuat oleh proses ini. Ini akan menyebabkan Windhawk memuat mod dengan sedikit keterlambatan dalam kasus-kasus seperti itu.", "titleInclusion": "Proses penyertaan daftar", "descriptionInclusion": "Daftar nama proses/jalur yang akan diinject Windhawk, bahkan jika mereka ada dalam daftar pengecualian.", "inclusionWithoutExclusionNotice": "Proses penyertaan daftar tidak berefek dengan proses kosong daftar pengecualian.", "inclusionWithoutTotalExclusionNotice": "Jika anda bermaksud untuk mengecualikan semua proses kecuali ini, anda bisa mengatur \"*\" di daftar proses pengecualian.", "processListPlaceholder": "Nama proses/jalur, satu per baris, sebagai contoh:" } }, "about": { "title": "Windhawk v{{version}}", "beta": "beta", "subtitle": "Marketplace kustomisasi untuk Windows dan program", "credit": "By <0>{{author}}", "update": { "title": "Pembaruan tersedia", "subtitle": "Pastikan memperbarui Windhawk untuk mendapatkan fitur terbaru dan perbaikan bug", "updateButton": "Rincian lengkapnya" }, "links": { "title": "Tautan", "homepage": "Homepage", "documentation": "Dokumentasi" }, "builtWith": { "title": "dibuat dengan", "vscodium": "Distribusi editor VSCode Microsoft yang digerakkan oleh komunitas", "llvmMingw": "Toolchain mingw--wg4 berbasis LLVM/Clang/LLD", "minHook": "Pustaka hooking API minimalis untuk Windows", "others": "tool lain dan perpustakaan, dan sedikit kode" } }, "installModal": { "title": "Pasang {{mod}}", "warningTitle": "Lanjutkan dengan hati-hati", "warningDescription": "Mod berbahaya dapat merusak komputer atau melanggar privasi Anda. Instal mod hanya dari pembuat yang Anda percaya.", "modAuthor": "Pembuat mod", "homepage": "Homepage", "github": "GitHub", "twitter": "X (Twitter)", "verified": "terverifikasi", "verifiedTooltip": "Kami memverifikasi bahwa profil ini milik pembuat mod, perlu dicatat <0>verifikasi tidak sama dengan <0>terpercaya. Pastikan anda percaya pembuat mod, atau hati-hati menginspect kode sumber sebelum memasang.", "acceptButton": "Terima dengan Hati-hati dan Pasang", "cancelButton": "Batal" }, "createNewModButton": { "title": "Buat Mod Baru" }, "devModeAction": { "message": "Membuat dan memodifikasi mod membutuhkan beberapa keahlian pengembangan C/C++ untuk Windows. Jika anda tidak yakin apa yang dimaksud, mungkin opsi ini bukan untuk Anda.", "hideOptionsCheckbox": "Sembunyikan semua opsi yang terkait dengan pengembangan", "hideOptionsButton": "Sembunyikan opsi", "beginCodingButton": "Mulai mengoding", "cancelButton": "Batal" }, "safeMode": { "alert": "Windhawk berjalan di mode aman, semua fungsi kode injeksi dinonaktifkan.", "offButton": "Nonaktifkan mode aman", "offConfirm": "Windhawk akan dimulai ulang untuk menerapkan pengaturan.", "offConfirmOk": "Mulai ulang Windhawk", "offConfirmCancel": "Batal" }, "modPreview": { "actionUnavailable": "Tindakan tidak tersedia di mode pratinjau", "notCompiled": "Mod harus dikompilasi sebelum bisa dipratinjau" }, "sidebar": { "modId": "Identifikasi mod", "enableMod": "Aktifkan mod", "enableLogging": "Aktifkan pencatatan", "notCompiled": "Mod harus di kompilasi", "compile": "Kompilasi Mod", "compilationFailed": "Kompilasi gagal", "preview": "Pratinjau Mod", "showLogOutput": "Tampilkan Log Output", "exit": "Tutup Mode Penyuntingan", "exitConfirmation": "Perubahan sejak kompilasi terakhir yang berhasil akan hilang", "exitButtonOk": "Tutup", "exitButtonCancel": "Stay" } } ================================================ FILE: src/vscode-windhawk-ui/apps/vscode-windhawk-ui/src/locales/it/DO_NOT_EDIT.txt ================================================ The files in this folder are generated automatically. To submit a translation update, please refer to this repository: https://github.com/ramensoftware/windhawk-translate ================================================ FILE: src/vscode-windhawk-ui/apps/vscode-windhawk-ui/src/locales/it/translation.json ================================================ { "general": { "loading": "Caricamento...", "loadingFailed": "Caricamento fallito, per favore controlla la tua connessione Internet.", "loadingFailedTitle": "Caricamento fallito", "loadingFailedSubtitle": "Per favore controlla la tua connessione Internet e prova di nuovo", "tryAgain": "Riprova ancora", "updating": "Aggiornamento...", "installing": "Installazione...", "compiling": "Compilazione..." }, "appHeader": { "home": "Home", "explore": "Esplora", "settings": "impostazioni", "about": "Informazioni" }, "mod": { "updateAvailable": "Aggiornamento disponibile", "installed": "Installato", "details": "Dettagli", "update": "Aggiorna", "install": "Installa", "compile": "Compila", "disable": "Disabilita", "enable": "Abilita", "edit": "Modifica", "fork": "Fai un fork", "remove": "Rimuovi", "removeConfirm": "Sei sicuro di voler rimuovere questa mod?", "removeConfirmOk": "Rimuovi mod", "removeConfirmCancel": "Annulla", "notCompiled": "La mod deve essere compilata", "editedLocally": "La mod è stata modificata localmente", "noDescription": "(nessuna descizione)", "users_one": "{{formattedCount}} utente", "users_many": "{{formattedCount}} utenti", "users_other": "{{formattedCount}} utenti" }, "modDetails": { "header": { "installedVersion": "Versione installata", "latestVersion": "Ultima versione", "loading": "caricamento...", "loadingFailed": "caricamento fallito", "modId": "Identificatore della mod", "modVersion": "Versione della mod", "modAuthor": { "title": "Autore della mod", "homepage": "Homepage", "github": "GitHub", "twitter": "X (Twitter)" }, "processes": { "all": "Tutti i processi", "allBut": "Tutto ma {{list}}", "except": "{{included}} eccetto {{excluded}}", "tooltip": { "targets": "Processi inclusi nel targeting", "excluded": "Esclusi" } }, "updateNotNeeded": "La versione installata è identica all'ultima versione" }, "details": { "title": "Dettagli", "noData": "Mancano i dettagli della mod." }, "settings": { "title": "Impostazioni", "noData": "In questa mod non sono disponibili le impostazioni.", "saveButton": "Salva impostazioni", "sampleValue": "Valore campione", "arrayItemAdd": "Aggiungi un nuovo elemento", "arrayItemRemove": "Riumuovi elemento" }, "code": { "title": "Codice sorgente", "noData": "Manca la fonte della mod.", "collapseExtra": "Comprimi Readme e impostazioni" }, "changelog": { "title": "Changelog", "loadingFailed": "Impossibile caricare il registro delle modifiche. <0>Fai clic qui per visualizzarlo su GitHub." }, "advanced": { "title": "Avanzate", "debugLogging": { "title": "Registrazione di debug", "description": "Può essere utile per la risoluzione dei problemi con il mod.", "none": "Nessuno", "modLogs": "Registri mod", "detailedLogs": "Mostra l'output del registro", "showLogButton": "Mostra l'output del registro" }, "modSettings": { "title": "Impostazioni delle mod", "description": "Esporta o condividi facilmente le tue impostazioni delle mod.", "loadButton": "Carica", "loadFormattedButton": "Carica formattato", "saveButton": "Salva", "invalidData": "Dati JSON non validi" }, "customList": { "titleInclusion": "Elenco di inclusione di processi personalizzati", "descriptionInclusion": "Un elenco personalizzato di nomi/percorsi di file eseguibili aggiuntivi che la mod includerà nel targeting. L'elenco viene aggiunto all'elenco di inclusione che l'autore della mod ha definito per determinare quali processi includere nel targeting. Per ogni processo, la mod viene caricata se il percorso del file eseguibile corrisponde a una delle voci di inclusione e non corrisponde a nessuna voce di esclusione.", "titleExclusion": "Elenco di esclusione processo personalizzato", "descriptionExclusion": "Un elenco personalizzato di nomi/percorsi di file eseguibili aggiuntivi che la mod escluderà dal targeting. L'elenco viene aggiunto all'elenco di esclusione definito dall'autore della mod per determinare quali processi includere nel targeting. Per ogni processo, la mod viene caricata se il percorso del file eseguibile corrisponde a una delle voci di inclusione e non corrisponde a nessuna voce di esclusione.", "processListPlaceholder": "Nomi/percorsi dei processi, uno per riga, ad esempio:", "saveButton": "Salva" } }, "changes": { "title": "Ultime modifiche della versione", "noData": "La versione installata è identica all'ultima versione.", "splitView": "Dividi vista", "expandLines_one": "Espandi una linea nascosta", "expandLines_many": "Espandi {{count}} linee nascoste", "expandLines_other": "Espandi {{count}} linee nascoste" } }, "modSearch": { "placeholder": "Cerca mod..." }, "home": { "browse": "Cerca Mod", "installedMods": { "title": "Mod installate", "noMods": "Non è installata nessuna mod" }, "featuredMods": { "title": "Mod in primo piano", "noMods": "Non ci sono mod in primo piano che non sono state ancora installate", "explore": "Esplora altre mod" } }, "explore": { "search": { "popularAndTopRated": "Popolare e più votato", "popular": "Popolare", "topRated": "Più votato", "newest": "Recente", "lastUpdated": "Ultimo aggiornamento", "alphabeticalOrder": "Ordine alfabetico" } }, "settings": { "language": { "title": "Lingua", "description": "Seleziona la lingua di visualizzazione preferita per Windhawk.", "contribute": "<0>Contribuisci con una nuova traduzione.", "credits": "Traduzione italiana di <0>Marco Petrucci.", "creditsLink": "https://github.com/marco00petrucci" }, "updates": { "title": "Ricerca aggiornamenti", "description": "Verifica periodicamente la presenza di nuove versioni di Windhawk e delle mod installate." }, "devMode": { "title": "Modalità sviluppatore", "description": "Mostra azioni per sviluppatori, come la creazione e la modifica di mod." }, "advancedSettings": "Impostazioni avanzate", "hideTrayIcon": { "title": "Nascondi l'icona nella barra delle applicazioni", "description": "Dovrai disabilitare questa opzione per uscire da Windhawk." }, "requireElevation": { "title": "Richiedi l'elevazione UAC per l'esecuzione di Windhawk", "description": "Windhawk richiede i diritti di amministratore, ma per un computer con un solo utente, aprire il prompt UAC ogni volta può essere fastidioso, ma puoi scegliere di far ignorare questa opzione a Windhawk. Abilita questa opzione per richiedere l'elevazione UAC per l'esecuzione di Windhawk." }, "modInitDialogDelay": { "title": "Ritardo nella finestra di dialogo di inizializzazione delle mod", "description": "Quantità di millisecondi da attendere prima di mostrare la finestra di dialogo di avanzamento per l'inizializzazione della mod." }, "moreAdvancedSettings": { "title": "Altre impostazioni avanzate", "restartNotice": "Windhawk sarà riavviato per applicare le impostazioni.", "saveButton": "Salva e riavvia Windhawk", "cancelButton": "Annulla" }, "loggingVerbosity": { "appLoggingTitle": "Verbosità della registrazione di Windhawk", "engineLoggingTitle": "Verbosità della registrazione del motore di Windhawk", "description": "I log possono essere visualizzati con uno strumento come DebugView.", "none": "Nessuno", "error": "Errore", "verbose": "Dettagliato" }, "processList": { "titleExclusion": "Elenco di processi esclusi", "descriptionExclusion": "Un elenco di nomi/percorsi di processo in cui Windhawk non verrà eseguito. Può essere utile nei casi in cui Windhawk non è compatibile con un programma specifico. Tieni presente che l'aggiunta di un processo a questo elenco non solo impedirà a Windhawk di personalizzarlo, ma impedirà anche a Windhawk di intercettare i processi figlio creati da questo processo. Ciò farà sì che Windhawk carichi le mod con un leggero ritardo in questi casi.", "titleInclusion": "Elenco di inclusione del processo", "descriptionInclusion": "Un elenco di nomi/percorsi di processo in cui Windhawk verrà eseguito, anche se sono nell'elenco di esclusione.", "inclusionWithoutExclusionNotice": "L'elenco di inclusione processi non ha effetto con un elenco di esclusione processi vuoto.", "inclusionWithoutTotalExclusionNotice": "Se intendevi escludere tutti i processi tranne questi, puoi impostare \"*\" nell'elenco di esclusione dei processi.", "processListPlaceholder": "Nomi/percorsi dei processi, uno per riga, ad esempio:" } }, "about": { "title": "Windhawk v{{version}}", "beta": "beta", "subtitle": "Il mercato della personalizzazione per i programmi Windows", "credit": "Di <0>{{author}}", "update": { "title": "È disponibile un aggiornamento", "subtitle": "Prendi in considerazione l'aggiornamento di Windhawk per ottenere le ultime funzionalità e correzioni di bug", "updateButton": "Più dettagli" }, "links": { "title": "Link", "homepage": "Homepage", "documentation": "Documentazione" }, "builtWith": { "title": "Realizzato con", "vscodium": "Una distribuzione guidata dalla comunità dell'editor VSCode di Microsoft", "llvmMingw": "Una toolchain mingw-w64 basata su LLVM/Clang/LLD", "minHook": "La libreria di hook API minimalista per Windows", "others": "Altri strumenti e librerie, e un po' di codice" } }, "installModal": { "title": "Installa {{mod}}", "warningTitle": "Procedi con cura", "warningDescription": "Le mod dannose possono danneggiare il tuo computer o violare la tua privacy. Installa mod solo da autori di cui ti fidi.", "modAuthor": "Autore della mod", "homepage": "Homepage", "github": "GitHub", "twitter": "X (Twitter)", "verified": "verificato", "verifiedTooltip": "Abbiamo verificato che questo profilo appartiene all'autore della mod, ma tieni presente che <0>verificato non equivale a <0>attendibile. Assicurati di fidarti dell'autore della mod o ispeziona attentamente il codice sorgente prima dell'installazione.", "acceptButton": "Accetta il rischio e installa", "cancelButton": "Annulla" }, "createNewModButton": { "title": "Crea una nuova mod" }, "devModeAction": { "message": "La creazione e la modifica di mod richiede una certa conoscenza dello sviluppo C/C++ per Windows. Se non sei sicuro di cosa significhi, forse queste opzioni non fanno per te.", "hideOptionsCheckbox": "Nascondi tutte le opzioni relative allo sviluppo", "hideOptionsButton": "Nascondi opzioni", "beginCodingButton": "Inizia la codifica", "cancelButton": "Annulla" }, "modPreview": { "actionUnavailable": "L'azione non è disponibile in modalità anteprima", "notCompiled": "La mod deve essere compilata prima di poter essere visualizzata in anteprima" }, "sidebar": { "modId": "Identificativo della mod", "enableMod": "Abilita mod", "enableLogging": "Abilita la registrazione", "notCompiled": "La mod deve essere compilata", "compile": "Compila la mod", "compilationFailed": "Compilazione fallita", "preview": "Anteprima della mod", "showLogOutput": "Mostra output registro", "exit": "Esci dalla modalità di modifica", "exitConfirmation": "Le modifiche apportate dall'ultima compilazione andata a buon fine andranno perse", "exitButtonOk": "Esci", "exitButtonCancel": "Resta" } } ================================================ FILE: src/vscode-windhawk-ui/apps/vscode-windhawk-ui/src/locales/ja/DO_NOT_EDIT.txt ================================================ The files in this folder are generated automatically. To submit a translation update, please refer to this repository: https://github.com/ramensoftware/windhawk-translate ================================================ FILE: src/vscode-windhawk-ui/apps/vscode-windhawk-ui/src/locales/ja/translation.json ================================================ { "general": { "loading": "読み込み中...", "loadingFailed": "読み込みに失敗しました。インターネット接続を確認してください。", "loadingFailedTitle": "読み込みに失敗", "loadingFailedSubtitle": "インターネット接続を確認してもう一度お試しください。", "tryAgain": "再試行", "updating": "更新中...", "installing": "インストール中...", "compiling": "コンパイル中...", "cut": "切り取り", "copy": "コピー", "paste": "貼り付け", "selectAll": "すべて選択" }, "appHeader": { "home": "ホーム", "explore": "探す", "settings": "設定", "about": "情報" }, "mod": { "updateAvailable": "利用可能な更新があります", "installed": "インストール済み", "details": "詳細", "update": "更新", "install": "インストール", "compile": "コンパイル", "disable": "無効化", "enable": "有効化", "edit": "編集", "fork": "フォーク", "remove": "削除", "removeConfirm": "この Mod を削除してもよろしいですか?", "removeConfirmOk": "Mod を削除", "removeConfirmCancel": "キャンセル", "notCompiled": "Mod のコンパイルが必要です", "editedLocally": "Mod はローカルで編集されました", "noDescription": "(説明はありません)", "users_one": "{{formattedCount}} 名のユーザー", "users_other": "{{formattedCount}} 名のユーザー" }, "modDetails": { "header": { "installedVersion": "インストール済みのバージョン", "latestVersion": "最新のバージョン", "loading": "読み込み中...", "loadingFailed": "読み込みに失敗しました", "modId": "Mod の識別子", "modVersion": "Mod のバージョン", "modAuthor": { "title": "Mod の作者", "homepage": "ホームページ", "github": "GitHub", "twitter": "X (Twitter)" }, "processes": { "all": "すべてのプロセス", "allBut": "{{list}} 以外すべて", "except": "{{excluded}} を除く {{included}}", "tooltip": { "targets": "ターゲットのプロセス", "excluded": "除外" } }, "updateNotNeeded": "インストールされているバージョンは最新です。" }, "details": { "title": "詳細", "noData": "Mod の詳細がありません。" }, "settings": { "title": "設定", "noData": "利用可能な Mod の設定はありません。", "saveButton": "設定を保存", "sampleValue": "サンプルの値", "arrayItemAdd": "新規項目を追加", "arrayItemRemove": "項目を削除" }, "code": { "title": "ソースコード", "noData": "Mod のソースはありません。", "collapseExtra": "README と設定を折りたたむ" }, "changelog": { "title": "更新履歴", "loadingFailed": "更新履歴の読み込みに失敗しました。GitHub で表示するには、<0>ここをクリックしてください。" }, "advanced": { "title": "高度", "debugLogging": { "title": "デバッグログ", "description": "Mod に関する問題のトラブルシューティングに役立ちます。", "none": "なし", "modLogs": "Mod のログ", "detailedLogs": "詳細なデバッグログ", "showLogButton": "出力されたログを表示" }, "modSettings": { "title": "Mod の設定", "description": "Mod の設定を簡単にエクスポートまたは共有できます。", "loadButton": "読み込み", "loadFormattedButton": "フォーマットされた物を読み込み", "saveButton": "保存", "invalidData": "JSON データが無効です" }, "customList": { "titleInclusion": "カスタムプロセス対象リスト", "descriptionInclusion": "Mod がターゲットとする追加の実行ファイルまたは、パスのカスタムリストです。このリストは、どのプロセスをターゲットにするか Mod の作者が定義した対象リスト上に追加されます。各プロセスにおいて実行可能なファイルパスの対象エントリのどれかにマッチし、どの除外リストにもマッチしない場合に Mod が読み込まれます。", "titleExclusion": "カスタムプロセス除外リスト", "descriptionExclusion": "Mod がターゲットから除外する実行ファイルまたは、パスのカスタムリストです。このリストは、Mod の作者が定義した除外リスト上に追加されます。各プロセスにおいて実行可能なファイルパスが対象エントリのどれかマッチし、除外エントリにマッチしない場合に Mod が読み込まれます。", "processListPlaceholder": "プロセス名またはパス (1 行に 1 つずつ)", "invalidCharactersWarning": "プロセスリストに無効な文字が含まれています。これらは削除されます: {{invalidCharacters}}", "saveButton": "保存" }, "includeExcludeCustomOnly": { "title": "Mod の対象リスト、除外リストを無視する", "description": "Mod の対象リストまたは除外リストを無視し、上記のカスタムリストのみを使用します。" }, "patternsMatchCriticalSystemProcesses": { "title": "重要なシステムプロセスの内包リストパターンを考慮", "description": "説明: 既定では Windhawk はプロセスのパターンなし (例: <0>* または <0>*.exe ではなく <0>critical.exe) で内包されている場合でのみ、重要なシステムプロセスに Mod を読み込みます。重要なシステムプロセスの詳細については<1>ドキュメントを参照してください。" } }, "changes": { "title": "最新バージョンの変更点", "noData": "インストールされているバージョンは最新です。", "splitView": "分割で表示", "expandLines_one": "1 個の隠れた行を展開", "expandLines_other": "{{count}} 個の隠れた行を展開" } }, "modSearch": { "placeholder": "Mod を検索..." }, "home": { "browse": "Mod を探す", "installedMods": { "title": "インストール済みの Mod", "noMods": "Mod はインストールされていません" }, "featuredMods": { "title": "注目の Mod", "noMods": "まだインストールされていない注目の Mod はありません", "explore": "その他の Mod を探す" } }, "explore": { "search": { "popularAndTopRated": "人気で高評価", "popular": "人気", "topRated": "高評価", "newest": "最新", "lastUpdated": "最終更新", "alphabeticalOrder": "アルファベット順" } }, "settings": { "language": { "title": "言語", "description": "Windhawk の表示言語を選択してください。", "contribute": "Windhawk の<0>翻訳に貢献する。", "credits": "lmncaj と Keita Kunishi と <0>Re*Index.(ot_inc) によって日本語訳されました。", "creditsLink": "https://reindex-ot.github.io/" }, "updates": { "title": "更新を確認", "description": "Windhawk とインストールされている Mod の新しいバージョンを定期的に確認します。" }, "devMode": { "title": "開発者モード", "description": "Mod の作成や変更などの開発者向けのアクションを表示します。" }, "advancedSettings": "高度な設定", "hideTrayIcon": { "title": "トレイアイコンを隠す", "description": "Windhawk を終了するには、このオプションを無効化する必要があります。" }, "requireElevation": { "title": "Windhawk の実行時に UAC の昇格を要求", "description": "Windhawk の実行には管理者権限が必要ですが、シングルユーザーコンピューターの環境で毎回 UAC のプロンプトが表示されるのは煩わしいため、Windhawk はこれを回避しています。Windhawk の実行に UAC の昇格を要求させるにはこのオプションを有効化してください。" }, "dontAutoShowToolkit": { "title": "ツールキットダイアログを自動で表示しない", "description": "既定では、システムが不安定な状態やその他の理由でタスクバーにアクセスできない状態を検出すると、Windhawk は自動的にツールキットダイアログを表示します。ツールキットダイアログは、「Windhawk の終了」「セーフモードの切り替え」「その他の操作」を行なうことができます。ツールキットダイアログは、Ctrl+Win+W のキーボードショートカットでも表示できます。" }, "modInitDialogDelay": { "title": "Mod 初期化ダイアログの遅延", "description": "Mod の初期化でプログレスダイアログを表示するまでの待機時間 (ミリ秒) を設定できます。" }, "moreAdvancedSettings": { "title": "その他の高度な設定", "restartNotice": "Windhawk を再起動すると設定が適用されます。", "saveButton": "保存して Windhawk を再起動", "cancelButton": "キャンセル" }, "loggingVerbosity": { "appLoggingTitle": "Windhawk ログの冗長性", "engineLoggingTitle": "Windhawk エンジンのログの冗長性", "description": "ログはデバッグビューなどのツールで確認することができます。", "none": "なし", "error": "エラー", "verbose": "冗長" }, "processList": { "titleExclusion": "プロセス除外リスト", "descriptionExclusion": "Windhawk がインジェクションをしないプロセスとパスのリストです。Windhawk が特定のプログラムと互換性がない場合に役立ちます。このリストにプロセスを追加すると、Windhawk がそのプロセスをカスタマイズしなくなるだけでなくそのプロセスが作成する、子プロセスを Windhawk が傍受できなくなることに注意してください。場合によっては Windhawk の Mod の読み込みに若干の遅延が発生します。", "descriptionExclusionWiki": "プロセスの除外と内蔵のプロセス除外リストの詳細については、<0>ドキュメントを参照してください。", "excludeCriticalProcesses": "重要なシステムプロセスを除外", "excludeIncompatiblePrograms": "互換性のない既知のプログラムを除外", "excludeGames": "有名なゲームを除外", "titleInclusion": "プロセス包含リスト", "descriptionInclusion": "除外リストに含んでいても Windhawk がインジェクションをするプロセス名とパスのリスト", "inclusionWithoutExclusionNotice": "プロセス除外リストが空の場合、プロセス対象リストには効果がありません。", "inclusionWithoutTotalExclusionNotice": "これら以外のすべてのプロセスを除外する場合は、プロセス除外リストに「*(ワイルドカード)」を設定できます。", "processListPlaceholder": "プロセス名またはパス (1 行に 1 つずつ):", "invalidCharactersWarning": "プロセスリストに無効な文字が含まれています。これらは削除されます: {{invalidCharacters}}" } }, "about": { "title": "Windhawk v{{version}}", "beta": "ベータ版", "subtitle": "Windows プログラムのカスタマイズマーケットプレイス", "credit": "作者: <0>{{author}}", "update": { "title": "更新が利用可能です", "subtitle": "最新の機能を使用、バグの修正を適用するために Windhawk を更新することをご検討ください。", "updateButton": "その他の詳細" }, "links": { "title": "リンク", "homepage": "ホームページ", "documentation": "ドキュメント" }, "builtWith": { "title": "ビルド", "vscodium": "Microsoft VSCode エディターのコミュニティ主導型のディストリビューション", "llvmMingw": "LLVM/Clang/LLD ベースの mingw-w64 ツールチェイン", "minHook": "Windows 用の最小限 API フッキングライブラリ", "others": "その他ツールとライブラリ、ビットコード" } }, "installModal": { "title": "{{mod}} をインストール", "warningTitle": "ご注意ください", "warningDescription": "悪意のある Mod はコンピューターに損害を与えたり、プライバシーを侵害する可能性があります。信頼できる作者からの Mod のみをインストールしてください。", "modAuthor": "Mod の作者", "homepage": "ホームページ", "github": "GitHub", "twitter": "X (Twitter)", "verified": "検証済み", "verifiedTooltip": "このプロファイルが Mod の作者のものであることを確認しましたが、<0>検証済みは、<0>信頼済みと異なることに注意してください。Mod の作者が信頼できることを確認するか、インストールする前にソースコードを慎重に確認してください。", "acceptButton": "リスクを受け入れてインストール", "cancelButton": "キャンセル" }, "createNewModButton": { "title": "新規 Mod を作成" }, "devModeAction": { "message": "Mod の作成と変更には、Windows の C/C++ 開発スキルが必要です。それがよくわからない人は、このオプションは不向きかもしれません。", "hideOptionsCheckbox": "開発関連のオプションをすべて隠す", "hideOptionsButton": "オプションを隠す", "beginCodingButton": "コーディングを開始", "cancelButton": "キャンセル" }, "safeMode": { "alert": "現在の Windhawk はセーフモードで動作しています。コードインジェクション機能はすべて停止しています。", "offButton": "セーフモードを終了", "offConfirm": "Windhawk が再起動され、設定が適用されます。", "offConfirmOk": "Windhawk を再起動", "offConfirmCancel": "キャンセル" }, "modPreview": { "actionUnavailable": "アクションはプレビューモードでは使用できません。", "notCompiled": "Mod はプレビューをする前にコンパイルする必要があります" }, "sidebar": { "modId": "Mod の識別子", "enableMod": "Mod を有効化", "enableLogging": "ログの出力を有効化", "notCompiled": "Mod のコンパイルが必要です", "compile": "Mod をコンパイル", "compilationFailed": "コンパイルに失敗しました", "preview": "Mod をプレビュー", "showLogOutput": "出力されたログを表示", "exit": "編集モードを終了", "exitConfirmation": "最後にコンパイルしてからの変更は失われます", "exitButtonOk": "終了", "exitButtonCancel": "キャンセル" } } ================================================ FILE: src/vscode-windhawk-ui/apps/vscode-windhawk-ui/src/locales/ko/DO_NOT_EDIT.txt ================================================ The files in this folder are generated automatically. To submit a translation update, please refer to this repository: https://github.com/ramensoftware/windhawk-translate ================================================ FILE: src/vscode-windhawk-ui/apps/vscode-windhawk-ui/src/locales/ko/translation.json ================================================ { "general": { "loading": "불러오는 중...", "loadingFailed": "불러오는 데 실패했습니다, 인터넷 연결을 확인해주세요", "loadingFailedTitle": "불러오기 실패", "loadingFailedSubtitle": "인터넷 연결을 확인하고 다시 시도해주세요", "tryAgain": "다시 시도", "updating": "업데이트 중...", "installing": "설치 중...", "compiling": "컴파일 중...", "cut": "잘라내기", "copy": "복사", "paste": "붙여넣기", "selectAll": "모두 선택", "loggingEnabled": "디버그 로깅 활성화됨" }, "appHeader": { "home": "홈", "explore": "탐색", "settings": "설정", "about": "정보" }, "mod": { "updateAvailable": "업데이트 사용 가능", "installed": "설치됨", "details": "세부 정보", "update": "업데이트", "downgrade": "다운그레이드", "install": "설치", "compile": "컴파일", "disable": "비활성화", "enable": "활성화", "edit": "편집", "fork": "끌어오기(포크)", "remove": "삭제", "removeConfirm": "이 모드를 삭제할까요?", "removeConfirmOk": "모드 삭제", "removeConfirmCancel": "취소", "donate": "기부", "notCompiled": "모드 컴파일이 필요합니다", "editedLocally": "모드가 로컬에서 편집되었습니다", "noDescription": "(설명 없음)", "users_one": "사용자 {{formattedCount}}명", "users_other": "사용자 {{formattedCount}}명", "notRated": "이 모드는 아직 평점이 없습니다", "loggingEnabledInAdvancedTab": "이 모드의 고급 탭에서 디버그 로깅이 활성화됨" }, "modDetails": { "header": { "installedVersion": "설치된 버전", "latestVersion": "최신 버전", "loading": "불러오는 중...", "loadingFailed": "불러오기 실패", "otherVersions": "다른 버전", "selectedVersion": "선택된 버전: {{version}}", "modId": "모드 식별자", "modVersion": "모드 버전", "lastUpdated": "최근 업데이트됨", "modAuthor": { "title": "모드 작성자", "homepage": "홈페이지", "github": "GitHub", "twitter": "X (Twitter)" }, "processes": { "all": "모든 프로세스", "allBut": "{{list}} 외의 모든 프로세스", "except": "{{excluded}} 외의 {{included}}", "tooltip": { "targets": "대상 프로세스", "excluded": "제외 프로세스" } }, "updateNotNeeded": "설치되어 있는 버전이 최신 버전입니다" }, "version": { "preRelease": "프리릴리스", "select": "선택", "cancel": "취소", "chooseVersion": "버전을 선택하세요..." }, "details": { "title": "세부 정보", "noData": "모드 세부 정보가 없습니다." }, "settings": { "title": "설정", "noData": "사용 가능한 모드 설정이 없습니다.", "saveButton": "설정 저장", "sampleValue": "샘플 값", "arrayItemAdd": "새 항목 추가", "arrayItemRemove": "항목 삭제", "yamlMode": "텍스트 모드", "uiMode": "시각 모드", "yamlInvalid": "올바르지 않은 YAML 형식입니다", "yamlParseError": "YAML 구문분석 오류: {{error}}", "yamlInvalidKey": "YAML의 올바르지 않은 키: '{{key}}'(은)는 잘못된 키입니다", "yamlTypeMismatch": "'{{key}}'의 유형이 일치하지 않음: {{expected}}(이)가 와야 하는데 {{actual}}이(가) 있습니다", "unsavedChangesTitle": "저장하지 않은 설정", "unsavedChangesMessage": "저장하지 않은 변경사항이 있습니다. 변경사항을 버리시겠어요?", "unsavedChangesLeave": "변경사항 버리기", "unsavedChangesStay": "계속 편집" }, "code": { "title": "소스 코드", "noData": "모드 소스가 없습니다.", "collapseExtra": "Readme와 설정 펼치기" }, "changelog": { "title": "체인지로그", "loadingFailed": "체인지로그를 불러오는 데 실패했습니다. GitHub에서 보려면 <0>여기를 누르세요." }, "advanced": { "title": "고급", "debugLogging": { "title": "디버그 로깅", "description": "모드의 문제 해결에 유용할 수 있습니다.", "none": "없음", "modLogs": "모드 로그", "detailedLogs": "자세한 디버그 로그", "showLogButton": "로그 출력 표시" }, "modSettings": { "title": "모드 설정", "description": "모드 설정을 손쉽게 내보내거나 공유합니다.", "loadButton": "불러오기", "loadFormattedButton": "형식 불러오기", "saveButton": "저장", "invalidData": "올바르지 않은 JSON 데이터" }, "customList": { "titleInclusion": "사용자 지정 프로세스 포함 목록", "descriptionInclusion": "모드가 대상으로 할 추가 실행 파일 이름/경로의 사용자 지정 목록입니다. 이 목록은 모드 작성자가 대상 프로세스를 결정하기 위해 정의한 포함 목록에 추가됩니다. 각 프로세스에 대해 실행 파일 경로가 포함 항목 중 하나와 일치하고 제외 항목과 일치하지 않으면 모드를 불러옵니다.", "titleExclusion": "사용자 지정 프로세스 제외 목록", "descriptionExclusion": "모드가 대상에서 제외할 추가 실행 파일 이름/경로의 사용자 지정 목록입니다. 이 목록은 모드 작성자가 대상 프로세스를 결정하기 위해 정의한 제외 목록에 추가됩니다. 각 프로세스에 대해 실행 파일 경로가 포함 항목 중 하나와 일치하고 제외 항목과 일치하지 않으면 모드를 불러옵니다.", "processListPlaceholder": "한 줄당 프로세스 이름/경로 1개, 예:", "invalidCharactersWarning": "프로세스 목록에 올바르지 않은 글자가 들어 있습니다. 제거된 글자: {{invalidCharacters}}", "saveButton": "저장" }, "includeExcludeCustomOnly": { "title": "모드 포함/제외 무시 목록", "description": "모드의 프로세스 포함/제외 목록을 무시하고 상기의 사용자 지정 목록만을 사용합니다." }, "patternsMatchCriticalSystemProcesses": { "title": "중대한 시스템 프로세스를 목록에 포함하기 위해 패턴(와일드카드) 고려", "description": "기본값으로 Windhawk는 프로세스가 패턴(와일드카드)으로 포함될 경우(예: <0>* 또는 <0>*.exe)에는 모드를 중대한 시스템 프로세스 내에서는 불러오지 않습니다. (허용하는 예: <0>critical.exe) 중대한 시스템 프로세스에 대한 자세한 정보는 <1>문서를 참조해주세요." } }, "changes": { "title": "소스 코드 변경사항", "noData": "이 버전이 최신 버전입니다.", "splitView": "분할 보기", "expandLines_one": "숨겨진 줄 1개 확장", "expandLines_other": "숨겨진 줄 {{count}}개 확장" } }, "modSearch": { "placeholder": "모드 검색...", "noResults": "현재 필터와 일치하는 모드가 없습니다" }, "home": { "browse": "모드 탐색", "filter": { "enabled": "활성화된 모드", "disabled": "비활성화된 모드", "updateAvailable": "업데이트 사용 가능한 모드", "clearFilters": "필터 지우기" }, "installedMods": { "title": "설치된 모드", "noMods": "설치된 모드가 없습니다", "grid": { "name": "이름", "description": "설명", "author": "작성자", "version": "버전", "status": "상태" } }, "featuredMods": { "title": "추천 모드", "noMods": "설치한 적 없는 추천 모드가 아직 없습니다", "explore": "다른 모드 탐색" } }, "explore": { "search": { "popularAndTopRated": "인기와 평점", "popular": "인기", "topRated": "평점", "newest": "최신", "lastUpdated": "최근 업데이트", "alphabeticalOrder": "알파벳순" }, "filter": { "installationStatus": "설치 상태", "installed": "설치됨", "notInstalled": "설치 안 됨", "author": "모드 작성자", "process": "대상 프로세스", "showMore": "더 보기...", "clearFilters": "필터 지우기" } }, "settings": { "language": { "title": "언어", "description": "Windhawk의 기본 표시 언어를 선택하세요.", "contribute": "<0>새 번역 기여하기.", "credits": "한국어 번역 by <0>근육질푸키.", "creditsLink": "mailto:muscularpuky@gmail.com" }, "updates": { "title": "업데이트 확인", "description": "Windhawk와 설치된 모드의 새로운 버전을 자동으로 확인하고 알립니다." }, "devMode": { "title": "개발자 모드", "description": "모드 만들기와 수정하기와 같은 개발자들을 위한 기능을 표시합니다." }, "advancedSettings": "고급 설정", "hideTrayIcon": { "title": "트레이 아이콘 숨기기", "description": "이 옵션을 비활성화하려면 Windhawk를 종료해야 합니다." }, "alwaysCompileModsLocally": { "title": "항상 모드를 로컬에서 컴파일", "description": "기본값으로는 Windhawk가 사용 가능할 때 미리 컴파일된 모드 바이너리를 Windhawk 서버에서 다운로드합니다. 이 옵션을 활성화하면 그러는 대신 항상 모드를 로컬에서 컴파일합니다." }, "requireElevation": { "title": "Windhawk를 실행하려면 UAC 권한 상승 필요", "description": "Windhawk가 관리자 권한을 요구하지만 단일 사용자 컴퓨터에서는 매번 UAC 프롬프트가 표시되어 성가실 수 있습니다. 그래서 Windhawk는 이것을 우회합니다. 이 옵션을 활성화하면 Windhawk를 실행하는 데 UAC 권한 상승을 요구하게 됩니다." }, "dontAutoShowToolkit": { "title": "툴킷 대화 상자 자동으로 표시 안 함", "description": "기본값으로는 시스템 불안정 또는 다른 이유로 인해 작업 표시줄에 액세스할 수 없음을 감지했을 때 Windhawk가 자동으로 툴킷 대화 상자를 표시합니다. 툴킷 대화 상자는 Windhawk를 종료하고, 안전 모드로 전환하고, 기타 작업을 수행할 수 있도록 합니다. Win+Ctrl+W 키보드 단축키로 툴킷 대화 상자를 표시할 수도 있습니다." }, "modInitDialogDelay": { "title": "모드 초기화 대화 상자 지연 시간", "description": "모드 초기화를 위한 진행률 대화 상자를 표시하기 전에 기다릴 시간(밀리초)입니다." }, "moreAdvancedSettings": { "title": "더 많은 설정", "restartNotice": "이 설정을 적용하면 Windhawk가 다시 시작됩니다.", "saveButton": "저장하고 Windhawk 다시 시작", "cancelButton": "취소" }, "loggingVerbosity": { "appLoggingTitle": "Windhawk 로깅 수준", "engineLoggingTitle": "Windhawk 엔진 로깅 수준", "description": "로그는 DebugView 같은 도구로 볼 수 있습니다.", "none": "없음", "error": "오류", "verbose": "자세히" }, "processList": { "titleExclusion": "프로세스 제외 목록", "descriptionExclusion": "Windhawk가 인젝션하지 않는 프로세스 이름/경로 목록입니다. Windhawk가 특정 프로그램과 호환되지 않는 경우에 유용할 수 있습니다. 이 목록에 프로세스를 추가하면 Windhawk가 해당 프로세스를 사용자 정의하지 못할 뿐만 아니라 Windhawk가 이 프로세스가 생성하는 자식 프로세스를 가로채는 것도 방지됩니다. 이렇게 하면 Windhawk가 모드를 불러오는 데 약간의 지연이 생깁니다.", "descriptionExclusionWiki": "프로세스 제외 및 내장된 프로세스 제외 목록에 대한 자세한 내용은 <0>문서를 참조해주세요.", "excludeCriticalProcesses": "중대한 시스템 프로세스 제외", "excludeIncompatiblePrograms": "호환되지 않는다고 알려진 프로그램 제외", "excludeGames": "익히 알려진 게임 제외", "titleInclusion": "프로세스 포함 목록", "descriptionInclusion": "제외 목록에 있어도 Windhawk가 인젝션할 프로세스 이름/경로 목록입니다.", "inclusionWithoutExclusionNotice": "프로세스 포함 목록은 빈 프로세스 제외 목록에 영향을 주지 않습니다.", "inclusionWithoutTotalExclusionNotice": "여기의 프로세스들 이외의 모든 프로세스를 제외하려면 프로세스 제외 목록에 \"*\"를 설정할 수 있습니다.", "processListPlaceholder": "1줄당 프로세스 이름/경로 1개, 예:", "invalidCharactersWarning": "프로세스 목록에 올바르지 않은 글자가 들어 있습니다. 제거된 글자: {{invalidCharacters}}" } }, "about": { "title": "Windhawk v{{version}}", "beta": "베타", "subtitle": "Windows 프로그램용 사용자 정의 마켓플레이스", "credit": "By <0>{{author}}", "update": { "title": "업데이트를 사용할 수 있음", "subtitle": "최신 기능 및 버그 수정을 받으려면 Windhawk를 업데이트하세요.", "changelogButton": "체인지로그", "updateButton": "업데이트", "modal": { "title": "Windhawk 업데이트 중", "downloading": "업데이트 다운로드 중...", "installing": "업데이트 설치 중...", "installingNote": "설치가 완료되면 Windhawk가 자동으로 다시 시작됩니다.", "failed": "업데이트 실패", "cancel": "취소" } }, "changelog": { "title": "Windhawk의 새로운 기능", "close": "닫기" }, "links": { "title": "링크", "homepage": "홈페이지", "documentation": "문서" }, "builtWith": { "title": "다음으로 개발됨", "vscodium": "Microsoft의 VSCode 편집기의 커뮤니티 기반 배포", "llvmMingw": "LLVM/Clang/LLD 기반 mingw-w64 툴체인", "minHook": "Windows용 최소한의 API 후킹 라이브러리", "others": "기타 툴과 라이브러리, 그리고 약간의 코드" } }, "installModal": { "title": "{{mod}} 설치", "warningTitle": "신중히 진행하세요", "warningDescription": "악성 모드는 컴퓨터를 손상시키거나 개인 정보를 침해할 수 있습니다. 신뢰할 수 있는 작성자의 모드만 설치하세요.", "modAuthor": "모드 작성자", "homepage": "홈페이지", "github": "GitHub", "twitter": "X (Twitter)", "verified": "인증됨", "verifiedTooltip": "이 프로필이 모드 작성자의 것임을 확인했지만 <0>인증됨은 <0>신뢰할 수 있음과 다릅니다. 모드 작성자를 신뢰하는지 확인하거나 설치하기 전에 소스 코드를 신중히 검사하세요.", "acceptButton": "위험을 감수하고 설치", "cancelButton": "취소" }, "createNewModButton": { "title": "새 모드 만들기" }, "devModeAction": { "message": "모드를 만들고 수정하려면 약간의 Windows용 C/C++ 개발 지식이 필요합니다. 무슨 뜻인지 모르겠다면 이러한 옵션들은 맞지 않을지도 모릅니다.", "hideOptionsCheckbox": "모든 개발 관련 옵션 숨기기", "hideOptionsButton": "옵션 숨기기", "beginCodingButton": "코딩 시작", "cancelButton": "취소" }, "safeMode": { "alert": "Windhawk가 안전 모드로 실행 중입니다. 모든 코드 인젝션 기능이 꺼졌습니다.", "offButton": "안전 모드 끄기", "offConfirm": "이 설정을 적용하기 위해 Windhawk가 다시 시작됩니다.", "offConfirmOk": "Windhawk 다시 시작", "offConfirmCancel": "취소" }, "modPreview": { "actionUnavailable": "미리 보기 모드에서는 사용할 수 없는 작업입니다.", "notCompiled": "모드를 미리 보려면 컴파일해야 합니다." }, "sidebar": { "modId": "모드 식별자", "enableMod": "모드 활성화", "enableLogging": "로깅 활성화", "notCompiled": "모드를 컴파일해야 합니다", "compile": "모드 컴파일", "compilationFailed": "컴파일 실패", "stopCompilation": "컴파일 중지", "preview": "모드 미리 보기", "showLogOutput": "로그 출력 표시", "exit": "편집 모드 나가기", "exitConfirmation": "마지막 컴파일 성공 후의 변경이 사라집니다", "exitButtonOk": "나가기", "exitButtonCancel": "남아 있기" } } ================================================ FILE: src/vscode-windhawk-ui/apps/vscode-windhawk-ui/src/locales/nl/DO_NOT_EDIT.txt ================================================ The files in this folder are generated automatically. To submit a translation update, please refer to this repository: https://github.com/ramensoftware/windhawk-translate ================================================ FILE: src/vscode-windhawk-ui/apps/vscode-windhawk-ui/src/locales/nl/translation.json ================================================ { "general": { "loading": "Bezig met laden…", "loadingFailed": "Laden mislukt, controleer je internetverbinding", "loadingFailedTitle": "Laden mislukt", "loadingFailedSubtitle": "Controleer je internetverbinding en probeer het opnieuw", "tryAgain": "Opnieuw proberen", "updating": "Bezig met updaten…", "installing": "Bezig met installeren…", "compiling": "Bezig met compileren…", "cut": "Knippen", "copy": "Kopiëren", "paste": "Plakken", "selectAll": "Alles selecteren" }, "appHeader": { "home": "Start", "explore": "Verkennen", "settings": "Instellingen", "about": "Over" }, "mod": { "updateAvailable": "Update beschikbaar", "installed": "Geïnstalleerd", "details": "Details", "update": "Updaten", "install": "Installeren", "compile": "Compileren", "disable": "Uitschakelen", "enable": "Inschakelen", "edit": "Bewerken", "fork": "Forken", "remove": "Verwijderen", "removeConfirm": "Weet je zeker dat je deze mod wilt verwijderen?", "removeConfirmOk": "Mod verwijderen", "removeConfirmCancel": "Annuleren", "notCompiled": "Mod moet worden gecompileerd", "editedLocally": "Mod is lokaal bewerkt", "noDescription": "(geen beschrijving)", "users_one": "{{formattedCount}} gebruiker", "users_other": "{{formattedCount}} gebruikers" }, "modDetails": { "header": { "installedVersion": "Geïnstalleerde versie", "latestVersion": "Nieuwste versie", "loading": "bezig met laden…", "loadingFailed": "Laden mislukt", "modId": "Mod-identificator", "modVersion": "Mod-versie", "modAuthor": { "title": "Mod-auteur", "homepage": "Homepage", "github": "GitHub", "twitter": "X (Twitter)" }, "processes": { "all": "Alle processen", "allBut": "Alle, behalve {{list}}", "except": "{{included}} behalve {{excluded}}", "tooltip": { "targets": "Doelprocessen", "excluded": "Uitgesloten" } }, "updateNotNeeded": "Geïnstalleerde versie is identiek aan de nieuwste versie" }, "details": { "title": "Details", "noData": "Details van mod ontbreken." }, "settings": { "title": "Instellingen", "noData": "Er zijn geen mod-instellingen beschikbaar.", "saveButton": "Instellingen opslaan", "sampleValue": "Voorbeeldwaarde", "arrayItemAdd": "Nieuw item toevoegen", "arrayItemRemove": "Item verwijderen" }, "code": { "title": "Broncode", "noData": "Bron van mod ontbreekt.", "collapseExtra": "Leesmij en instellingen samenvouwen" }, "changelog": { "title": "Wijzigingslog", "loadingFailed": "Wijzigingslog laden mislukt. <0>Klik hier om het op GitHub te bekijken." }, "advanced": { "title": "Geavanceerd", "debugLogging": { "title": "Debug loggen", "description": "Kan nuttig zijn voor het oplossen van problemen met de mod.", "none": "Geen", "modLogs": "Mod-logs", "detailedLogs": "Gedetailleerde debug logs", "showLogButton": "Loguitvoer weergeven" }, "modSettings": { "title": "Mod-instellingen", "description": "Eenvoudig je mod-instellingen exporteren of delen.", "loadButton": "Laden", "loadFormattedButton": "Opgemaakt laden", "saveButton": "Opslaan", "invalidData": "Ongeldige JSON-gegevens" }, "customList": { "titleInclusion": "Aangepaste proces-opnamelijst", "descriptionInclusion": "Een aangepaste lijst met extra uitvoerbare bestandsnamen/-paden die de mod zal targeten. De lijst wordt toegevoegd aan de opnamelijst die de auteur van de mod heeft gedefinieerd om te bepalen welke processen moeten worden getarget. Voor elk proces wordt de mod geladen als het pad van het uitvoerbare bestand overeenkomt met een van de opnamevermeldingen en niet overeenkomt met een uitsluitingsvermelding.", "titleExclusion": "Aangepaste proces-uitsluitingslijst", "descriptionExclusion": "Een aangepaste lijst met extra uitvoerbare bestandsnamen/-paden die de mod zal uitsluiten van targeten. De lijst wordt toegevoegd aan de uitsluitingslijst die de auteur van de mod heeft gedefinieerd om te bepalen welke processen moeten worden getarget. Voor elk proces wordt de mod geladen als het pad van het uitvoerbare bestand overeenkomt met een van de opnamevermeldingen en niet overeenkomt met een uitsluitingsvermelding.", "processListPlaceholder": "Procesnamen/-paden, één per regel, bijvoorbeeld:", "invalidCharactersWarning": "De proceslijst bevat ongeldige tekens die zullen worden verwijderd: {{invalidCharacters}}", "saveButton": "Opslaan" }, "includeExcludeCustomOnly": { "title": "Mod opname-/uitsluitingslijsten negeren", "description": "Negeer de opname- en uitsluitingslijsten van de mod en gebruik alleen de aangepaste lijsten hierboven." }, "patternsMatchCriticalSystemProcesses": { "title": "Overweeg opnamelijst-patronen voor kritieke systeemprocessen", "description": "Standaard laadt Windhawk alleen mods in kritieke systeemprocessen als het proces zonder patronen is opgenomen, bijv. <0>critical.exe, niet <0>* of <0>*.exe. Raadpleeg <1>de documentatie voor meer informatie over kritieke systeemprocessen." } }, "changes": { "title": "Wijzigingen laatste versie", "noData": "Geïnstalleerde versie is identiek aan de nieuwste versie.", "splitView": "Gesplitste weergave", "expandLines_one": "Eén verborgen regel uitvouwen", "expandLines_other": "{{count}} verborgen regels uitvouwen" } }, "modSearch": { "placeholder": "Naar mods zoeken…" }, "home": { "browse": "Zoeken naar mods", "installedMods": { "title": "Geïnstalleerde mods", "noMods": "Er zijn geen mods geïnstalleerd" }, "featuredMods": { "title": "Aanbevolen mods", "noMods": "Er zijn geen mods die nog niet geïnstalleerd zijn.", "explore": "Adere mods verkennen" } }, "explore": { "search": { "popularAndTopRated": "Populair en best beoordeeld", "popular": "Populair", "topRated": "Best beoordeeld", "newest": "Nieuwste", "lastUpdated": "Laatst bijgewerkt", "alphabeticalOrder": "Alfabetische volgorde" } }, "settings": { "language": { "title": "Taal", "description": "Selecteer de gewenste weergavetaal voor Windhawk.", "contribute": "<0>Een nieuwe vertaling toevoegen.", "credits": "Nederlandse vertaling door <0>Toine Rademacher (toineenzo).", "creditsLink": "https://toine.zip" }, "updates": { "title": "Controleren op updates", "description": "Controleer regelmatig op nieuwe versies van Windhawk en van de geïnstalleerde mods." }, "devMode": { "title": "Ontwikkelaarsmodus", "description": "Acties weergeven voor ontwikkelaars, zoals het maken en wijzigen van mods." }, "advancedSettings": "Geavanceerde instellingen", "hideTrayIcon": { "title": "Systeemvakpictogram verbergen", "description": "Je moet deze optie uitschakelen om Windhawk af te sluiten." }, "requireElevation": { "title": "UAC-verhoging vereisen voor het uitvoeren van Windhawk", "description": "Windhawk vereist beheerdersrechten, maar voor een computer met één gebruiker kan het vervelend zijn om elke keer de UAC-melding te krijgen, dus omzeilt Windhawk deze. Schakel deze optie in om UAC-verhoging te vereisen voor het uitvoeren van Windhawk." }, "dontAutoShowToolkit": { "title": "Toolkit-melding niet automatisch weergeven", "description": "Standaard geeft Windhawk automatisch het toolkit meldingvenster weer wanneer het detecteert dat de taakbalk niet toegankelijk is, door systeeminstabiliteit of om een andere reden. Met het toolkit meldingvenster kun je Windhawk afsluiten, overschakelen naar de veilige modus en andere bewerkingen uitvoeren. Het toolkit meldingvenster kan ook worden weergegeven met de Ctrl+Win+W sneltoets." }, "modInitDialogDelay": { "title": "Mod-initialisatie meldingvertraging", "description": "Aantal milliseconden dat moet worden gewacht voordat het voortgangsvenster voor mod-initialisatie wordt weergegeven." }, "moreAdvancedSettings": { "title": "Meer geavanceerde instellingen", "restartNotice": "Windhawk wordt opnieuw opgestart om de instellingen toe te passen.", "saveButton": "Windhawk opslaan en opnieuw opstarten", "cancelButton": "Annuleren" }, "loggingVerbosity": { "appLoggingTitle": "Windhawk logging verbosity", "engineLoggingTitle": "Windhawk engine logging verbosity", "description": "Logs kunnen worden bekeken met een hulpprogramma zoals DebugView.", "none": "Geen", "error": "Fout", "verbose": "Gedetailleerd" }, "processList": { "titleExclusion": "Uitsluitingslijst verwerken", "descriptionExclusion": "Een lijst met procesnamen/-paden waarin Windhawk niet zal injecteren. Kan handig zijn voor gevallen waarin Windhawk niet compatibel is met een specifiek programma. Let er op dat het toevoegen van een proces aan deze lijst niet alleen voorkomt dat Windhawk het aanpast, maar ook voorkomt dat Windhawk subprocessen onderschept die dit proces aanmaakt. Hierdoor zal Windhawk in zulke gevallen mods met een kleine vertraging laden.", "descriptionExclusionWiki": "Raadpleeg <0>de documentatie voor meer informatie over het uitsluiten van processen en over de ingebouwde lijsten met uitgesloten processen.", "excludeCriticalProcesses": "Kritieke systeemprocessen uitsluiten", "excludeIncompatiblePrograms": "Bekende incompatibele programma's uitsluiten", "excludeGames": "Bekende spellen uitsluiten", "titleInclusion": "Proces-opnamelijst", "descriptionInclusion": "Een lijst met procesnamen/-paden waarin Windhawk zal injecteren, zelfs als ze in de uitsluitingslijst staan.", "inclusionWithoutExclusionNotice": "Proces-opnameijst heeft geen effect met een lege proces-uitsluitingslijst.", "inclusionWithoutTotalExclusionNotice": "Als je alle processen wilt uitsluiten behalve deze, kun je \"*\" in de proces-uitsluitingslijst zetten.", "processListPlaceholder": "Procesnamen/-paden, één per regel, bijvoorbeeld:", "invalidCharactersWarning": "De proceslijst bevat ongeldige tekens die zullen worden verwijderd: {{invalidCharacters}}" } }, "about": { "title": "Windhawk v{{version}}", "beta": "bèta", "subtitle": "De personalisatie marktplaats voor Windows programma's", "credit": "Door <0>{{author}}", "update": { "title": "Er is een update beschikbaar", "subtitle": "Overweeg om Windhawk bij te werken voor de nieuwste functies en bugfixes", "updateButton": "Meer informatie" }, "links": { "title": "Links", "homepage": "Homepage", "documentation": "Documentatie" }, "builtWith": { "title": "Gebouwd met", "vscodium": "Een gemeenschapsgestuurde distributie van Microsoft's VSCode-editor", "llvmMingw": "Een op LLVM/Clang/LLD gebaseerde mingw-w64 toolchain", "minHook": "De minimalistische API-hook bibliotheek voor Windows", "others": "Andere gereedschappen en bibliotheken, en een beetje code" } }, "installModal": { "title": "{{mod}} installeren", "warningTitle": "Ga voorzichtig te werk", "warningDescription": "Kwaadaardige mods kunnen je computer beschadigen of je privacy schenden. Installeer alleen mods van auteurs die je vertrouwt.", "modAuthor": "Mod-auteur", "homepage": "Homepage", "github": "GitHub", "twitter": "X (Twitter)", "verified": "geverifieerd", "verifiedTooltip": "We hebben geverifieerd dat dit profiel toebehoort aan de auteur van de mod, maar <0>geverifieerd is niet hetzelfde als <0>vertrouwd. Zorg ervoor dat je de auteur van de mod vertrouwt of inspecteer de broncode zorgvuldig voordat je installeert.", "acceptButton": "Risico accepteren en installeren", "cancelButton": "Annuleren" }, "createNewModButton": { "title": "Een nieuwe mod maken" }, "devModeAction": { "message": "Het maken en aanpassen van mods vereist enige kennis van C/C++ ontwikkeling voor Windows. Als je niet zeker weet wat dat betekent, zijn deze opties misschien niets voor jou.", "hideOptionsCheckbox": "Alle ontwikkelingsgerelateerde opties verbergen", "hideOptionsButton": "Opties verbergen", "beginCodingButton": "Begin met coderen", "cancelButton": "Annuleren" }, "safeMode": { "alert": "Windhawk draait in veilige modus, alle functionaliteit voor code-injectie is uitgeschakeld.", "offButton": "Veilige modus uitschakelen", "offConfirm": "Windhawk wordt opnieuw gestart om de instellingen toe te passen.", "offConfirmOk": "Windhawk opnieuw opstarten", "offConfirmCancel": "Annuleren" }, "modPreview": { "actionUnavailable": "Actie is niet beschikbaar in voorvertoningsmodus", "notCompiled": "Mod moet worden gecompileerd voordat deze kan worden bekeken" }, "sidebar": { "modId": "Mod-identificator", "enableMod": "Mod inschakelen", "enableLogging": "Loggen inschakelen", "notCompiled": "Mod moet worden gecompileerd", "compile": "Mod compileren", "compilationFailed": "Compileren mislukt", "preview": "Mod voorvertonen", "showLogOutput": "Loguitvoer weergeven", "exit": "Bewerkingsmodus afsluiten", "exitConfirmation": "Veranderingen sinds de laatste succesvolle compilering gaan verloren", "exitButtonOk": "Afsluiten", "exitButtonCancel": "Blijven" } } ================================================ FILE: src/vscode-windhawk-ui/apps/vscode-windhawk-ui/src/locales/pl/DO_NOT_EDIT.txt ================================================ The files in this folder are generated automatically. To submit a translation update, please refer to this repository: https://github.com/ramensoftware/windhawk-translate ================================================ FILE: src/vscode-windhawk-ui/apps/vscode-windhawk-ui/src/locales/pl/translation.json ================================================ { "general": { "loading": "Ładowanie...", "loadingFailed": "Ładowanie zakończyło się niepowodzeniem, sprawdź swoje połączenie internetowe", "loadingFailedTitle": "Ładowanie zakończyło się niepowodzeniem", "loadingFailedSubtitle": "Sprawdź swoje połączenie internetowe i spróbuj ponownie", "tryAgain": "Spróbuj ponownie", "updating": "Aktualizowanie...", "installing": "Instalowanie...", "compiling": "Kompilowanie...", "cut": "Wytnij", "copy": "Kopiuj", "paste": "Wklej", "selectAll": "Zaznacz Wszystko" }, "appHeader": { "home": "Strona główna", "explore": "Eksploruj", "settings": "Ustawienia", "about": "O aplikacji" }, "mod": { "updateAvailable": "Aktualizacja dostępna", "installed": "Zainstalowane", "details": "Informacje", "update": "Zaktualizuj", "install": "Zainstaluj", "compile": "Kompiluj", "disable": "Deaktywuj", "enable": "Aktywuj", "edit": "Edytuj", "fork": "Forkuj", "remove": "Usuń", "removeConfirm": "Czy jesteś pewny że chcesz usunąć tego moda?", "removeConfirmOk": "Usuń moda", "removeConfirmCancel": "Anuluj", "notCompiled": "Mod musi być skompilowany", "editedLocally": "Mod był edytowany lokalnie", "noDescription": "(brak opisu)", "users_one": "{{formattedCount}} użytkownik", "users_few": "{{formattedCount}} użytkownicy", "users_many": "{{formattedCount}} użytkowników", "users_other": "{{formattedCount}} użytkowników" }, "modDetails": { "header": { "installedVersion": "Zainstalowana wersja", "latestVersion": "Najnowsza wersja", "loading": "Ładowanie...", "loadingFailed": "Ładowanie zakończone niepowodzeniem", "modId": "Identyfikacja moda", "modVersion": "Wersja moda", "modAuthor": { "title": "Autor moda", "homepage": "Strona główna", "github": "GitHub", "twitter": "X (Twitter)" }, "processes": { "all": "Wszystkie procesy", "allBut": "Wszystkie ale {{list}}", "except": "{{included}} oprócz {{excluded}}", "tooltip": { "targets": "Procesy docelowe", "excluded": "Wyjątki" } }, "updateNotNeeded": "Zainstalowana wersja jest identyczna z najnowszą wersją" }, "details": { "title": "Informacje", "noData": "Informacje o modzie są niedostępne." }, "settings": { "title": "Ustawienia", "noData": "Brak dostępnych ustawień moda.", "saveButton": "Zapisz Ustawienia", "sampleValue": "Przykładowa Wartość", "arrayItemAdd": "Dodaj element", "arrayItemRemove": "Usuń element" }, "code": { "title": "Kod źródłowy", "noData": "Kod źródłowy moda jest niedostępny.", "collapseExtra": "Zwiń ustawienia i Readme" }, "changelog": { "title": "Dziennik zmian", "loadingFailed": "Ładowanie dziennika zmian zakończone niepowodzeniem. <0>Kliknij tutaj aby zobaczyć na GitHubie." }, "advanced": { "title": "Zaawansowane", "debugLogging": { "title": "Logi debugowania", "description": "Przydatne do rozwiązywania problemów z modami.", "none": "Nic", "modLogs": "Logi moda", "detailedLogs": "Szczegółowe logi debugowania", "showLogButton": "Pokaż wyjście logów" }, "modSettings": { "title": "Ustawienia moda", "description": "Łatwo wyeksportuj i udostępnij ustawienia moda", "loadButton": "Wczytaj", "loadFormattedButton": "Wczytaj sformatowane", "saveButton": "Zapisz", "invalidData": "Niepoprawne informacje JSON" }, "customList": { "titleInclusion": "Niestandardowa lista uwzględniania procesów", "descriptionInclusion": "Niestandardowa lista dodatkowych nazw/ścieżek plików wykonywalnych, na które mod będzie ukierunkowany. Lista ta jest dodawana do listy dołączania zdefiniowanej przez autora moda w celu określenia, które procesy mają być celem. Dla każdego procesu mod jest ładowany, jeśli ścieżka pliku wykonywalnego pasuje do jednego z wpisów włączenia i nie pasuje do żadnego wpisu wyłączenia.", "titleExclusion": "Niestandardowa lista wykluczonych procesów", "descriptionExclusion": "Niestandardowa lista dodatkowych nazw/ścieżek plików wykonywalnych, które mod wykluczy z namierzania. Lista ta jest dodawana do listy wykluczeń zdefiniowanej przez autora moda w celu określenia, które procesy mają być docelowe. Dla każdego procesu mod jest ładowany, jeśli ścieżka pliku wykonywalnego pasuje do jednego z wpisów włączenia i nie pasuje do żadnego wpisu wyłączenia.", "processListPlaceholder": "Nazwy/ścieżki procesów, po jednej w wierszu, na przykład:", "saveButton": "Zapisz" }, "includeExcludeCustomOnly": { "title": "Ignoruj listy włączeń/wykluczeń modów", "description": "Używaj powyższych list włączeń/wykluczeń zamiast list ustawionych przez mody." } }, "changes": { "title": "Zmiany w Najnowszej Wersji", "noData": "Zainstalowana wersja jest identyczna z najnowszą wersją", "splitView": "Widok podzielony", "expandLines_one": "Rozwiń jedną ukrytą linię", "expandLines_few": "Rozwiń {{count}} ukryte linie", "expandLines_many": "Rozwiń {{count}} ukrytych linii", "expandLines_other": "Rozwiń {{count}} ukrytych linii" } }, "modSearch": { "placeholder": "Szukaj modów.." }, "home": { "browse": "Przeglądaj w poszukiwaniu modów", "installedMods": { "title": "Zainstalowane mody", "noMods": "Nie zainstalowano żadnych modów" }, "featuredMods": { "title": "Wyróżnione mody", "noMods": "Nie ma żadnych polecanych modów, które nie zostały jeszcze zainstalowane", "explore": "Eksploruj inne mody" } }, "explore": { "search": { "popularAndTopRated": "Popularne i najbardziej ocenianie", "popular": "Popularne", "topRated": "Najbardziej oceniane", "newest": "Najnowsze", "lastUpdated": "Ostatnio aktualizowane", "alphabeticalOrder": "Kolejność alfabetyczna" } }, "settings": { "language": { "title": "Język", "description": "Wybierz preferowany język wyświetlania dla aplikacji Windhawk.", "contribute": "<0>Współtwórz nowe tłumaczenie.", "credits": "Polskie tłumaczenie stworzył <0>Inkatail oraz CyprinusCarpio (https://github.com/CyprinusCarpio), a funn-hue poprawił.", "creditsLink": "https://github.com/Inkatail" }, "updates": { "title": "Sprawdź aktualizacje", "description": "Okresowo sprawdzaj dostępność nowych wersji Windhawk i zainstalowanych modów." }, "devMode": { "title": "Tryb dewelopera", "description": "Pokaż akcje dla deweloperów, takie jak tworzenie i modyfikowanie modów." }, "advancedSettings": "Ustawienia zaawansowane", "hideTrayIcon": { "title": "Ukryj ikonę w zasobniku", "description": "Będziesz musiał wyłączyć tę opcję, aby wyjść z Windhawk." }, "requireElevation": { "title": "Wymagaj uprawnień UAC do uruchomienia Windhawk", "description": "Windhawk wymaga uprawnień administratora, ale dla komputera z jednym użytkownikiem wyświetlanie komunikatu UAC za każdym razem może być irytujące, więc Windhawk go omija. Włącz tę opcję, aby wymagać uprawnień UAC do uruchamiania Windhawk." }, "dontAutoShowToolkit": { "title": "Nie wyświetlaj automatycznie okna dialogowego narzędzi", "description": "Domyślnie Windhawk automatycznie wyświetla okno dialogowe narzędzi, gdy wykryje, że pasek zadań nie jest dostępny z powodu niestabilności systemu lub z innego powodu. Okno dialogowe narzędzi pozwala wyjść z Windhawk, przełączyć się do trybu bezpiecznego i wykonać inne operacje. Okno dialogowe narzędzi można również wyświetlić za pomocą skrótu klawiaturowego Ctrl+Win+W." }, "modInitDialogDelay": { "title": "Opóźnienie okna dialogowego inicjalizacji modów", "description": "Ilość milisekund do odczekania przed wyświetleniem okna dialogowego postępu inicjalizacji modów." }, "moreAdvancedSettings": { "title": "Więcej ustawień zaawansowanych", "restartNotice": "Windhawk musi zostać uruchomiony ponownie, aby zastosować ustawienia.", "saveButton": "Zapisz i uruchom ponownie Windhawk", "cancelButton": "Anuluj" }, "loggingVerbosity": { "appLoggingTitle": "Szczegółowe logi Windhawk", "engineLoggingTitle": "Szczegółowe logi silnika Windhawk", "description": "Logi mogą zostać wyswietlone używając narzędzia DebugView", "none": "Nic", "error": "Błąd", "verbose": "Szczegóły" }, "processList": { "titleExclusion": "Lista wykluczonych procesów", "descriptionExclusion": "Lista nazw procesów/ścieżek, do których Windhawk nie będzie wstrzykiwał. Może być przydatna w przypadkach, gdy Windhawk nie jest kompatybilny z określonym programem. Należy pamiętać, że dodanie procesu do tej listy nie tylko uniemożliwi Windhawk dostosowanie go, ale także uniemożliwi Windhawk przechwytywanie procesów potomnych tworzonych przez ten proces. Spowoduje to, że w takich przypadkach Windhawk będzie ładował mody z niewielkim opóźnieniem.", "titleInclusion": "Lista uwzględnionych procesów", "descriptionInclusion": "Lista nazw/ścieżek procesów, do których Windhawk będzie wstrzykiwał dane, nawet jeśli znajdują się one na liście wykluczeń.", "inclusionWithoutExclusionNotice": "Lista uwzględnionych procesów nie ma wpływu na pustą listę wykluczania procesów.", "inclusionWithoutTotalExclusionNotice": "Jeśli chcesz wykluczyć wszystkie procesy oprócz tych, możesz ustawić \"*\" na liście wykluczeń procesów.", "processListPlaceholder": "Nazwy/ścieżki procesów, po jednej w wierszu, na przykład:" } }, "about": { "title": "Windhawk v{{version}}", "beta": "beta", "subtitle": "Rynek personalizacji dla programów Windows", "credit": "Stworzył <0>{{author}}", "update": { "title": "Aktualizacja jest dostępna", "subtitle": "Rozważ aktualizację Windhawk, aby uzyskać najnowsze funkcje i poprawki błędów", "updateButton": "Więcej informacji" }, "links": { "title": "Linki", "homepage": "Strona główna", "documentation": "Dokumentacja" }, "builtWith": { "title": "Zrobione używając", "vscodium": "Społecznościowa dystrybucja edytora VSCode firmy Microsoft", "llvmMingw": "Oparty na toolchainie LLVM/Clang/LLD dla mingw-w64", "minHook": "Minimalistyczna biblioteka do instalowanie hooków dla Windows", "others": "Inne narzędzia i biblioteki oraz trochę kodu" } }, "installModal": { "title": "Zainstaluj {{mod}}", "warningTitle": "Postępuj ostrożnie", "warningDescription": "Złośliwe mody mogą uszkodzić komputer lub naruszyć prywatność użytkownika. Instaluj mody tylko od zaufanych autorów.", "modAuthor": "Autor moda", "homepage": "Strona główna", "github": "GitHub", "twitter": "X (Twitter)", "verified": "zweryfikowany", "verifiedTooltip": "Zweryfikowaliśmy, że ten profil należy do autora moda, ale należy pamiętać, że <0>zweryfikowany to nie to samo, co <0>zaufany. Upewnij się, że ufasz autorowi moda lub dokładnie sprawdź kod źródłowy przed instalacją.", "acceptButton": "Zaakceptuj ryzyko i zainstaluj", "cancelButton": "Anuluj" }, "createNewModButton": { "title": "Utwórz nowego moda" }, "devModeAction": { "message": "Tworzenie i modyfikowanie modów wymaga pewnej wiedzy na temat programowania w językach C/C++. Jeśli nie jesteś pewien, co to oznacza, być może te opcje nie są dla Ciebie.", "hideOptionsCheckbox": "Ukryj wszystkie opcje związane z programowaniem", "hideOptionsButton": "Ukryj opcje", "beginCodingButton": "Rozpocznij kodowanie", "cancelButton": "Anuluj" }, "safeMode": { "alert": "Windhawk działa w trybie bezpiecznym, wszystkie funkcje wstrzykiwania kodu są wyłączone.", "offButton": "Wyłącz tryb bezpieczny", "offConfirm": "Windhawk zostanie uruchomiony ponownie, aby zastosować ustawienia.", "offConfirmOk": "Zrestartuj Windhawk", "offConfirmCancel": "Anuluj" }, "modPreview": { "actionUnavailable": "Opcja nie jest dostępna w trybie podglądowym", "notCompiled": "Mod musi zostać skompilowany przed podglądem" }, "sidebar": { "modId": "Identyfikator moda", "enableMod": "Włącz moda", "enableLogging": "Włącz dziennik zdarzeń", "notCompiled": "Mod musi być skompilowany", "compile": "Kompiluj moda", "compilationFailed": "Kompilacja zakończyła się sukcesem", "preview": "Podgląd modu", "showLogOutput": "Pokaż dane dziennika", "exit": "Wyjdź z trybu edycji", "exitConfirmation": "Zmiany wprowadzone od ostatniej udanej kompilacji zostaną utracone", "exitButtonOk": "Wyjdź", "exitButtonCancel": "Pozostań" } } ================================================ FILE: src/vscode-windhawk-ui/apps/vscode-windhawk-ui/src/locales/pt-BR/DO_NOT_EDIT.txt ================================================ The files in this folder are generated automatically. To submit a translation update, please refer to this repository: https://github.com/ramensoftware/windhawk-translate ================================================ FILE: src/vscode-windhawk-ui/apps/vscode-windhawk-ui/src/locales/pt-BR/translation.json ================================================ { "general": { "loading": "Carregando...", "loadingFailed": "Falha ao carregar, verifique sua conexão", "loadingFailedTitle": "Falha no carregamento", "loadingFailedSubtitle": "Por favor verifique sua conexão e tente novamente", "tryAgain": "Tente novamente", "updating": "Atualizando...", "installing": "Instalando...", "compiling": "Compilando...", "cut": "Cortar", "copy": "Copiar", "paste": "Colar", "selectAll": "Selecionar tudo" }, "appHeader": { "home": "Início", "explore": "Explorar", "settings": "Configurações", "about": "Sobre" }, "mod": { "updateAvailable": "Atualização disponivel", "installed": "Instalado", "details": "Detalhes", "update": "Atualizar", "install": "Instalar", "compile": "Compilar", "disable": "Desabilitar", "enable": "Habilitar", "edit": "Editar", "fork": "Parar", "remove": "Remover", "removeConfirm": "Tem certeza de que deseja remover este Mod?", "removeConfirmOk": "Remover Mod", "removeConfirmCancel": "Cancelar", "notCompiled": "O Mod precisa ser compilado", "editedLocally": "O Mod foi editado localmente", "noDescription": "(sem descrição)", "users_one": "{{formattedCount}} usuário", "users_many": "{{formattedCount}} usuários", "users_other": "{{formattedCount}} usuários" }, "modDetails": { "header": { "installedVersion": "Versão instalada", "latestVersion": "Última versão", "loading": "Carregando...", "loadingFailed": "Falha ao Carregar", "modId": "Identificador de Mod", "modVersion": "Versão do Mod", "modAuthor": { "title": "Autor do Mod", "homepage": "Site Pessoal", "github": "GitHub", "twitter": "X (Twitter)" }, "processes": { "all": "Todos os Processos", "allBut": "Todos exceto {{list}}", "except": "{{included}} exeto {{excluded}}", "tooltip": { "targets": "Processos alvo", "excluded": "Excluído" } }, "updateNotNeeded": "A versão instalada é idêntica à versão mais recente" }, "details": { "title": "Detalhes", "noData": "Faltam detalhes do Mod." }, "settings": { "title": "Configurações", "noData": "Nenhuma configuração de Mod está disponível.", "saveButton": "Salvar configurações", "sampleValue": "Valor da amostra", "arrayItemAdd": "Adicionar novo item", "arrayItemRemove": "Remover item" }, "code": { "title": "Código fonte", "noData": "A fonte do Mod está ausente.", "collapseExtra": "Recolher Leia-me e Configurações" }, "changelog": { "title": "Log de alterações", "loadingFailed": "Falha ao carregar a Log de Alterações. <0>Clique aqui para visualizá-lo no GitHub." }, "advanced": { "title": "Avançado", "debugLogging": { "title": "Log de depuração", "description": "Pode ser útil para solucionar problemas com Mods.", "none": "Nada", "modLogs": "Logs de modificação", "detailedLogs": "Logs de depuração detalhados", "showLogButton": "Mostrar saída de Logs" }, "modSettings": { "title": "Configurações de Mod", "description": "Exporte ou compartilhe facilmente suas configurações de Mod.", "loadButton": "Carregar", "loadFormattedButton": "Carregar formatado", "saveButton": "Salvar", "invalidData": "Dados JSON inválidos" }, "customList": { "titleInclusion": "Lista de inclusão de processo personalizado", "descriptionInclusion": "Uma lista personalizada de nomes/caminhos de arquivos executáveis ​adicionais que o Mod terá como alvo. A lista é adicionada à lista de inclusão que o autor do Mod definiu para determinar quais processos devem ser direcionados. Para cada processo, o Mod é carregado se o caminho do arquivo executável corresponder a uma das entradas de inclusão e não corresponder a nenhuma entrada de exclusão.", "titleExclusion": "Lista de exclusão de processo personalizada", "descriptionExclusion": "Uma lista personalizada de nomes/caminhos de arquivos executáveis ​​adicionais que o Mod excluirá do direcionamento. A lista é adicionada à lista de exclusão que o autor do Mod definiu para determinar quais processos visar. Para cada processo, o Mod é carregado se o caminho do arquivo executável corresponder a uma das entradas de inclusão e não corresponder a nenhuma entrada de exclusão.", "processListPlaceholder": "Nomes/caminhos de processo, um por linha, por exemplo:", "saveButton": "Salvar" }, "includeExcludeCustomOnly": { "title": "Ignorar listas de inclusão/exclusão de mods", "description": "Ignora as listas de inclusão/exclusão de processos do mod e usar apenas as listas personalizadas acima." } }, "changes": { "title": "Últimas alterações da versão", "noData": "A versão instalada é idêntica à versão mais recente.", "splitView": "Viualização dividida", "expandLines_one": "Expandir uma linha oculta", "expandLines_many": "Expandir {{count}} linhas ocultas", "expandLines_other": "Expandir {{count}} linhas ocultas" } }, "modSearch": { "placeholder": "Pesquisar Mods..." }, "home": { "browse": "Pesquisar Mods", "installedMods": { "title": "Mods Instalados", "noMods": "Nenhum mod instalado" }, "featuredMods": { "title": "Mods em Destaque", "noMods": "Não há Mods em destaque que ainda não foram instalados", "explore": "Explorar outros Mods" } }, "explore": { "search": { "popularAndTopRated": "Popular e Melhor Avaliado", "popular": "Popular", "topRated": "Melhor Avaliado", "newest": "Recente", "lastUpdated": "Recem Atualizados", "alphabeticalOrder": "Ordem Alfabetica" } }, "settings": { "language": { "title": "Idioma", "description": "Selecione o idioma de exibição.", "contribute": "<0>Contribua com uma tradução.", "credits": "Tradução para o Português Brasileiro por: <0>Cauaã Vinicius Moraes (C4UA).", "creditsLink": "mailto:cauaaviniciusmoraes@gmail.com" }, "updates": { "title": "Verificar atualizações", "description": "Verifica periodicamente por novas versões do Windhawk e dos mods instalados." }, "devMode": { "title": "Modo desenvolvedor", "description": "Mostrar ações para desenvolvedores, como criar e editar mods." }, "advancedSettings": "Configurações avançadas", "hideTrayIcon": { "title": "Ocultar ícone da bandeja", "description": "Você terá que desabilitar esta opção para sair do Windhawk." }, "requireElevation": { "title": "Exigir elevação UAC para executar Windhawk", "description": "O Windhawk requer direitos de administrador, mas para um computador de usuário único, obter o prompt do UAC toda vez pode ser irritante, então o Windhawk o ignora. Ative esta opção para exigir a elevação do UAC para executar o Windhawk." }, "dontAutoShowToolkit": { "title": "Não mostrar automaticamente a caixa de diálogo do kit de ferramentas", "description": "Por padrão, o Windhawk mostra automaticamente a caixa de diálogo do kit de ferramentas quando detecta que a barra de tarefas não está acessível, devido à instabilidade do sistema ou por outro motivo. A caixa de diálogo do kit de ferramentas permite sair do Windhawk, alternar para o modo de segurança e fazer outras operações. A caixa de diálogo do kit de ferramentas também pode ser exibida com o atalho de teclado Ctrl+Win+W." }, "modInitDialogDelay": { "title": "Atraso da caixa de diálogo de inicialização do mod", "description": "Quantidade de milissegundos antes de mostrar a caixa de progresso para inicialização do mod." }, "moreAdvancedSettings": { "title": "Mais configurações avançadas", "restartNotice": "Windhawk será reiniciado para aplicar as configurações.", "saveButton": "Salve e reinicie o Windhawk", "cancelButton": "Cancelar" }, "loggingVerbosity": { "appLoggingTitle": "Verbosidade da Log do Windhawk", "engineLoggingTitle": "Verbosidade da Log do mecanismo Windhawk", "description": "Os logs podem ser visualizados com uma ferramenta como DebugView.", "none": "Nada", "error": "Erro", "verbose": "Verbosidade" }, "processList": { "titleExclusion": "Lista de exclusão de processo", "descriptionExclusion": "Uma lista de nomes/caminhos de processos nos quais o Windhawk não injetará. Pode ser útil em casos que o Windhawk não é compatível com um programa específico. Observe que adicionar um processo a esta lista não apenas impedirá Windhawk de personalizá-lo, mas também impedirá Windhawk de interceptar processos filho criados por esse processo. Isso fará com que o Windhawk carregue os mods com um pequeno atraso nesses casos.", "titleInclusion": "Lista de inclusão de processos", "descriptionInclusion": "Uma lista de nomes/caminhos de processos nos quais o Windhawk injetará, mesmo que estejam na lista de exclusão.", "inclusionWithoutExclusionNotice": "A lista de inclusão de processo não tem efeito com uma lista de exclusão de processo vazia.", "inclusionWithoutTotalExclusionNotice": "Se você pretende excluir todos os processos exceto estes, você pode definir \"*\" na lista de exclusão de processos.", "processListPlaceholder": "Nomes/caminhos de processo, um por linha, por exemplo:" } }, "about": { "title": "Windhawk v{{version}}", "beta": "beta", "subtitle": "O suíte de personalização para programas do Windows", "credit": "Por: <0>{{author}}", "update": { "title": "Nova atualização disponível", "subtitle": "Atualize o Windhawk para obter novos recursos e correções de bugs", "updateButton": "Mais detalhes" }, "links": { "title": "Links", "homepage": "Site Oficial", "documentation": "Documentação" }, "builtWith": { "title": "Feito com", "vscodium": "Distribuição voltada para a comunidade do editor VSCode da Microsoft", "llvmMingw": "Uma cadeia de ferramentas mingw-w64 baseada em LLVM/Clang/LLD", "minHook": "Biblioteca de conexão de API minimalista para Windows", "others": "Outras ferramentas e bibliotecas e um pouco de código" } }, "installModal": { "title": "Instalar {{mod}}", "warningTitle": "Prossiga com cautela", "warningDescription": "Mods maliciosos podem danificar seu computador ou violar sua privacidade. Instale mods apenas de autores em quem você confia.", "modAuthor": "Autor do mod", "homepage": "Site Pessoal", "github": "GitHub", "twitter": "X (Twitter)", "verified": "Verificado", "verifiedTooltip": "Verificamos que este perfil pertence ao autor do mod, mas observe que <0>verificado não é o mesmo que <0>confiável. Certifique-se de confiar no autor do mod ou inspecione cuidadosamente o código-fonte antes de instalar.", "acceptButton": "Aceitar os riscos e instalar", "cancelButton": "Cancelar" }, "createNewModButton": { "title": "Criar um novo Mod" }, "devModeAction": { "message": "Criar e editar mods requer algum conhecimento da linguagem C/C++ para Windows. Se você não tem certeza do que isso significa, talvez essas opções não sejam para você.", "hideOptionsCheckbox": "Ocultar todas as opções relacionadas ao desenvolvimento", "hideOptionsButton": "Esconder opções", "beginCodingButton": "Começar a codificar", "cancelButton": "Cancelar" }, "safeMode": { "alert": "O Windhawk está sendo executado no modo de segurança, todas as funcionalidades de injeção de código estão desativadas.", "offButton": "Sair do modo de segurança", "offConfirm": "Windhawk será reiniciado para aplicar as configurações.", "offConfirmOk": "Reiniciar Windhawk", "offConfirmCancel": "Cancelar" }, "modPreview": { "actionUnavailable": "A ação não está disponível no modo de visualização", "notCompiled": "Mod precisa ser compilado para poder ser vizualizado" }, "sidebar": { "modId": "Identificador de Mod", "enableMod": "Habilitar Mod", "enableLogging": "Habilitar carregamento", "notCompiled": "O mod precisa ser compilado", "compile": "Compilar Mod", "compilationFailed": "Falha na compilação", "preview": "Vizualizar Mod", "showLogOutput": "Mostrar Log de saida", "exit": "Sair do modo de edição", "exitConfirmation": "As alterações desde a última compilação bem-sucedida serão perdidas", "exitButtonOk": "Sair", "exitButtonCancel": "Cancelar" } } ================================================ FILE: src/vscode-windhawk-ui/apps/vscode-windhawk-ui/src/locales/ro/DO_NOT_EDIT.txt ================================================ The files in this folder are generated automatically. To submit a translation update, please refer to this repository: https://github.com/ramensoftware/windhawk-translate ================================================ FILE: src/vscode-windhawk-ui/apps/vscode-windhawk-ui/src/locales/ro/translation.json ================================================ { "general": { "loading": "Se încarcă...", "loadingFailed": "Nu s-a putut încărca, verifică-ți conexiunea la internet", "loadingFailedTitle": "Nu s-a putut încărca", "loadingFailedSubtitle": "Verifică-ți conexiunea la rețea și încearcă din nou", "tryAgain": "Încearcă din nou", "updating": "Se actualizează...", "installing": "Se instalează...", "compiling": "Se compilează...", "cut": "Decupare", "copy": "Copiere", "paste": "Lipire", "selectAll": "Selectează tot" }, "appHeader": { "home": "Acasă", "explore": "Explorează", "settings": "Setări", "about": "Despre" }, "mod": { "updateAvailable": "Actualizare disponibilă", "installed": "Instalat", "details": "Detalii", "update": "Actualizare", "install": "Instalează", "compile": "Compilează", "disable": "Dezactivează", "enable": "Activează", "edit": "Editează", "fork": "Fork", "remove": "Elimină", "removeConfirm": "Sigur elimini acest mod?", "removeConfirmOk": "Elimină modul", "removeConfirmCancel": "Anulează", "notCompiled": "Modul trebuie să fie compilat", "editedLocally": "Modul a fost editat local", "noDescription": "(fără descriere)", "users_one": "{{formattedCount}} utilizator", "users_few": "{{formattedCount}} utilizatori", "users_other": "{{formattedCount}} de utilizatori" }, "modDetails": { "header": { "installedVersion": "Versiune instalată", "latestVersion": "Cea mai recentă versiune", "loading": "Se încarcă...", "loadingFailed": "Nu s-a putut încărca", "modId": "Identificator mod", "modVersion": "Versiune mod", "modAuthor": { "title": "Autorul modului", "homepage": "Pagina de pornire", "github": "GitHub", "twitter": "X (Twitter)" }, "processes": { "all": "Toate procesele", "allBut": "Toate, cu excepția {{list}}", "except": "{{included}} exceptând {{excluded}}", "tooltip": { "targets": "Procese țintă", "excluded": "Excluse" } }, "updateNotNeeded": "Versiunea instalată este identică cu cea recentă" }, "details": { "title": "Detalii", "noData": "Detaliile modului lipsesc." }, "settings": { "title": "Setări", "noData": "Nu sunt disponibile setări pentru acest mod.", "saveButton": "Salvează setările", "sampleValue": "Valoarea eșantionului", "arrayItemAdd": "Adaugă un nou element", "arrayItemRemove": "Elimină element" }, "code": { "title": "Cod sursă", "noData": "Codul sursă lipsește.", "collapseExtra": "Restrânge Readme și Setări" }, "changelog": { "title": "Jurnal de modificări", "loadingFailed": "Nu s-a putut încărca jurnalul de modificări. <0>Clic aici pentru a-l vedea pe GitHub." }, "advanced": { "title": "Avansat", "debugLogging": { "title": "Jurnal de depanare", "description": "Poate fi folositor pentru a depana modul.", "none": "Nimic", "modLogs": "Jurnalul modului", "detailedLogs": "Jurnal detaliat de depanare", "showLogButton": "Arată ieșirea în jurnal" }, "modSettings": { "title": "Setări mod", "description": "Exportă sau distribuie cu ușurință setările tale ale acestui mod", "loadButton": "Încarcă", "loadFormattedButton": "Încarcă formatat", "saveButton": "Salvează", "invalidData": "Date JSON invalide" }, "customList": { "titleInclusion": "Listă personalizată de includere a proceselor", "descriptionInclusion": "O listă personalizată de nume/căi de fișiere executabile suplimentare pe care modul le va viza. Lista este adăugată la lista de includere definită de autorul modului pentru a determina ce procese trebuie să fie vizate. Pentru fiecare proces, modul este încărcat dacă calea fișierului executabil se potrivește cu una dintre intrările de includere și nu se potrivește cu nicio intrare de excludere.", "titleExclusion": "Listă personalizată de excludere a proceselor", "descriptionExclusion": "O listă personalizată de nume/căi de fișiere executabile suplimentare pe care modul nu le va lua în considerare. Lista se adaugă la lista de excludere definită de autorul modului pentru a determina ce procese să fie vizate. Pentru fiecare proces, modul este încărcat dacă calea fișierului executabil se potrivește cu una dintre intrările de includere și nu se potrivește cu nicio intrare de excludere.", "processListPlaceholder": "Numele/calea procesului, câte unul/una pe o singură linie, de exemplu:", "invalidCharactersWarning": "Lista de procese conține caractere nevalide ce vor fi eliminate: {{invalidCharacters}}", "saveButton": "Salvează" }, "includeExcludeCustomOnly": { "title": "Ignoră listele de includeri/excluderi a modurilor", "description": "Ignoră listele de includeri/excluderi a proceselor modului și folosește numai listele personalizate de mai sus." }, "patternsMatchCriticalSystemProcesses": { "title": "Ia în considerare pattern-urile pentru lista de includere a procesele de sistem critice", "description": "În mod normal, Windhawk încarcă moduri în procesele de sistem critice numai dacă procesul este inclus fără pattern-uri, de exemplu <0>proces_critic.exe, nu <0>* sau <0>*.exe. Pentru mai multe detalii despre procesele de sistem critice, consultă <1>documentația." } }, "changes": { "title": "Modificări în cea mai recentă versiune", "noData": "Versiunea instalată este identică cu cea mai recentă.", "splitView": "Vizualizare divizată", "expandLines_one": "Extinde o linie ascunsă", "expandLines_few": "Extinde {{count}} linii ascunse", "expandLines_other": "Extinde {{count}} de linii ascunse" } }, "modSearch": { "placeholder": "Caută moduri..." }, "home": { "browse": "Răsfoiește moduri", "installedMods": { "title": "Moduri instalate", "noMods": "Niciun mod instalat" }, "featuredMods": { "title": "Moduri promovate", "noMods": "Nu există moduri promovate care n-au fost încă instalate", "explore": "Explorează alte moduri" } }, "explore": { "search": { "popularAndTopRated": "Populare și top evaluate", "popular": "Populare", "topRated": "Top evaluate", "newest": "Cele mai noi", "lastUpdated": "Actualizate recent", "alphabeticalOrder": "Ordine alfabetică" } }, "settings": { "language": { "title": "Limbă", "description": "Selectează limba ta preferată de afișare pentru Windhawk.", "contribute": "<0>Contribuie pentru o nouă traducere.", "credits": "Tradus în limba română de <0>Valer100.", "creditsLink": "https://github.com/Valer100" }, "updates": { "title": "Verifică pentru actualizări", "description": "Verifică periodic pentru noi versiuni de Windhawk și pentru modurile instalate." }, "devMode": { "title": "Mod dezvoltator", "description": "Arată acțiuni pentru dezvoltatori, cum ar fi crearea și modificarea modurilor." }, "advancedSettings": "Setări avansate", "hideTrayIcon": { "title": "Ascunde pictograma Windhawk din bara de activități", "description": "Va trebui să dezactivezi această opțiune pentru a închide Windhawk." }, "requireElevation": { "title": "Cere elevare UAC pentru a rula Windhawk", "description": "Windhawk necesită drepturi de administrator, dar pentru un calculator folosit de o singură persoană, afișarea ecranului UAC de fiecare dată poate fi enervantă, așa că Windhawk ocolește acest lucru. Activează această opțiune pentru a cere elevare UAC pentru a rula Windhawk." }, "dontAutoShowToolkit": { "title": "Nu arăta automat dialogul cu trusa de unelte", "description": "În mod normal, Windhawk arată automat dialogul cu trusa de unelte când detectează că bara de activități nu este accesibilă, fie din motive de instabilitate a sistemului sau din alt motiv. Dialogul cu trusa de unelte îți permite să închizi Windhawk, să activezi modul de siguranță și să faci alte operațiuni. Dialogul cu trusa de unelte poate fi de asemenea afișat folosind combinația de taste Ctrl+Win+W." }, "modInitDialogDelay": { "title": "Întârzierea dialogului de inițializare a modului", "description": "Numărul de milisecunde de așteptat înainte de a arăta dialogul de progres pentru inițializarea modului." }, "moreAdvancedSettings": { "title": "Mai multe setări avansate", "restartNotice": "Windhawk va fi repornit pentru a aplica setările.", "saveButton": "Salvează și repornește Windhawk", "cancelButton": "Anulare" }, "loggingVerbosity": { "appLoggingTitle": "Verbozitatea scrierii în jurnal pentru Windhawk", "engineLoggingTitle": "Verbozitatea engine-ului de scriere în jurnal Windhawk", "description": "Jurnalurile pot fi văzute cu o unealtă precum DebugView.", "none": "Nimic", "error": "Eroare", "verbose": "Verbose" }, "processList": { "titleExclusion": "Lista de excludere a proceselor", "descriptionExclusion": "O listă de nume/căi de procese pe care Windhawk nu le va putea injecta. Poate fi utilă pentru cazurile în care Windhawk nu este compatibil cu un anumit program. Ține minte că adăugând un proces în această listă nu va doar preveni Windhawk de a-l personaliza, dar va preveni de asemenea Windhawk din a intercepta procesele pe care acesta le creează. Acest lucru va cauza ca Windhawk să încarce modurile cu o mică întârziere în astfel de cazuri.", "descriptionExclusionWiki": "Pentru mai multe detalii despre excluderea proceselor și despre listele incluse de excludere a proceselor, consultă <0>documentația.", "excludeCriticalProcesses": "Exclude procesele de sistem critice", "excludeIncompatiblePrograms": "Exclude programele incompatibile cunoscute", "excludeGames": "Exclude jocuri bine cunoscute", "titleInclusion": "Lista de includere a proceselor", "descriptionInclusion": "O listă de nume/căi de procese pe care Windhawk le va putea injecta, chiar dacă ele sunt în lista de excludere.", "inclusionWithoutExclusionNotice": "Lista proceselor incluse nu are niciun efect dacă lista de procese excluse este goală.", "inclusionWithoutTotalExclusionNotice": "Dacă te refereai la excluderea tuturor proceselor, dar nu acestea, poți adăuga \"*\" în lista de excludere a proceselor.", "processListPlaceholder": "Numele/căile proceselor, câte unul/una pe un singur rând, de exemplu:", "invalidCharactersWarning": "Lista de procese conține caractere nevalide ce vor fi eliminate: {{invalidCharacters}}" } }, "about": { "title": "Windhawk v{{version}}", "beta": "beta", "subtitle": "Piața de personalizare pentru programele Windows", "credit": "De <0>{{author}}", "update": { "title": "O actualizare este disponibilă", "subtitle": "Ia în considerare să actualizezi Windhawk pentru a obține cele mai noi funcții și remedieri de bug-uri", "updateButton": "Mai multe detalii" }, "links": { "title": "Linkuri", "homepage": "Pagina de pornire", "documentation": "Documentație" }, "builtWith": { "title": "Construit cu", "vscodium": "O distribuție a editorului VSCode de la Microsoft condusă de comunitate", "llvmMingw": "Un lanț de instrumente mingw-w64 bazat pe LLVM/Clang/LLD", "minHook": "Biblioteca minimalistă de API hooking pentru Windows", "others": "Alte unelte și biblioteci și puțin cod" } }, "installModal": { "title": "Instalează {{mod}}", "warningTitle": "Continuă cu prudență", "warningDescription": "Modurile malițioase pot provoca daune calculatorului tău sau pot să-ți încalce confidențialitatea. Instalează moduri numai de la autorii de încredere.", "modAuthor": "Autorul modului", "homepage": "Pagina de pornire", "github": "GitHub", "twitter": "X (Twitter)", "verified": "verificat", "verifiedTooltip": "Am verificat că acest profil îi aparține autorului modului, dar <0>persoanele verificate nu pot fi întotdeauna <0>de încredere. Asigură-te că autorul acestui mod este de încredere sau inspectează cu atenție codul sursă înainte de instalare.", "acceptButton": "Acceptă riscul și instalează", "cancelButton": "Anulare" }, "createNewModButton": { "title": "Creează un mod" }, "devModeAction": { "message": "Crearea și modificarea modurilor necesită niște cunoștințe de dezvoltare Windows C/C++. Dacă nu știi ce înseamnă asta, se pare că aceste opțiuni nu sunt pentru tine.", "hideOptionsCheckbox": "Ascunde toate opțiunile relevante dezvoltării", "hideOptionsButton": "Ascunde opțiunile", "beginCodingButton": "Începe să codezi", "cancelButton": "Anulare" }, "safeMode": { "alert": "Windhawk rulează în modul de siguranță. Toate funcționalitățile de injectat cod sunt dezactivate.", "offButton": "Oprește modul de siguranță", "offConfirm": "Windhawk va fi repornit pentru a aplica setările.", "offConfirmOk": "Repornește Windhawk", "offConfirmCancel": "Anulare" }, "modPreview": { "actionUnavailable": "Acțiunea nu este disponibilă în modul de previzualizare", "notCompiled": "Modul trebuie să fie compilat pentru a-l putea previzualiza" }, "sidebar": { "modId": "Identificator mod", "enableMod": "Activează modul", "enableLogging": "Activează scrierea în jurnal", "notCompiled": "Modul trebuie să fie compilat", "compile": "Compilează modul", "compilationFailed": "Compilare eșuată", "preview": "Previzualizează modul", "showLogOutput": "Arată ieșirea în jurnal", "exit": "Ieșire din modul de editare", "exitConfirmation": "Modificările de la ultima compilare reușită vor fi pierdute", "exitButtonOk": "Ieșire", "exitButtonCancel": "Stai" } } ================================================ FILE: src/vscode-windhawk-ui/apps/vscode-windhawk-ui/src/locales/ru/DO_NOT_EDIT.txt ================================================ The files in this folder are generated automatically. To submit a translation update, please refer to this repository: https://github.com/ramensoftware/windhawk-translate ================================================ FILE: src/vscode-windhawk-ui/apps/vscode-windhawk-ui/src/locales/ru/translation.json ================================================ { "general": { "loading": "Загрузка...", "loadingFailed": "Загрузка не удалась, пожалуйста, проверьте ваше соединение с интернетом", "loadingFailedTitle": "Загрузка не удалась", "loadingFailedSubtitle": "Пожалуйста, проверьте ваше соединение с интернетом и попытайтесь снова", "tryAgain": "Попробовать ещё раз", "updating": "Обновление...", "installing": "Установка...", "compiling": "Компилирование...", "cut": "Вырезать", "copy": "Копировать", "paste": "Вставить", "selectAll": "Выбрать все" }, "appHeader": { "home": "Главная", "explore": "Поиск", "settings": "Параметры", "about": "О программе" }, "mod": { "updateAvailable": "Доступно обновление", "installed": "Установлено", "details": "Описание", "update": "Обновить", "install": "Установить", "compile": "Скомпилировать", "disable": "Отключить", "enable": "Включить", "edit": "Редактировать", "fork": "Созд. копию", "remove": "Удалить", "removeConfirm": "Вы уверены, что хотите удалить эту модификацию?", "removeConfirmOk": "Удалить модификацию", "removeConfirmCancel": "Отмена", "notCompiled": "Модификация должна быть скомпилирована", "editedLocally": "Модификация отредактирована локально", "noDescription": "(нет описания)", "users_one": "{{formattedCount}} пользователь", "users_few": "{{formattedCount}} пользователя", "users_many": "{{formattedCount}} пользователей", "users_other": "{{formattedCount}} пользователей" }, "modDetails": { "header": { "installedVersion": "Установленная версия", "latestVersion": "Последняя версия", "loading": "загрузка...", "loadingFailed": "загрузка не удалась", "modId": "ID модификации", "modVersion": "Версия модификации", "modAuthor": { "title": "Автор модификации", "homepage": "Главная страница", "github": "GitHub", "twitter": "X (Твиттер)" }, "processes": { "all": "Все процессы", "allBut": "Все, кроме {{list}}", "except": "{{included}} кроме {{excluded}}", "tooltip": { "targets": "Целевые процессы", "excluded": "Исключённые" } }, "updateNotNeeded": "Установлена самая последняя версия" }, "details": { "title": "Описание", "noData": "Описание модификации отсутствует." }, "settings": { "title": "Настройки", "noData": "Нет доступных настроек модификации.", "saveButton": "Сохранить настройки", "sampleValue": "Стандартное значение", "arrayItemAdd": "Добавить новый элемент", "arrayItemRemove": "Удалить элемент" }, "code": { "title": "Исходный код", "noData": "Исходный код отсутствует.", "collapseExtra": "Скрыть настройки и Readme" }, "changelog": { "title": "Список изменений", "loadingFailed": "Не удалось загрузить список изменений. Нажмите <0>сюда, чтобы просмотреть на GitHub." }, "advanced": { "title": "Дополнительно", "debugLogging": { "title": "Логирование отладки", "description": "Может быть использовано для решения проблем с модификацией.", "none": "Нет", "modLogs": "Логи модификаций", "detailedLogs": "Подробные логи отладки", "showLogButton": "Показать логи" }, "modSettings": { "title": "Настройки модификации", "description": "Легко экспортируйте или делитесь вашими настройками модификаций.", "loadButton": "Загрузить", "loadFormattedButton": "Загрузить отформатированные", "saveButton": "Сохранить", "invalidData": "Неверные JSON-данные" }, "customList": { "titleInclusion": "Пользовательский список целевых процессов", "descriptionInclusion": "Пользовательский список с дополнительными файлами exe или путями к ним, которые затронет модификация. Этот список будет добавлен к списку затронутых процессов, который определил сам автор модификации. То есть, модификация загружается для каждого процесса, только если путь совпадает с одним из списка целевых и не совпадает с любым из списка исключённых.", "titleExclusion": "Пользовательский список исключённых", "descriptionExclusion": "Пользовательский список с дополнительными файлами exe или путями к ним, которые модификация НЕ затронет. Этот список будет добавлен к списку исключения от автора модификации. То есть, модификация загружается для каждого процесса, только если путь совпадает с одним из списка целевых и не совпадает с любым из списка исключённых.", "processListPlaceholder": "Имя процесса (exe)/пути, по одному в строке, например:", "saveButton": "Сохранить" }, "includeExcludeCustomOnly": { "title": "Игнорировать списки целевых/исключённых модификаций", "description": "Игнорировать списки целевых/исключённых процессов, установленных модом, и используйте только пользовательские списки, приведённые выше." } }, "changes": { "title": "Изменения в последней версии", "noData": "Установлена самая последняя версия.", "splitView": "Раздельный просмотр", "expandLines_one": "Раскрыть одну скрытую линию", "expandLines_few": "Раскрыть {{count}} скрытые линии", "expandLines_many": "Раскрыть {{count}} скрытых линий", "expandLines_other": "Раскрыть {{count}} скрытых линий" } }, "modSearch": { "placeholder": "Искать модификации..." }, "home": { "browse": "Поиск модификаций", "installedMods": { "title": "Установленные модификации", "noMods": "Модификации не установлены" }, "featuredMods": { "title": "Рекомендованные модификации", "noMods": "Нет рекомендованных модификаций, которые ещё не установлены", "explore": "Искать другие модификации" } }, "explore": { "search": { "popularAndTopRated": "Популярные и с наивысшим рейтингом", "popular": "Популярные", "topRated": "С наивысшим рейтингом", "newest": "Новые", "lastUpdated": "Недавно обновленные", "alphabeticalOrder": "Алфавитный порядок" } }, "settings": { "language": { "title": "Язык", "description": "Выберите желаемый язык интерфейса для Windhawk.", "contribute": "<0>Добавить новый перевод.", "credits": "Русский перевод от <0>Сергея Крылова с некоторыми правками от berry, а также от других членов сообщества.", "creditsLink": "mailto:krylov.sn@nxt.ru" }, "updates": { "title": "Проверить обновления", "description": "Периодически проверять наличие обновлений для Windhawk и установленных модификаций." }, "devMode": { "title": "Режим разработчика", "description": "Показать действия для разработчиков, например создание и редактирование модификаций." }, "advancedSettings": "Продвинутые параметры", "hideTrayIcon": { "title": "Скрыть иконку в трее", "description": "Вам нужно отключить этот параметр для выхода из Windhawk." }, "requireElevation": { "title": "Требовать UAC для запуска Windhawk", "description": "Windhawk нуждается в правах администратора, но для компьютеров с одним пользователем постоянные запросы от UAC надоедают, поэтому Windhawk их обходит. Включите этот параметр, чтобы вернуть UAC запросы на запуск Windhawk." }, "dontAutoShowToolkit": { "title": "Не показывать автоматически диалог меню", "description": "По умолчанию Windhawk автоматически вызывает диалог меню когда обнаруживает, что панель задач не работает из-за нестабильности системы или другой причины. Диалог меню позволяет закрыть Windhawk, перейти в безопасный режим и совершать другие действия. Диалог меню можно также вызвать комбинацией клавиш Ctrl+Win+W." }, "modInitDialogDelay": { "title": "Задержка диалога инициализации модификации", "description": "Количество миллисекунд ожидания перед показом диалогового окна прогресса инициализации модификации." }, "moreAdvancedSettings": { "title": "Дополнительные продвинутые параметры", "restartNotice": "Windhawk будет перезапущен для применения настроек.", "saveButton": "Сохранить и перезапустить Windhawk", "cancelButton": "Отмена" }, "loggingVerbosity": { "appLoggingTitle": "Подробное логирование Windhawk", "engineLoggingTitle": "Подробное логирование движка Windhawk", "description": "Логи могут быть просмотрены, например утилитой DebugView.", "none": "Нет", "error": "Ошибка", "verbose": "Подробно" }, "processList": { "titleExclusion": "Список исключённых процессов", "descriptionExclusion": "Список процессов/пути, в которые Windhawk не будет внедрять код. Может быть полезно в случаях, когда Windhawk несовместим с определённой программой. Обратите внимание, что добавление процесса в этот список не только не даст Windhawk его изменять, но и предотвратит Windhawk от перехвата дочерних процессов, созданных исключённым. Это вызовет задержку при загрузке модификаций в Windhawk.", "titleInclusion": "Список целевых процессов", "descriptionInclusion": "Список целевых процессов/путей, в которые Windhawk будет внедрять код, даже если они в списке исключённых.", "inclusionWithoutExclusionNotice": "Список целевых процессов ничего не делает, если список исключённых процессов пуст.", "inclusionWithoutTotalExclusionNotice": "Если вы хотите исключить все процессы, кроме этих, вы можете задать «*» в списке исключённых процессов.", "processListPlaceholder": "Имя процесса (exe)/пути, по одному в строке, например:" } }, "about": { "title": "Windhawk вер. {{version}}", "beta": "бета", "subtitle": "Маркетплейс для кастомизации программ Windows", "credit": "Автор: <0>{{author}}", "update": { "title": "Доступно обновление", "subtitle": "Обновите Windhawk для получения доступа к новейшим функциям и исправления ошибок", "updateButton": "Подробнее" }, "links": { "title": "Ссылки", "homepage": "Главная страница", "documentation": "Документация" }, "builtWith": { "title": "Скомпилировано с", "vscodium": "Версия от сообщества редактора VSCode от Microsoft", "llvmMingw": "LLVM/Clang/LLD на базе среды mingw-w64", "minHook": "Минималистичная API hooking библиотека для Windows", "others": "Другие инструменты и библиотеки, а также немного написанного кода" } }, "installModal": { "title": "Установить {{mod}}", "warningTitle": "Продолжайте осторожно", "warningDescription": "Вредоносные модификации могут повредить ваш компьютер и нарушить вашу приватность. Устанавливайте модификации только от авторов, которым доверяете.", "modAuthor": "Автор модификации", "homepage": "Главная страница", "github": "GitHub", "twitter": "X (Твиттер)", "verified": "подтверждён", "verifiedTooltip": "Мы подтверждаем, что этот профиль принадлежит автору модификации, но обратите внимание, что <0>подтверждённый не значит <0>доверенный. Убедитесь, что вы доверяете автору модификации или тщательно проверьте исходный код перед установкой.", "acceptButton": "Принять риск и продолжить", "cancelButton": "Отмена" }, "createNewModButton": { "title": "Создать новую модификацию" }, "devModeAction": { "message": "Создание и изменение модификаций требует некоторых знаний C/C++ в Windows. Если вы не понимаете о чём речь, возможно эти параметры не для вас.", "hideOptionsCheckbox": "Спрятать все параметры для разработчиков", "hideOptionsButton": "Спрятать параметры", "beginCodingButton": "Начать писать код", "cancelButton": "Отмена" }, "safeMode": { "alert": "Windhawk работает в безопасном режиме, кастомизация программ отключена.", "offButton": "Выключить безопасный режим", "offConfirm": "Windhawk будет перезапущен для применения настроек.", "offConfirmOk": "Перезапустить Windhawk", "offConfirmCancel": "Отмена" }, "modPreview": { "actionUnavailable": "Действие недоступно в режиме просмотра", "notCompiled": "Необходимо скомпилировать модификацию для просмотра" }, "sidebar": { "modId": "ID модификации", "enableMod": "Включить модификацию", "enableLogging": "Включить логирование", "notCompiled": "необходимо скомпилировать модификацию", "compile": "Скомпилировать модификацию", "compilationFailed": "Компиляция не удалась", "preview": "Просмотр модификации", "showLogOutput": "Показать логи", "exit": "Выйти из режима редактирования", "exitConfirmation": "Изменения после последней успешной компиляции будут потеряны", "exitButtonOk": "Выйти", "exitButtonCancel": "Остаться" } } ================================================ FILE: src/vscode-windhawk-ui/apps/vscode-windhawk-ui/src/locales/sv/DO_NOT_EDIT.txt ================================================ The files in this folder are generated automatically. To submit a translation update, please refer to this repository: https://github.com/ramensoftware/windhawk-translate ================================================ FILE: src/vscode-windhawk-ui/apps/vscode-windhawk-ui/src/locales/sv/translation.json ================================================ { "general": { "loading": "Laddar...", "loadingFailed": "Det gick inte att ladda, kontrollera din internetanslutning", "loadingFailedTitle": "Det gick inte att ladda", "loadingFailedSubtitle": "Kontrollera din internetanslutning och försök igen", "tryAgain": "Försök igen", "updating": "Uppdaterar...", "installing": "Installerar...", "compiling": "Kompileras...", "cut": "Klipp ut", "copy": "Kopiera", "paste": "Klistra in", "selectAll": "Markera allt" }, "appHeader": { "home": "Hem", "explore": "Utforska", "settings": "Inställningar", "about": "Om" }, "mod": { "updateAvailable": "Uppdatering tillgänglig", "installed": "Installerad", "details": "Detaljer", "update": "Uppdatering", "install": "Installera", "compile": "Kompilera", "disable": "Inaktivera", "enable": "Aktivera", "edit": "Redigera", "fork": "Fork", "remove": "Ta bort", "removeConfirm": "Är du säker på att du vill ta bort denna mod?", "removeConfirmOk": "Ta bort mod", "removeConfirmCancel": "Avbryt", "notCompiled": "Modden måste kompileras", "editedLocally": "Modden har redigerats lokalt", "noDescription": "(ingen beskrivning)", "users_one": "{{formattedCount}} användare", "users_other": "{{formattedCount}} användare" }, "modDetails": { "header": { "installedVersion": "Installerad version", "latestVersion": "Senaste version", "loading": "laddar...", "loadingFailed": "laddningen misslyckades", "modId": "Modd-identifierare", "modVersion": "Modd-version", "modAuthor": { "title": "Modd-upphovsman", "homepage": "Hemsida", "github": "GitHub", "twitter": "X (Twitter)" }, "processes": { "all": "Alla processor", "allBut": "Alla utom {{list}}", "except": "{{included}} utom {{excluded}}", "tooltip": { "targets": "Målprocesser", "excluded": "Uteslutna" } }, "updateNotNeeded": "Den installerade versionen är identisk med den senaste versionen" }, "details": { "title": "Detaljer", "noData": "Modd-detaljer saknas." }, "settings": { "title": "Inställningar", "noData": "Inga tillgängliga modd-inställningar.", "saveButton": "Spara inställningarna", "sampleValue": "Exempelvärde", "arrayItemAdd": "Lägg till nytt objekt", "arrayItemRemove": "Ta bort objekt" }, "code": { "title": "Källkod", "noData": "Modd-källa saknas.", "collapseExtra": "Fäll ihop viktigt och inställningar" }, "changelog": { "title": "Ändringslogg", "loadingFailed": "Det gick inte att läsa in ändringsloggen. <0>Klicka här för att läsa den på GitHub." }, "advanced": { "title": "Avancerat", "debugLogging": { "title": "Felsökningsloggning", "description": "Kan vara användbart för att felsöka problem med modden.", "none": "Inga", "modLogs": "Modd-loggar", "detailedLogs": "Detaljerade felsökningsloggar", "showLogButton": "Visa loggutdata" }, "modSettings": { "title": "Modd-inställningar", "description": "Exportera eller dela enkelt dina modd-inställningar.", "loadButton": "Ladda", "loadFormattedButton": "Ladda formaterad", "saveButton": "Spara", "invalidData": "Ogiltig JSON-data" }, "customList": { "titleInclusion": "Anpassad processinkluderingslista", "descriptionInclusion": "En anpassad lista över ytterligare körbara filnamn/sökvägar som modden kommer att fungera med. Listan läggs till i inkluderingslistan som mod-utvecklaren definierade för att avgöra vilka processer den ska fungera med. För varje process laddas modden om den körbara filsökvägen matchar en av inkluderingsposterna och inte matchar någon undantagspost.", "titleExclusion": "Anpassad undantagslista för processer", "descriptionExclusion": "En anpassad lista med ytterligare körbara filnamn/sökvägar som modden inte kommer att fungera med. Listan läggs till i undantagslistan som mod-utvecklaren definierade för att bestämma vilka processer den inte ska fungera med. För varje process laddas modden om den körbara filsökvägen matchar en av inkluderingsposterna och inte matchar någon undantagspost.", "processListPlaceholder": "Processnamn/sökvägar, en per rad, till exempel:", "saveButton": "Spara" }, "includeExcludeCustomOnly": { "title": "Ignorera modd-inkluderings-/exkluderingslistor", "description": "Ignorera moddens processinkluderings-/exkluderingslistor och använd endast de anpassade listorna ovan." } }, "changes": { "title": "Senaste versionsändringar", "noData": "Den installerade versionen är identisk med den senaste versionen.", "splitView": "Delad vy", "expandLines_one": "Expandera en dold rad", "expandLines_other": "Expandera {{count}} dolda rader" } }, "modSearch": { "placeholder": "Sök efter moddar..." }, "home": { "browse": "Bläddra efter moddar", "installedMods": { "title": "Installerade moddar", "noMods": "Inga moddar är installerade" }, "featuredMods": { "title": "Utvalda moddar", "noMods": "Det finns inga utvalda moddar som inte har installerats ännu", "explore": "Utforska andra moddar" } }, "explore": { "search": { "popularAndTopRated": "Populär och högst rankad", "popular": "Populär", "topRated": "Högst rankad", "newest": "Nyaste", "lastUpdated": "Senast uppdaterade", "alphabeticalOrder": "Alfabetisk ordning" } }, "settings": { "language": { "title": "Språk", "description": "Välj önskat visningsspråk för Windhawk.", "contribute": "<0>Bidra med en ny översättning.", "credits": "Svensk översättning av <0>Sopor.", "creditsLink": "mailto:sopor@users.noreply.github.com" }, "updates": { "title": "Sök efter uppdateringar", "description": "Kontrollera med jämna mellanrum efter nya versioner av Windhawk och de installerade moddarna." }, "devMode": { "title": "Utvecklarläge", "description": "Visa åtgärder för utvecklare som att skapa och ändra moddar." }, "advancedSettings": "Avancerade inställningar", "hideTrayIcon": { "title": "Dölj meddelandefältsikonen", "description": "Du måste inaktivera det här alternativet för att avsluta Windhawk." }, "requireElevation": { "title": "Kräv UAC-förhöjda rättigheter för att köra Windhawk", "description": "Windhawk kräver administratörsrättigheter, men för en enanvändardator kan det vara irriterande att se UAC-prompten varje gång, så Windhawk går förbi den. Aktivera det här alternativet för att kräva UAC-förhöjda rättigheter för att köra Windhawk." }, "dontAutoShowToolkit": { "title": "Visa inte dialogrutan för toolkit automatiskt", "description": "Som standard visar Windhawk dialogrutan för toolkit automatiskt när den upptäcker att aktivitetsfältet inte är tillgängligt, antingen på grund av systeminstabilitet eller av annan anledning. Dialogruta för toolkit gör det möjligt att avsluta Windhawk, växla till säkert läge och göra andra operationer. Dialogruta för toolkit kan också visas med kortkommandot Ctrl-Win+W." }, "modInitDialogDelay": { "title": "Fördröjning för modd-initialiseringsdialog", "description": "Antal millisekunder att vänta innan förloppsdialogrutan visas för modd-initiering." }, "moreAdvancedSettings": { "title": "Fler avancerade inställningar", "restartNotice": "Windhawk kommer att startas om för att verkställa inställningarna.", "saveButton": "Spara och starta om Windhawk", "cancelButton": "Avbryt" }, "loggingVerbosity": { "appLoggingTitle": "Windhawk-loggning", "engineLoggingTitle": "Windhawk-motorloggning", "description": "Loggar kan till exempel ses med verktyget DebugView.", "none": "Inga", "error": "Fel", "verbose": "Utförlig" }, "processList": { "titleExclusion": "Processundantagslista", "descriptionExclusion": "En lista över processnamn/sökvägar som Windhawk inte kommer att injiceras i. Kan vara användbart när Windhawk inte är kompatibel med ett specifikt program. Observera att om du lägger till en process i den här listan hindrars Windhawk från att anpassa den, men Windhawk hindras även från att fånga upp underordnade processer som den här processen skapar. Detta kommer i sådana fall att få Windhawk att ladda moddar med en liten fördröjning.", "titleInclusion": "Processinkluderingslista", "descriptionInclusion": "En lista över processnamn/sökvägar som Windhawk kommer att injicera i även om de finns i undantagslistan.", "inclusionWithoutExclusionNotice": "Processinkluderingslistan har ingen effekt med en tom processundantagslista.", "inclusionWithoutTotalExclusionNotice": "Om du menade att utesluta alla processer utom dessa, då kan du ställa in \"*\" i listan över processuteslutningar.", "processListPlaceholder": "Processnamn/sökvägar, en per rad, till exempel:" } }, "about": { "title": "Windhawk v{{version}}", "beta": "beta", "subtitle": "Anpassningsmarknaden för Windows-program", "credit": "Av <0>{{author}}", "update": { "title": "En uppdatering är tillgänglig", "subtitle": "Överväg att uppdatera Windhawk för att få de senaste funktionerna och buggfixarna", "updateButton": "Fler detaljer" }, "links": { "title": "Länkar", "homepage": "Hemsida", "documentation": "Dokumentation" }, "builtWith": { "title": "Byggd med", "vscodium": "En samhällsdriven distribution av Microsofts VSCode-redigerare", "llvmMingw": "En LLVM/Clang/LLD-baserad mingw-w64 verktygskedja", "minHook": "Det minimalistiska API-hooking-biblioteket för Windows", "others": "Andra verktyg och bibliotek och lite kod" } }, "installModal": { "title": "Installera {{mod}}", "warningTitle": "Fortsätt, men ta det försiktigt", "warningDescription": "Skadliga moddar kan skada din dator eller kränka din integritet. Installera endast moddar från utvecklare du litar på.", "modAuthor": "Modd-utvecklare", "homepage": "Hemsida", "github": "GitHub", "twitter": "X (Twitter)", "verified": "verifierad", "verifiedTooltip": "Vi har verifierat att den här profilen tillhör modd-utvecklaren, men notera att <0>verifierad inte är detsamma som <0>betrodd. Se till att du litar på modd-utvecklaren eller inspektera källkoden noggrant innan du installerar.", "acceptButton": "Acceptera risken och installera", "cancelButton": "Avbryt" }, "createNewModButton": { "title": "Skapa en ny modd" }, "devModeAction": { "message": "Att skapa och ändra moddar kräver viss kunskap om C/C++-utveckling för Windows. Om du inte är säker på vad det betyder är dessa alternativen säkert inget för dig.", "hideOptionsCheckbox": "Dölj alla utvecklingsrelaterade alternativ", "hideOptionsButton": "Dölj alternativ", "beginCodingButton": "Börja koda", "cancelButton": "Avbryt" }, "safeMode": { "alert": "Windhawk körs i felsäkert läge och all kodinjektion är avstängd.", "offButton": "Stäng av felsäkert läge", "offConfirm": "Windhawk kommer att startas om för att verkställa inställningarna.", "offConfirmOk": "Starta om Windhawk", "offConfirmCancel": "Avbryt" }, "modPreview": { "actionUnavailable": "Åtgärden är inte tillgänglig i förhandsgranskningsläget", "notCompiled": "Modden måste kompileras innan den kan förhandsgranskas" }, "sidebar": { "modId": "Modd-identifierare", "enableMod": "Aktivera modd", "enableLogging": "Aktivera logging", "notCompiled": "Modden måste kompileras", "compile": "Kompilera modden", "compilationFailed": "Kompileringen misslyckades", "preview": "Förhandsgranska modden", "showLogOutput": "Visa loggutdata", "exit": "Avsluta redigeringsläge", "exitConfirmation": "Ändringar sedan den senaste lyckade kompileringen kommer att gå förlorade", "exitButtonOk": "Avsluta", "exitButtonCancel": "Stanna kvar" } } ================================================ FILE: src/vscode-windhawk-ui/apps/vscode-windhawk-ui/src/locales/ta/DO_NOT_EDIT.txt ================================================ The files in this folder are generated automatically. To submit a translation update, please refer to this repository: https://github.com/ramensoftware/windhawk-translate ================================================ FILE: src/vscode-windhawk-ui/apps/vscode-windhawk-ui/src/locales/ta/translation.json ================================================ { "general": { "loading": "ஏற்றுகிறது...", "loadingFailed": "ஏற்றல் தோல்வியடைந்தது, உங்கள் இணைய இணைப்பைச் சரிபார்க்கவும்", "loadingFailedTitle": "ஏற்றல் தோல்வியடைந்தது", "loadingFailedSubtitle": "தயவுசெய்து உங்கள் இணைய இணைப்பைச் சரிபார்த்து மீண்டும் முயற்சிக்கவும்", "tryAgain": "மீண்டும் முயற்சிக்கவும்", "updating": "புதுப்பிக்கிறது...", "installing": "நிறுவுகிறது...", "compiling": "கணக்கிடுகிறது...", "cut": "வெட்டு", "copy": "பிரதி எடு", "paste": "ஒட்டு", "selectAll": "அனைத்தையும் தேர்ந்தெடு" }, "appHeader": { "home": "முகப்பு", "explore": "ஆராயவும்", "settings": "அமைப்புகள்", "about": "பற்றி" }, "mod": { "updateAvailable": "புதுப்பிப்பு கிடைக்கிறது", "installed": "நிறுவப்பட்டது", "details": "விவரங்கள்", "update": "புதுப்பி", "install": "நிறுவு", "compile": "கணக்கிடு", "disable": "முடக்கு", "enable": "இயக்கு", "edit": "தொகு", "fork": "பிளவு", "remove": "அகற்று", "removeConfirm": "இந்த மாற்றத்தை நீங்கள் நிச்சயமாக அகற்ற விரும்புகிறீர்களா?", "removeConfirmOk": "மாற்றத்தை அகற்று", "removeConfirmCancel": "ரத்து செய்", "notCompiled": "மாற்றம் கணக்கிடப்பட வேண்டும்", "editedLocally": "மாற்றம் உள்ளூர் முறையில் திருத்தப்பட்டது", "noDescription": "(விளக்கம் இல்லை)", "users_one": "{{formattedCount}} பயனர்", "users_other": "{{formattedCount}} பயனர்கள்" }, "modDetails": { "header": { "installedVersion": "நிறுவப்பட்ட பதிப்பு", "latestVersion": "புதிய பதிப்பு", "loading": "ஏற்றுகிறது...", "loadingFailed": "ஏற்றல் தோல்வியடைந்தது", "modId": "மாற்ற அடையாளம்", "modVersion": "மாற்றம் பதிப்பு", "modAuthor": { "title": "மாற்றம் எழுத்தாளர்", "homepage": "முகப்பு பக்கம்", "github": "கிட்ஹப்", "twitter": "X (ட்விட்டர்)" }, "processes": { "all": "அனைத்து செயல்முறைகள்", "allBut": "{{list}} தவிர", "except": "{{included}} தவிர {{excluded}}", "tooltip": { "targets": "இலக்கு செயல்முறைகள்", "excluded": "விலக்கப்பட்டது" } }, "updateNotNeeded": "நிறுவப்பட்ட பதிப்பு மற்றும் புதிய பதிப்பு ஒரே மாதிரியானவை" }, "details": { "title": "விவரங்கள்", "noData": "மாற்றம் விவரங்கள் இல்லை." }, "settings": { "title": "அமைப்புகள்", "noData": "மாற்றம் அமைப்புகள் இல்லை.", "saveButton": "அமைப்புகளைச் சேமிக்கவும்", "sampleValue": "மாதிரி மதிப்பு", "arrayItemAdd": "புதிய உருப்படியைச் சேர்", "arrayItemRemove": "உருப்படியை அகற்று" }, "code": { "title": "மூல குறியீடு", "noData": "மாற்றத்தின் மூலக்குறியீடு இல்லை.", "collapseExtra": "Readme மற்றும் அமைப்புகளை மடக்கு" }, "changelog": { "title": "மாற்றப்பட்ட பதிவுகள்", "loadingFailed": "மாற்றப்பட்ட பதிவுகளை ஏற்ற முடியவில்லை. <0>இங்கே கிளிக் செய்யவும் GitHub இல் பார்க்க." }, "advanced": { "title": "மேம்பட்டவை", "debugLogging": { "title": "பிழை திருத்த பதிவுகள்", "description": "மாற்றத்துடன் உள்ள சிக்கல்களை தீர்க்க இது பயனுள்ளதாக இருக்கலாம்.", "none": "இல்லை", "modLogs": "மாற்றம் பதிவுகள்", "detailedLogs": "விவரமான பிழை திருத்த பதிவுகள்", "showLogButton": "பதிவுகள் வெளியீட்டைக் காட்டு" }, "modSettings": { "title": "மாற்றம் அமைப்புகள்", "description": "உங்கள் மாற்றம் அமைப்புகளை எளிதில் ஏற்றுமதி செய்யலாம் அல்லது பகிரலாம்.", "loadButton": "ஏற்று", "loadFormattedButton": "வடிவமைக்கப்பட்டதை ஏற்று", "saveButton": "சேமி", "invalidData": "தவறான JSON தரவு" }, "customList": { "titleInclusion": "தனிப்பயன் செயல்முறை சேர்க்கை பட்டியல்", "descriptionInclusion": "மாற்றம் இலக்காக அமைக்கப்படும் கூடுதல் செயலியாக்கக்கூடிய கோப்புகளின் பெயர்கள்/பாதைகள் கொண்ட தனிப்பயன் பட்டியல். இது எழுத்தாளரால் வரையறுக்கப்பட்ட சேர்க்கை பட்டியலுடன் இணைக்கப்படும். செயல்முறை ஒரு சேர்க்கை பதிவுடன் பொருந்தினால் மற்றும் எந்த விலக்கு பதிவுடனும் பொருந்தவில்லை என்றால் மாற்றம் ஏற்றப்படும்.", "titleExclusion": "தனிப்பயன் செயல்முறை விலக்கு பட்டியல்", "descriptionExclusion": "மாற்றத்திலிருந்து விலக்கப்பட வேண்டிய கூடுதல் செயலியாக்கக்கூடிய கோப்புகளின் பெயர்கள்/பாதைகள் கொண்ட தனிப்பயன் பட்டியல். இது எழுத்தாளரால் வரையறுக்கப்பட்ட விலக்கு பட்டியலுடன் இணைக்கப்படும். செயல்முறை ஒரு சேர்க்கை பதிவுடன் பொருந்தினால் மற்றும் எந்த விலக்கு பதிவுடனும் பொருந்தவில்லை என்றால் மாற்றம் ஏற்றப்படும்.", "processListPlaceholder": "ஒவ்வொன்றும் ஒரு வரியில் செயல்முறை பெயர்கள்/பாதைகள், எடுத்துக்காட்டாக:", "invalidCharactersWarning": "செயல்முறை பட்டியலில் தவறான எழுத்துகள் உள்ளன: {{invalidCharacters}}", "saveButton": "சேமிக்கவும்" }, "includeExcludeCustomOnly": { "title": "மாற்றத்தின் சேர்க்கை/விலக்கு பட்டியல்களை புறக்கணிக்கவும்", "description": "மேலே உள்ள தனிப்பயன் பட்டியல்களை மட்டும் பயன்படுத்தி, மாற்றத்தின் பட்டியல்களை புறக்கணிக்கவும்." }, "patternsMatchCriticalSystemProcesses": { "title": "முக்கிய சிஸ்டம் செயல்முறைகளுக்கான சேர்க்கை மாதிரிகளை பரிசீலனை செய்யவும்", "description": "விரும்பத்தகுந்த முறையில், விண்ட்ஹாக் முக்கிய செயல்முறைகளில் மாதிரிகள் இல்லாமல் சேர்க்கப்பட்டால் மட்டுமே மாற்றங்களை ஏற்றுகிறது (உதா: <0>critical.exe, ஆனால் <0>* அல்லது <0>*.exe அல்ல). மேலும் விபரங்களுக்கு <1>ஆவணங்களை காணவும்." } }, "changes": { "title": "புதிய பதிப்பில் ஏற்பட்ட மாற்றங்கள்", "noData": "நிறுவப்பட்ட பதிப்பு மற்றும் புதிய பதிப்பு ஒன்றே.", "splitView": "பிளவுபார்வை", "expandLines_one": "ஒரு மறைக்கப்பட்ட வரியை விரிவாக்கவும்", "expandLines_other": "{{count}} மறைக்கப்பட்ட வரிகளை விரிவாக்கவும்" } }, "modSearch": { "placeholder": "மாற்றங்களை தேடவும்..." }, "home": { "browse": "மாற்றங்களைத் தேடவும்", "installedMods": { "title": "நிறுவப்பட்ட மாற்றங்கள்", "noMods": "எந்த மாற்றங்களும் நிறுவப்படவில்லை" }, "featuredMods": { "title": "சிறப்பு மாற்றங்கள்", "noMods": "நிறுவப்படாத சிறப்பு மாற்றங்கள் எதுவும் இல்லை", "explore": "மற்ற மாற்றங்களை ஆராயவும்" } }, "explore": { "search": { "popularAndTopRated": "பிரபலமான மற்றும் சிறந்த மதிப்பீட்டுடன்", "popular": "பிரபலமானவை", "topRated": "சிறந்த மதிப்பீட்டுடன்", "newest": "புதியவை", "lastUpdated": "கடைசியாக புதுப்பிக்கப்பட்டவை", "alphabeticalOrder": "அகர வரிசை" } }, "settings": { "language": { "title": "மொழி", "description": "விண்ட்ஹாக் க்கான விருப்பமான காட்சிமொழியைத் தேர்ந்தெடுக்கவும்.", "contribute": "<0>புதிய மொழிபெயர்ப்பை பங்களிக்கவும்.", "credits": "தமிழ் மொழிபெயர்ப்பு செய்தவர்: <0>JeevabharathiRK.", "creditsLink": "https://github.com/JeevabharathiRK" }, "updates": { "title": "புதுப்பிப்புகளை சரிபார்க்கவும்", "description": "விண்ட்ஹாக் மற்றும் நிறுவப்பட்ட மாற்றங்களின் புதிய பதிப்புகளைக் காண மையமாகத்தோறும் சரிபார்க்கவும்." }, "devMode": { "title": "டெவலப்பர் பயன்முறை", "description": "மாற்றங்களை உருவாக்குவதற்கும் திருத்துவதற்குமான செயற்பாடுகளை காண்பிக்கவும்." }, "advancedSettings": "மேம்பட்ட அமைப்புகள்", "hideTrayIcon": { "title": "டிரே ஐகானை மறை", "description": "விண்ட்ஹாக் இருந்து வெளியேற இந்த விருப்பத்தை முடக்க வேண்டும்." }, "requireElevation": { "title": "விண்ட்ஹாக் இயக்க UAC உயர்வை தேவைப்படுத்து", "description": "விண்ட்ஹாக் நிர்வாக உரிமைகளை தேவைப்படுத்துகிறது, ஆனால் ஒற்றை பயனர் கணினிக்கு, ஒவ்வொரு முறையும் UAC எச்சரிக்கை வருவது தொந்தரவு தரலாம், எனவே விண்ட்ஹாக் அதை தவிர்க்கிறது. இந்த விருப்பத்தை இயக்கினால், இயக்கும் போது உயர்வை தேவையாக்கும்." }, "dontAutoShowToolkit": { "title": "கருவித்தொகுப்பு உரையாடலை தானாக காட்ட வேண்டாம்", "description": "முன்னிருப்பாக, விண்ட்ஹாக் டாஸ்க்பார் அணுக முடியாத நிலையில் இருந்தால் கருவித்தொகுப்பு உரையாடலை தானாக காட்டும். இது விண்ட்ஹாக் ஐ முடிக்க, பாதுகாப்பு பயன்முறைக்கு மாற மற்றும் பிற செயல்களைச் செய்ய உதவுகிறது. Ctrl+Win+W குறுக்குவழி மூலம் இதை கைமுறையாகவும் திறக்கலாம்." }, "modInitDialogDelay": { "title": "மாற்றம் துவக்க உரையாடல் தாமதம்", "description": "மாற்றம் துவக்கத்தின் போது முன்னேற்ற உரையாடலைக் காட்ட மில்லிசெக்கன்களில் காத்திருக்க வேண்டிய நேரம்." }, "moreAdvancedSettings": { "title": "மேலும் மேம்பட்ட அமைப்புகள்", "restartNotice": "அமைப்புகளை செயல்படுத்த விண்ட்ஹாக் மீண்டும் துவக்கப்படும்.", "saveButton": "சேமித்து விண்ட்ஹாக் ஐ மீண்டும் துவக்கு", "cancelButton": "ரத்து செய்" }, "loggingVerbosity": { "appLoggingTitle": "விண்ட்ஹாக் பதிவு விவரக்குறைவு", "engineLoggingTitle": "விண்ட்ஹாக் என்ஜின் பதிவு விவரக்குறைவு", "description": "பதிவுகளை DebugView போன்ற கருவியின் மூலம் காணலாம்.", "none": "இல்லை", "error": "பிழை", "verbose": "விவரமாக" }, "processList": { "titleExclusion": "செயல்முறை விலக்கு பட்டியல்", "descriptionExclusion": "விண்ட்ஹாக் செருக வேண்டாமென உள்ள செயல்முறை பெயர்கள்/பாதைகளின் பட்டியல். குறிப்பிட்ட நிரல்களுடன் விண்ட்ஹாக் பொருந்தாத நிலையில் இது உதவிகரமாக இருக்கும். இதில் சேர்க்கப்பட்ட செயல்முறைகள் அதன் பிள்ளை செயல்களை கூட அவதானிக்க முடியாது.", "descriptionExclusionWiki": "செயல்முறைகளை விலக்குவது மற்றும் உள்ளமைந்த பட்டியல்கள் பற்றி மேலும் தெரிந்து கொள்ள <0>ஆவணங்களை பார்க்கவும்.", "excludeCriticalProcesses": "முக்கிய செயல்முறைகளை விலக்கு", "excludeIncompatiblePrograms": "தொல்லை தரும் நிரல்களை விலக்கு", "excludeGames": "நன்கு அறியப்பட்ட விளையாட்டுகளை விலக்கு", "titleInclusion": "செயல்முறை சேர்க்கை பட்டியல்", "descriptionInclusion": "விலக்கப்பட்ட பட்டியலில் இருந்தாலும் விண்ட்ஹாக் செருக வேண்டிய செயல்முறை பெயர்கள்/பாதைகள்.", "inclusionWithoutExclusionNotice": "விலக்கப்பட்ட பட்டியல் இல்லாமல் சேர்க்கை பட்டியல் வேலை செய்யாது.", "inclusionWithoutTotalExclusionNotice": "மற்ற அனைத்தையும் விலக்க விரும்பினால், விலக்கு பட்டியலில் \"*\" ஐ சேர்க்கவும்.", "processListPlaceholder": "ஒவ்வொன்றும் ஒரு வரியில் செயல்முறை பெயர்கள்/பாதைகள், எடுத்துக்காட்டாக:", "invalidCharactersWarning": "செயல்முறை பட்டியலில் தவறான எழுத்துகள் உள்ளன: {{invalidCharacters}}" } }, "about": { "title": "விண்ட்ஹாக் v{{version}}", "beta": "பீட்டா", "subtitle": "Windows மற்றும் நிரல்களுக்கான தனிப்பயனாக்க சந்தை", "credit": "இயற்றியவர் <0>{{author}}", "update": { "title": "புதுப்பிப்பு கிடைக்கிறது", "subtitle": "புதிய அம்சங்கள் மற்றும் பிழை திருத்தங்களுக்கு விண்ட்ஹாக் ஐ புதுப்பிக்க பரிந்துரை செய்யப்படுகிறது", "updateButton": "மேலும் விபரங்கள்" }, "links": { "title": "இணைப்புகள்", "homepage": "முகப்பு பக்கம்", "documentation": "ஆவணங்கள்" }, "builtWith": { "title": "பயன்படுத்திய கருவிகள்", "vscodium": "மைக்ரோசாப்டின் VSCode ஐ அடிப்படையாக கொண்ட சமூக இயக்கும் தொகுப்பி", "llvmMingw": "LLVM/Clang/LLD அடிப்படையிலான mingw-w64 கருவி தொகுப்பு", "minHook": "Windows க்கான குறைந்தபட்ச API ஹூக்கிங் நூலகம்", "others": "பிற கருவிகள் மற்றும் நூலகங்கள், மேலும் கொஞ்சம் குறியீடு" } }, "installModal": { "title": "{{mod}} ஐ நிறுவுக", "warningTitle": "கவனத்துடன் தொடரவும்", "warningDescription": "தீங்கான மாற்றங்கள் உங்கள் கணினிக்கு சேதம் செய்யலாம் அல்லது உங்கள் தனிப்பட்ட தகவல்களை மீறலாம். நம்பிக்கையுடன் உள்ள எழுத்தாளர்களிடமிருந்து மட்டுமே மாற்றங்களை நிறுவுங்கள்.", "modAuthor": "மாற்ற எழுத்தாளர்", "homepage": "முகப்பு பக்கம்", "github": "GitHub", "twitter": "X (ட்விட்டர்)", "verified": "சரிபார்க்கப்பட்டது", "verifiedTooltip": "இந்த சுயவிவரம் மாற்ற எழுத்தாளருக்கே சேர்ந்ததாக நாம் உறுதி செய்துள்ளோம். ஆனால் <0>சரிபார்க்கப்பட்டது என்பது <0>நம்பத்தகுந்தது என்பதுடன் சமமாக இல்லை. நீங்கள் எழுத்தாளரை நம்புகிறீர்களா என்பதை உறுதி செய்யவும், இல்லையெனில் மூலக் குறியீட்டை நன்றாக ஆய்வு செய்யவும்.", "acceptButton": "அபாயத்தை ஏற்று நிறுவவும்", "cancelButton": "ரத்து செய்" }, "createNewModButton": { "title": "புதிய மாற்றம் உருவாக்கவும்" }, "devModeAction": { "message": "மாற்றங்களை உருவாக்க மற்றும் திருத்த C/C++ பற்றிய அறிவு தேவைப்படும். இதைப்பற்றி தெளிவில்லை என்றால், இவ்விருப்பங்கள் உங்களுக்கு பொருத்தமில்லாமல் இருக்கலாம்.", "hideOptionsCheckbox": "மேம்பாட்டு தொடர்பான விருப்பங்களை மறைக்கவும்", "hideOptionsButton": "விருப்பங்களை மறைக்கவும்", "beginCodingButton": "குறியீட்டாக்கத் தொடங்கவும்", "cancelButton": "ரத்து செய்" }, "safeMode": { "alert": "விண்ட்ஹாக் பாதுகாப்பு பயன்முறையில் இயங்குகிறது, அனைத்து குறியீடு செருகல் செயல்பாடுகளும் முடக்கப்பட்டுள்ளன.", "offButton": "பாதுகாப்பு பயன்முறையை அணைக்கவும்", "offConfirm": "அமைப்புகளைச் செயல்படுத்த விண்ட்ஹாக் மீண்டும் துவங்கும்.", "offConfirmOk": "விண்ட்ஹாக் ஐ மீண்டும் துவக்கு", "offConfirmCancel": "ரத்து செய்" }, "modPreview": { "actionUnavailable": "இந்த செயல் முன்னோட்ட பயன்முறையில் கிடைக்கவில்லை", "notCompiled": "மாற்றம் முன்னோட்டம் காணும் முன் கணக்கிடப்பட வேண்டும்" }, "sidebar": { "modId": "மாற்ற அடையாளம்", "enableMod": "மாற்றத்தை இயக்கவும்", "enableLogging": "பதிவை இயக்கவும்", "notCompiled": "மாற்றம் கணக்கிடப்படவில்லை", "compile": "மாற்றத்தை கணக்கிடு", "compilationFailed": "கணக்கீடு தோல்வியடைந்தது", "preview": "மாற்றம் முன்னோட்டம்", "showLogOutput": "பதிவு வெளியீட்டை காட்டு", "exit": "திருத்தும் முறையிலிருந்து வெளியேறு", "exitConfirmation": "கடைசி வெற்றிகரமான கணக்கீட்டுக்கு பிறகு செய்யப்பட்ட மாற்றங்கள் இழக்கப்படும்", "exitButtonOk": "வெளியேறு", "exitButtonCancel": "தங்கு" } } ================================================ FILE: src/vscode-windhawk-ui/apps/vscode-windhawk-ui/src/locales/th/DO_NOT_EDIT.txt ================================================ The files in this folder are generated automatically. To submit a translation update, please refer to this repository: https://github.com/ramensoftware/windhawk-translate ================================================ FILE: src/vscode-windhawk-ui/apps/vscode-windhawk-ui/src/locales/th/translation.json ================================================ { "general": { "loading": "กำลังโหลด...", "loadingFailed": "ไม่สามารถโหลดได้ โปรดตรวจสอบการเชื่อมต่ออินเทอร์เน็ตของคุณ", "loadingFailedTitle": "ไม่สามารถโหลดได้", "loadingFailedSubtitle": "โปรดตรวจสอบการเชื่อมต่ออินเทอร์เน็ตของคุณและลองอีกครั้ง", "tryAgain": "ลองอีกครั้ง", "updating": "กำลังอัปเดต...", "installing": "กำลังติดตั้ง...", "compiling": "กำลังคอมไพล์...", "cut": "ตัด", "copy": "คัดลอก", "paste": "วาง", "selectAll": "เลือกทั้งหมด" }, "appHeader": { "home": "หน้าหลัก", "explore": "สำรวจ", "settings": "การตั้งค่า", "about": "เกี่ยวกับ" }, "mod": { "updateAvailable": "อัปเดตพร้อมให้ใช้งาน", "installed": "ติดตั้งแล้ว", "details": "รายละเอียด", "update": "อัปเดต", "install": "ติดตั้ง", "compile": "คอมไพล์", "disable": "ปิด", "enable": "เปิด", "edit": "แก้ไข", "fork": "แยกไปสร้างใหม่", "remove": "ลบ", "removeConfirm": "คุณแน่ใจหรือไม่ว่าต้องการลบม็อดนี้", "removeConfirmOk": "ลบม็อด", "removeConfirmCancel": "ยกเลิก", "notCompiled": "ม็อดจำเป็นต้องได้รับการคอมไพล์", "editedLocally": "ม็อดถูกแก้ไขในเครื่อง", "noDescription": "(ไม่มีคำอธิบาย)", "users_one": "ผู้ใช้ {{formattedCount}} คน", "users_other": "ผู้ใช้ {{formattedCount}} คน" }, "modDetails": { "header": { "installedVersion": "เวอร์ชั่นที่ติดตั้งไว้", "latestVersion": "เวอร์ชั่นล่าสุด", "loading": "กำลังโหลด...", "loadingFailed": "โหลดไม่สำเร็จ", "modId": "ตัวระบุม็อด", "modVersion": "เวอร์ชั่นของม็อด", "modAuthor": { "title": "ผู้สร้างม็อด", "homepage": "หน้าหลัก", "github": "GitHub", "twitter": "X (Twitter)" }, "processes": { "all": "กระบวนการทั้งหมด", "allBut": "กระบวนการทั้งหมด ยกเว้น {{list}}", "except": "{{included}} ยกเว้น {{excluded}}", "tooltip": { "targets": "กระบวนการเป้าหมาย", "excluded": "กระบวนการที่ยกเว้น" } }, "updateNotNeeded": "เวอร์ชั่นที่ติดตั้งไว้เหมือนกับเวอร์ชั่นล่าสุด" }, "details": { "title": "รายละเอียด", "noData": "ไม่มีรายละเอียดของม็อดนี้" }, "settings": { "title": "การตั้งค่า", "noData": "ไม่มีการตั้งค่าใด ๆ ของม็อดนี้", "saveButton": "บันทึกการตั้งค่า", "sampleValue": "ค่าตัวอย่าง", "arrayItemAdd": "เพิ่มไอเท็มใหม่", "arrayItemRemove": "ลบไอเท็ม" }, "code": { "title": "โค้ดต้นฉบับ", "noData": "ไม่มีโค้ดต้นฉบับของม็อดนี้", "collapseExtra": "ยุบ Readme และการตั้งค่าต่าง ๆ" }, "changelog": { "title": "บันทึกการเปลี่ยนแปลง", "loadingFailed": "ไม่สามารถโหลดบันทึกการเปลี่ยนแปลงได้ <0>กดที่นี่เพื่อดูบันทึกการเปลี่ยนแปลงบน GitHub" }, "advanced": { "title": "ขั้นสูง", "debugLogging": { "title": "การบันทึกข้อมูลดีบั๊ก", "description": "มีประโยชน์สำหรับสำหรับการแก้ไขปัญหาเกี่ยวกับม็อด", "none": "ไม่มี", "modLogs": "ข้อมูลของม็อดที่บันทึกเอาไว้", "detailedLogs": "รายละเอียดข้อมูลของม็อดที่บันทึกเอาไว้", "showLogButton": "แสดงผลการบันทึก" }, "modSettings": { "title": "การตั้งค่าของม็อด", "description": "ส่งออกหรือแชร์การตั้งค่าของม็อดได้อย่างง่ายดาย", "loadButton": "โหลด", "loadFormattedButton": "โหลดพร้อมรูปแบบ", "saveButton": "บันทึก", "invalidData": "ข้อมูล JSON ไม่ถูกต้อง" }, "customList": { "titleInclusion": "รายการกระบวนการที่ถูกรวมที่กำหนดเอง", "descriptionInclusion": "รายการชื่อหรือเส้นทางของกระบวนการเพิ่มเติมที่ม็อดจะตั้งเป้าหมาย รายการนี้จะถูกรวมลงไปในรายการกระบวนการที่ถูกรวมที่ผู้สร้างม็อดกำหนดเอาไว้แล้ว สำหรับแต่ละกระบวนการ ม็อดจะถูกโหลดถ้าเส้นทางของกระบวนการตรงกับหนึ่งในกระบวนการที่รวมไว้และไม่ตรงกับกระบวนการที่ยกเว้นไว้", "titleExclusion": "รายการกระบวนการที่ถูกยกเว้นที่กำหนดเอง", "descriptionExclusion": "รายการชื่อหรือเส้นทางของกระบวนการเพิ่มเติมที่ม็อดจะถูกยกเว้นไม่ให้เป้าหมาย รายการนี้จะถูกรวมลงไปในรายการกระบวนการที่ถูกยกเว้นที่ผู้สร้างม็อดกำหนดเอาไว้แล้ว สำหรับแต่ละกระบวนการ ม็อดจะถูกโหลดถ้าเส้นทางของกระบวนการตรงกับหนึ่งในกระบวนการที่รวมไว้และไม่ตรงกับกระบวนการที่ยกเว้นไว้", "processListPlaceholder": "ชื่อหรือเส้นทางของกระบวนการ หนึ่งกระบวนการต่อหนึ่งบรรทัด ตัวอย่าง:", "invalidCharactersWarning": "รายการกระบวนการมีตัวอักษรที่ไม่ถูกต้อง ซึ่งจะถูกตัดออก: {{invalidCharacters}}", "saveButton": "บันทึก" }, "includeExcludeCustomOnly": { "title": "ไม่สนใจรายการกระบวนการที่ถูกรวม/ยกเว้นที่กำหนดโดยม็อด", "description": "ไม่สนใจรายการกระบวนการที่ถูกรวม/ยกเว้นที่กำหนดโดยม็อดและใช้แต่รายการที่กำหนดเองตามข้างบน" }, "patternsMatchCriticalSystemProcesses": { "title": "พิจารณารูปแบบรายการกระบวนการที่ถูกรวมสำหรับกระบวนการระบบที่สำคัญ", "description": "โดยปกติ Windhawk จะโหลดม็อดในกระบวนการระบบที่สำคัญถ้ากระบวนการไม่รวมอยู่ในรูปแบบต่าง ๆ เท่านั้น เช่น <0>critical.exe ไม่ใช่ <0>* หรือ <0>*.exe สำหรับรายละเอียดเกี่ยวกับกระบวนการระบบที่สำคัญ โปรดอ่าน<0>เอกสาร" } }, "changes": { "title": "การเปลี่ยนแปลงเวอร์ชั่นล่าสุด", "noData": "เวอร์ชั่นที่ติดตั้งไว้เหมือนกับเวอร์ชั่นล่าสุด", "splitView": "มุมมองแยก", "expandLines_one": "ขยายหนึ่งบรรทัดที่ซ่อนอยู่", "expandLines_other": "ขยาย {{count}} บรรทัดที่ซ่อนอยู่" } }, "modSearch": { "placeholder": "ค้นหาม็อด..." }, "home": { "browse": "สำรวจม็อด", "installedMods": { "title": "ม็อดที่ติดตั้งไว้", "noMods": "ไม่มีม็อดที่ติดตั้งไว้" }, "featuredMods": { "title": "ม็อดที่แนะนำ", "noMods": "คุณไม่มีม็อดที่แนะนำตัวไหนที่ถูกติดตั้ง", "explore": "สำรวจม็อดอื่นๆ" } }, "explore": { "search": { "popularAndTopRated": "ยอดนิยมและอันดับสูงสุด", "popular": "ยอดนิยม", "topRated": "อันดับสูงสุด", "newest": "ใหม่ที่สุด", "lastUpdated": "อัปเดตครั้งล่าสุด", "alphabeticalOrder": "ลำดับตัวอักษร" } }, "settings": { "language": { "title": "ภาษา", "description": "เลือกภาษาที่คุณต้องการแสดงสำหรับ Windhawk", "contribute": "<0>ร่วมแปลภาษาใหม่.", "credits": "Thai translation by <0>achia70.", "creditsLink": "https://github.com/achia70" }, "updates": { "title": "ตรวจสอบการอัปเดต", "description": "ตรวจสอบเวอร์ชั่นที่ใหม่กว่าของ Windhawk และม็อดที่ติดตั้งไว้เป็นระยะ ๆ" }, "devMode": { "title": "โหมดผู้พัฒนา", "description": "แสดงตัวเลือกสำหรับผู้พัฒนา เช่นการสร้างและการแก้ไขม็อด" }, "advancedSettings": "การตั้งค่าขั้นสูง", "hideTrayIcon": { "title": "ซ่อนไอคอนถาด", "description": "ปิดใช้งานตัวเลือกนี้เพื่อออกจาก Windhawk" }, "requireElevation": { "title": "ต้องมีการควบคุมบัญชีผู้ใช้ (UAC) สำหรับการเรียกใช้ Windhawk", "description": "Windhawk ต้องมีสิทธิ์ผู้ดูแลระบบ แต่สำหรับคอมพิวเตอร์ที่มีผู้ใช้คนเดียว Windhawk จะหลีกเลี่ยงไม่ให้แสดงการแจ้งเตือนการควบคุมบัญชีผู้ใช้เพื่อลดความรำคาญ เปิดใช้งานตัวเลือกนี้หากคุณต้องการให้มีการควบคุมบัญชีผู้ใช้ (UAC) สำหรับการเรียกใช้ Windhawk" }, "dontAutoShowToolkit": { "title": "ไม่ต้องแสดงกล่องโต้ตอบชุดเครื่องมือโดยอัตโนมัติ", "description": "โดยปกติ Windhawk แสดงกล่องโต้ตอบชุดเครื่องมือโดยอัตโนมัติเมื่อมีการตรวจพบว่าไม่สามารถเข้าถึงแถบงานได้ อาจเกิดจากความไม่เสถียรของระบบหรือเหตุผลอื่นๆ กล่องโต้ตอบชุดเครื่องมือนี้อนุญาตให้ออกจาก Windhawk เปลี่ยนไปใช้โหมดความปลอดภัย และดำเนินการสิ่งอื่น ๆ ได้ ยังสามารถแสดงกล่องโต้ตอบชุดเครื่องมือได้โดยใช้แป้นพิมพ์ลัด Ctrl+Win+W" }, "modInitDialogDelay": { "title": "การหน่วงเวลาของกล่องโต้ตอบการเริ่มต้นม็อด", "description": "จำนวนมิลลิวินาทีในการรอก่อนที่จะโชว์กล่องโต้ตอบความคืบหน้าสำหรับการเริ่มต้นม็อด" }, "moreAdvancedSettings": { "title": "การตั้งค่าขั้นสูงยิ่งขึ้น", "restartNotice": "Windhawk จะถูกรีสตาร์ทเพื่อนำการตั้งค่าไปใช้.", "saveButton": "บันทึกการตั้งค่า และรีสตาร์ท Windhawk", "cancelButton": "ยกเลิก" }, "loggingVerbosity": { "appLoggingTitle": "ความละเอียดของการบันทึกข้อมูลของ Windhawk", "engineLoggingTitle": "ความละเอียดของการบันทึกข้อมูลของเอนจิน Windhawk", "description": "ข้อมูลที่บันทึกสามารถดูได้ด้วยเครื่องมือต่าง ๆ ได้แก่ DebugView", "none": "ไม่มี", "error": "เฉพาะข้อผิดพลาด (error)", "verbose": "ละเอียด (verbose)" }, "processList": { "titleExclusion": "รายการกระบวนการที่ถูกยกเว้น", "descriptionExclusion": "รายการชื่อหรือเส้นทางของกระบวนการที่จะไม่ถูกเจาะโดย Windhawk มีประโยชน์ในกรณีที่ Windhawk เข้ากันไม่ได้กับโปรแกรมเฉพาะ โปรดทราบว่าการเพิ่มกระบวนการลงไปในรายการนี้ไม่เพียงแต่จะป้องกันไม่ให้ Windhawk ปรับแต่งกระบวนการนั้น ๆ แต่ยังจะป้องกันไม่ให้ Windhawk เข้าไปสกัดกระบวนการลูกที่กระบวนการนั้น ๆ ได้สร้างเอาไว้ ในกรณีนี้อาจจะทำให้ Windhawk โหลดม็อดด้วยความล่าช้าเล็กน้อย", "descriptionExclusionWiki": "สำหรับรายละเอียดเกี่ยวกับการยกเว้นกระบวนการและรายละเอียดเกี่ยวกับรายการกระบวนการที่ถูกยกเว้นในตัว โปรดอ่าน<0>เอกสาร", "excludeCriticalProcesses": "ยกเว้นกระบวนการระบบที่สำคัญ", "excludeIncompatiblePrograms": "ยกเว้นโปรแกรมที่ถูกรายงานว่าเข้ากันไม่ได้", "excludeGames": "ยกเว้นเกมที่ถูกรายงาน", "titleInclusion": "รายการกระบวนการที่ถูกรวม", "descriptionInclusion": "รายการชื่อหรือเส้นทางของกระบวนการที่จะถูกเจาะโดย Windhawk ถึงแม้กระบวนการนั้นจะอยู่ในรายการกระบวนการที่ถูกยกเว้น", "inclusionWithoutExclusionNotice": "รายการนี้ไม่มีผลต่อรายการกระบวนการที่ถูกยกเว้นที่ว่างเปล่า", "inclusionWithoutTotalExclusionNotice": "หากคุณต้องการยกเว้นกระบวนการทั้งหมดยกเว้นกระบวนการพวกนี้ คุณสามารถตั้งค่ารายการกระบวนกรที่ถูกยกเว้นให้เป็น \"*\"", "processListPlaceholder": "ชื่อหรือเส้นทางของกระบวนการ หนึ่งกระบวนการต่อหนึ่งบรรทัด ตัวอย่าง:", "invalidCharactersWarning": "รายการกระบวนการมีตัวอักษรที่ไม่ถูกต้อง ซึ่งจะถูกตัดออก: {{invalidCharacters}}" } }, "about": { "title": "Windhawk v{{version}}", "beta": "เบต้า", "subtitle": "โปรแกรมรวบรวมการปรับแต่งสำหรับ Windows และโปรแกรมต่าง ๆ", "credit": "โดย <0>{{author}}", "update": { "title": "มีการอัปเดตที่พร้อมใช้งาน", "subtitle": "อัปเดต Windhawk เพื่อรับคุณสมบัติล่าสุดและการแก้ไขข้อบกพร่อง", "updateButton": "รายละเอียดเพิ่มเติม" }, "links": { "title": "ลิงก์", "homepage": "หน้าหลัก", "documentation": "เอกสาร" }, "builtWith": { "title": "สร้างด้วย", "vscodium": "การกระจาย VSCode Editor ของ Microsoft ที่ขับเคลื่อนโดยชุมชน", "llvmMingw": "ชุดเครื่องมือ mingw-w64 ที่ใช้ LLVM/Clang/LLD", "minHook": "ไลบรารีการเชื่อมต่อ API ที่เรียบง่ายสำหรับ Windows", "others": "เครื่องมือและไลบรารี่อื่นๆ กับโค้ดอีกเล็กน้อย" } }, "installModal": { "title": "ติดตั้ง {{mod}}", "warningTitle": "ดำเนินการอย่างมีสติ", "warningDescription": "ม็อดที่เป็นอันตรายสามารถสร้างความเสียหายให้กับคอมพิวเตอร์ของคุณหรือละเมิดความเป็นส่วนตัวของคุณได้ ควรติดตั้งม็อดจากผู้สร้างที่คุณเชื่อถือเท่านั้น", "modAuthor": "ผู้สร้างม็อด", "homepage": "หน้าหลัก", "github": "GitHub", "twitter": "X (Twitter)", "verified": "ยืนยันแล้ว", "verifiedTooltip": "เรายืนยันแล้วว่าโปรไฟล์นี้เป็นของผู้สร้างม็อด แต่โปรดทราบว่าถึงแม้จะ<0>มีการยืนยันแล้วไม่ได้หมายความว่าจะมีความน่าเชื่อถือเสมอไป อย่าลืมตรวจสอบด้วยว่าผู้สร้างมีความน่าเชื่อถือหรือไม่ หรือตรวจสอบโค้ดต้นฉบับให้ดีก่อนติดตั้ง", "acceptButton": "ยอมรับความเสี่ยงและติดตั้ง", "cancelButton": "ยกเลิก" }, "createNewModButton": { "title": "สร้างม็อดใหม่" }, "devModeAction": { "message": "การสร้างหรือแก้ไขม็อดนั้นต้องมีความรู้เกี่ยวกับการพัฒนา C/C++ สำหรับ Windows อยู่บ้าง หากคุณไม่เข้าใจว่าตามนี้หมายถึงอะไร บางทีตัวเลือกเหล่านี้อาจไม่เหมาะกับคุณ", "hideOptionsCheckbox": "ซ่อนตัวเลือกที่เกี่ยวกับการพัฒนาทั้งหมด", "hideOptionsButton": "ซ่อนตัวเลือก", "beginCodingButton": "เริ่มการโค้ด", "cancelButton": "ยกเลิก" }, "safeMode": { "alert": "Windhawk กำลังทำงานอยู่ในโหมดปลอดภัย ฟังก์ชั่นการเจาะโค้ดทั้งหมดถูกปิด", "offButton": "ปิดโหมดปลอดภัย", "offConfirm": "Windhawk จะถูกรีสตาร์ทเพื่อนำการตั้งค่าไปใช้", "offConfirmOk": "รีสตาร์ท Windhawk", "offConfirmCancel": "ยกเลิก" }, "modPreview": { "actionUnavailable": "การกระทำไม่พร้อมใช้งานในโหมดตัวอย่าง", "notCompiled": "ม็อดจำเป็นต้องได้รับการคอมไพล์ก่อนจึงจะได้รับการดูตัวอย่างได้" }, "sidebar": { "modId": "ตัวระบุม็อด", "enableMod": "เปิดม็อด", "enableLogging": "เปิดการบันทึก (logging)", "notCompiled": "ม็อดจำเป็นต้องได้รับการคอมไพล์", "compile": "คอมไพล์ม็อด", "compilationFailed": "ไม่สามารถคอมไพล์ได้", "preview": "ดูตัวอย่างม็อด", "showLogOutput": "ดูผลการบันทึก", "exit": "ออกจากโหมดการแก้ไข", "exitConfirmation": "การเปลี่ยนแปลงนับตั้งแต่การคอมไพล์ครั้งล่าสุดที่สำเร็จจะหายไป", "exitButtonOk": "ออก", "exitButtonCancel": "อยู่ต่อ" } } ================================================ FILE: src/vscode-windhawk-ui/apps/vscode-windhawk-ui/src/locales/tr/DO_NOT_EDIT.txt ================================================ The files in this folder are generated automatically. To submit a translation update, please refer to this repository: https://github.com/ramensoftware/windhawk-translate ================================================ FILE: src/vscode-windhawk-ui/apps/vscode-windhawk-ui/src/locales/tr/translation.json ================================================ { "general": { "loading": "Yükleniyor...", "loadingFailed": "Yükleme başarısız, lütfen internet bağlantınızı kontrol edin", "loadingFailedTitle": "Yükleme başarısız", "loadingFailedSubtitle": "Lütfen internet bağlantınızı kontrol edin ve tekrar deneyin", "tryAgain": "Tekrar dene", "updating": "Güncelleniyor...", "installing": "Yükleniyor...", "compiling": "Derleniyor..." }, "appHeader": { "home": "Ana Sayfa", "explore": "Keşfet", "settings": "Ayarlar", "about": "Hakkında" }, "mod": { "updateAvailable": "Güncelleme mevcut", "installed": "Yüklendi", "details": "Ayrıntılar", "update": "Güncelle", "install": "Yükle", "compile": "Derle", "disable": "Devre dışı bırak", "enable": "Etkinleştir", "edit": "Düzenle", "fork": "Çatal", "remove": "Kaldır", "removeConfirm": "Bu modu kaldırmak istediğinizden emin misiniz??", "removeConfirmOk": "Modu kaldır", "removeConfirmCancel": "İptal", "notCompiled": "Modun derlenmesi gerekiyor", "editedLocally": "Mod yerel olarak düzenlendi", "noDescription": "(açıklama yok)", "users_one": "{{formattedCount}} kullanıcı", "users_other": "{{formattedCount}} kullanıcı" }, "modDetails": { "header": { "installedVersion": "Yüklü sürüm", "latestVersion": "En son sürüm", "loading": "Yükleniyor...", "loadingFailed": "yükleme başarısız", "modId": "mod tanımlayıcı", "modVersion": "Mod sürümü", "modAuthor": { "title": "Mod yapımcısı", "homepage": "Ana Sayfa", "github": "GitHub", "twitter": "X (Twitter)" }, "processes": { "all": "Tüm işlemler", "allBut": "Tümü ama {{list}}", "except": "{{included}} hariç {{excluded}}", "tooltip": { "targets": "Hedef işlemler", "excluded": "Hariç" } }, "updateNotNeeded": "Yüklü sürüm, en son sürümle aynı" }, "details": { "title": "Ayrıntılar", "noData": "Mod ayrıntıları eksik." }, "settings": { "title": "Ayarlar", "noData": "Kullanılabilir mod ayarı yok.", "saveButton": "Ayarları kaydet", "sampleValue": "Örnek değer", "arrayItemAdd": "Yeni öğe ekle", "arrayItemRemove": "Öğeyi kaldır" }, "code": { "title": "Kaynak Kodu", "noData": "Mod kaynağı eksik.", "collapseExtra": "Beni Oku ve Ayarları Daralt" }, "changelog": { "title": "Değişiklikler", "loadingFailed": "Değişiklikler yüklenemedi. GitHub'da görüntülemek için <0>burayı tıklayın." }, "advanced": { "title": "Gelişmiş", "debugLogging": { "title": "Hata ayıklama günlüğü", "description": "Modla ilgili sorunları gidermek için yararlı olabilir.", "none": "Hiçbiri", "modLogs": "Mod günlükleri", "detailedLogs": "Ayrıntılı hata ayıklama günlükleri", "showLogButton": "Günlük çıktısını göster" }, "modSettings": { "title": "Mod ayarları", "description": "Mod ayarlarınızı kolayca dışa aktarın veya paylaşın.", "loadButton": "Yükle", "loadFormattedButton": "Biçimlendirilmiş yükle", "saveButton": "Kaydet", "invalidData": "Geçersiz JSON verileri" }, "customList": { "titleInclusion": "Özel işlem dahil etme listesi", "descriptionInclusion": "Modun hedefleyeceği ek yürütülebilir dosya adlarının/yollarının özel bir listesi. Liste, hangi işlemlerin hedefleneceğini belirlemek için mod yazarının tanımladığı dahil etme listesine eklenir. Her işlem için, yürütülebilir dosya yolu dahil etme girişlerinden biriyle eşleşirse ve herhangi bir hariç tutma girişiyle eşleşmezse mod yüklenir.", "titleExclusion": "Özel işlem hariç tutma listesi", "descriptionExclusion": "Modun hedefleme dışında bırakacağı ek yürütülebilir dosya adlarının/yollarının özel bir listesi. Liste, hangi işlemlerin hedefleneceğini belirlemek için mod yazarının tanımladığı dışlama listesine eklenir. Her işlem için, yürütülebilir dosya yolu dahil etme girişlerinden biriyle eşleşirse ve herhangi bir hariç tutma girişiyle eşleşmezse mod yüklenir.", "processListPlaceholder": "İşlem adları/yolları, örneğin her satıra bir tane:", "saveButton": "Kaydet" }, "includeExcludeCustomOnly": { "title": "Mod ekleme/hariç tutma listelerini yoksay", "description": "Modun işlem dahil etme/hariç tutma listelerini göz ardı et ve yalnızca yukarıdaki özel listeleri kullan." } }, "changes": { "title": "Son Sürüm Değişiklikleri", "noData": "Yüklü sürüm, en son sürümle aynı.", "splitView": "Bölünmüş görünüm", "expandLines_one": "Bir gizli satırı genişlet", "expandLines_other": "{{count}} gizli satırı genişlet" } }, "modSearch": { "placeholder": "Modları ara..." }, "home": { "browse": "Modlara Göz At", "installedMods": { "title": "Yüklü Modlar", "noMods": "Hiçbir mod yüklü değil" }, "featuredMods": { "title": "Öne Çıkan Modlar", "noMods": "Henüz yüklenmemiş öne çıkan mod yok", "explore": "Diğer Modları Keşfet" } }, "explore": { "search": { "popularAndTopRated": "Popüler ve en beğenilenler", "popular": "Popüler", "topRated": "En beğenilenler", "newest": "Yeniler", "lastUpdated": "Son güncellenenler", "alphabeticalOrder": "Alfabetik sıra" } }, "settings": { "language": { "title": "Dil", "description": "Windhawk için tercih ettiğiniz görüntüleme dilini seçin.", "contribute": "<0>Yeni bir çeviriye katkıda bulunun.", "credits": "Türkçe çeviri: <0>Fatih Fırıncı.", "creditsLink": "mailto:fatihfirinci@gmail.com" }, "updates": { "title": "Güncellemeleri denetle", "description": "Windhawk'ın ve yüklü modların yeni sürümlerini düzenli olarak denetle." }, "devMode": { "title": "Geliştirici modu", "description": "Mod oluşturma ve değiştirme gibi geliştirici eylemlerini göster." }, "advancedSettings": "Gelişmiş ayarlar", "hideTrayIcon": { "title": "Tepsi simgesini gizle", "description": "Windhawk'tan çıkmak için bu seçeneği devre dışı bırakmanız gerekir." }, "requireElevation": { "title": "Windhawk'ı çalıştırmak için UAC yükseltmesi gerektir", "description": "Windhawk, yönetici hakları gerektirir, ancak tek kullanıcılı bir bilgisayar için, her seferinde UAC istemini almak can sıkıcı olabilir, bu nedenle Windhawk bunu atlar. Windhawk'ı çalıştırmak için UAC yükseltmesi gerektirmek üzere bu seçeneği etkinleştirin." }, "dontAutoShowToolkit": { "title": "Araç takımı iletişim kutusunu otomatik olarak gösterme", "description": "Varsayılan olarak, Windhawk görev çubuğunun sistem dengesizliği veya başka bir nedenden dolayı erişilemez olduğunu algıladığında araç takımı iletişim kutusunu otomatik olarak gösterir. Araç takımı iletişim kutusu Windhawk'tan çıkmayı, güvenli moda geçmeyi ve diğer işlemleri yapmayı sağlar. Araç takımı iletişim kutusu Ctrl+Win+W klavye kısayoluyla da gösterilebilir." }, "modInitDialogDelay": { "title": "Mod başlatma ekranı gecikmesi", "description": "Mod başlatma için ilerleme iletişim kutusunu göstermeden önce beklenecek milisaniye miktarı." }, "moreAdvancedSettings": { "title": "Daha fazla gelişmiş ayar", "restartNotice": "Windhawk, ayarları uygulamak için yeniden başlatılacak.", "saveButton": "Kaydet ve Windhawk'ı yeniden başlat", "cancelButton": "İptal" }, "loggingVerbosity": { "appLoggingTitle": "Windhawk günlüğe kaydetme ayrıntısı", "engineLoggingTitle": "Windhawk motor günlüğü ayrıntı düzeyi", "description": "Günlükler, DebugView gibi bir araçla görüntülenebilir.", "none": "Bilinmeyen", "error": "Hata", "verbose": "Ayrıntılı" }, "processList": { "titleExclusion": "İşlem hariç etme listesi", "descriptionExclusion": "Windhawk'ın enjekte etmeyeceği işlem adlarının/yollarının bir listesi. Windhawk'ın belirli bir programla uyumlu olmadığı durumlarda yararlı olabilir. Bu listeye bir süreç eklemenin Windhawk'ın onu özelleştirmesini engellemenin yanı sıra Windhawk'ın bu sürecin oluşturduğu alt süreçleri yakalamasını da engelleyeceğini unutmayın. Bu, Windhawk'ın bu gibi durumlarda modları hafif bir gecikmeyle yüklemesine neden olacaktır.", "titleInclusion": "İşlem dahil etme listesi", "descriptionInclusion": "Dışlama listesinde olsalar bile Windhawk'ın enjekte edeceği işlem adlarının/yollarının bir listesi.", "inclusionWithoutExclusionNotice": "İşlem dahil etme listesinin, boş bir işlem hariç tutma listesi ile hiçbir etkisi yoktur.", "inclusionWithoutTotalExclusionNotice": "Bunlar dışındaki tüm işlemleri hariç tutmak istiyorsanız, işlem dışlama listesinde \"*\" ayarını yapabilirsiniz.", "processListPlaceholder": "İşlem adları/yolları, örneğin her satıra bir tane:" } }, "about": { "title": "Windhawk v{{version}}", "beta": "beta", "subtitle": "Windows programları için özelleştirme pazarı", "credit": "Yapımcı: <0>{{author}}", "update": { "title": "Bir güncelleme mevcut", "subtitle": "En son özellikleri ve hata düzeltmelerini almak için Windhawk'ı güncellemeyi düşünün", "updateButton": "Daha fazla ayrıntı" }, "links": { "title": "Bağlantılar", "homepage": "Ana sayfa", "documentation": "Belgeler" }, "builtWith": { "title": "Oluşturanlar", "vscodium": "Microsoft'un VSCode düzenleyicisinin topluluk odaklı bir dağıtımı", "llvmMingw": "LLVM/Clang/LLD tabanlı mingw-w64 araç zinciri", "minHook": "Windows için minimalist API bağlama kitaplığı", "others": "Diğer araçlar ve kitaplıklar ve biraz kod" } }, "installModal": { "title": "Yükle {{mod}}", "warningTitle": "Dikkatli ilerleyin", "warningDescription": "Kötü amaçlı modlar bilgisayarınıza zarar verebilir veya gizliliğinizi ihlal edebilir. Yalnızca güvendiğiniz yapımcıların modlarını yükleyin.", "modAuthor": "Mod yapımcısı", "homepage": "Ana sayfa", "github": "GitHub", "twitter": "X (Twitter)", "verified": "doğrulanmış", "verifiedTooltip": "Bu profilin modun yapımcısına ait olduğunu doğruladık ancak <0>doğrulanmış olması <0>güvenilir olduğu anlamına gelmiyor. Mod yapımcısına güvendiğinizden emin olun veya yüklemeden önce kaynak kodunu dikkatlice inceleyin.", "acceptButton": "Riski Kabul Et ve Yükle", "cancelButton": "İptal" }, "createNewModButton": { "title": "Yeni Mod Oluştur" }, "devModeAction": { "message": "Mod oluşturmak ve değiştirmek, Windows için biraz C/C++ geliştirme bilgisi gerektirir. Bunun ne anlama geldiğinden emin değilseniz, belki de bu seçenekler size göre değildir.", "hideOptionsCheckbox": "Geliştirmeyle ilgili tüm seçenekleri gizle", "hideOptionsButton": "Seçenekleri gizle", "beginCodingButton": "Kodlamaya başla", "cancelButton": "İptal" }, "modPreview": { "actionUnavailable": "Eylem önizleme modunda kullanılamaz", "notCompiled": "Önizleme yapılmadan önce modun derlenmesi gerekir" }, "sidebar": { "modId": "Mod tanımlayıcı", "enableMod": "Modu etkinleştir", "enableLogging": "Günlüğü etkinleştir", "notCompiled": "Modun derlenmesi gerekiyor", "compile": "Modu Derle", "compilationFailed": "Derleme başarısız", "preview": "Önizleme Modu", "showLogOutput": "Günlük Çıktısını Göster", "exit": "Düzenleme Modundan Çık", "exitConfirmation": "Son başarılı derlemeden bu yana yapılan değişiklikler kaybolacak", "exitButtonOk": "Çık", "exitButtonCancel": "Kal" } } ================================================ FILE: src/vscode-windhawk-ui/apps/vscode-windhawk-ui/src/locales/uk/DO_NOT_EDIT.txt ================================================ The files in this folder are generated automatically. To submit a translation update, please refer to this repository: https://github.com/ramensoftware/windhawk-translate ================================================ FILE: src/vscode-windhawk-ui/apps/vscode-windhawk-ui/src/locales/uk/translation.json ================================================ { "general": { "loading": "Завантаження...", "loadingFailed": "Помилка завантаження, будь ласка, перевірте своє інтернет-з'єднання", "loadingFailedTitle": "Не вдалося завантажити", "loadingFailedSubtitle": "Будь ласка, перевірте своє інтернет-з'єднання та спробуйте ще раз", "tryAgain": "Спробуйте ще раз", "updating": "Оновлення...", "installing": "Встановлення...", "compiling": "Компіляція...", "cut": "Вирізати", "copy": "Копіювати", "paste": "Вставити", "selectAll": "Вибрати все" }, "appHeader": { "home": "Домашня", "explore": "Шукати", "settings": "Налаштування", "about": "Довідка" }, "mod": { "updateAvailable": "Доступне оновлення", "installed": "Встановлено", "details": "Деталі", "update": "Оновити", "install": "Встановити", "compile": "Компілювати", "disable": "Вимкнути", "enable": "Увімкнути", "edit": "Редагувати", "fork": "Форкнути", "remove": "Видалити", "removeConfirm": "Ви дійсно бажаєте видалити цей мод?", "removeConfirmOk": "Видалити мод", "removeConfirmCancel": "Скасувати", "notCompiled": "Мод повинен бути скомпільованим", "editedLocally": "Мод відредаговано локально", "noDescription": "(Опис порожній)", "users_one": "{{formattedCount}} користувач", "users_few": "{{formattedCount}} користувачі", "users_many": "{{formattedCount}} користувачів", "users_other": "{{formattedCount}} користувачів" }, "modDetails": { "header": { "installedVersion": "Встановлена версія", "latestVersion": "Остання версія", "loading": "завантаження...", "loadingFailed": "Помилка завантаження", "modId": "Ідентифікатор моду", "modVersion": "Версія моду", "modAuthor": { "title": "Автори моду", "homepage": "Домашня сторінка", "github": "GitHub", "twitter": "X (Twitter)" }, "processes": { "all": "Всі процеси", "allBut": "Всі крім {{list}}", "except": "{{included}} крім {{excluded}}", "tooltip": { "targets": "Цільові процеси", "excluded": "Винятки" } }, "updateNotNeeded": "Встановлена версія ідентична останній" }, "details": { "title": "Деталі", "noData": "Деталі відсутні." }, "settings": { "title": "Налаштування", "noData": "Налаштування моду відсутні.", "saveButton": "Зберегти зміни", "sampleValue": "Стандартне значення", "arrayItemAdd": "Додати", "arrayItemRemove": "Видалити" }, "code": { "title": "Вихідний код", "noData": "Вихідний код моду відсутній.", "collapseExtra": "Приховати Readme та Налаштування" }, "changelog": { "title": "Журнал змін", "loadingFailed": "Не вдалося завантажити журнал змін. <0>Натисніть тут щоб переглянути його на GitHub." }, "advanced": { "title": "Просунуті", "debugLogging": { "title": "Логування налагодження", "description": "Може бути корисним для усунення проблем з цим модом.", "none": "Ні", "modLogs": "Логи модів", "detailedLogs": "Детальні логи налагодження", "showLogButton": "Показати вивід логу" }, "modSettings": { "title": "Налаштування моду", "description": "Легко експортуй або поділися своїми налаштуваннями моду.", "loadButton": "Завантажити", "loadFormattedButton": "Завантаження відформатовано", "saveButton": "Зберегти", "invalidData": "Пошкодженні JSON дані" }, "customList": { "titleInclusion": "Користувальницький список цільових процесів", "descriptionInclusion": "Користувацький список додаткових імен/шляхів до виконуваних файлів, на які буде спрямовано мод. Цей список додається до списку цілей, який автор моду визначив для визначення процесів, на які вона буде націлена. Мод буде увімкнено для кожного процесу, якщо шлях до виконуваного файлу збігається зі списком цілей і не збігається з жодним зі списків винятків.", "titleExclusion": "Користувальницький список виняткових процесів", "descriptionExclusion": "Користувацький список додаткових імен/шляхів до виконуваних файлів, на які НЕ буде спрямовано мод. Цей список додається до списку винятків, який автор моду визначив для визначення процесів, на які вона буде націлена. Мод буде увімкнено для кожного процесу, якщо шлях до виконуваного файлу збігається зі списком цілей і не збігається з жодним зі списків винятків.", "processListPlaceholder": "Назви/шляхи процесів, по одному в рядку, наприклад:", "saveButton": "Зберегти" }, "includeExcludeCustomOnly": { "title": "Ігнорувати списки включення/виключення модів", "description": "Ігнорувати списки включення/виключення процесів мода і використовувати лише користувацькі списки, наведені вище." } }, "changes": { "title": "Зміни в останній версії:", "noData": "Встановлена версія ідентична останній", "splitView": "Розділити вид", "expandLines_one": "Розгорнути один прихований рядок", "expandLines_few": "Розгорнути {{count}} приховані рядки", "expandLines_many": "Розгорнути {{count}} прихованих рядків", "expandLines_other": "Розгорнути {{count}} прихованих рядків" } }, "modSearch": { "placeholder": "Шукати моди..." }, "home": { "browse": "Шукати моди:", "installedMods": { "title": "Встановлені моди:", "noMods": "Не встановлено жодного моду" }, "featuredMods": { "title": "Рекомендовані моди", "noMods": "Всі рекомендовані моди вже встановлені", "explore": "Шукати інші моди" } }, "explore": { "search": { "popularAndTopRated": "Популярні та з найкращим рейтингом", "popular": "Популярні", "topRated": "З найкращим рейтингом", "newest": "Найновіші", "lastUpdated": "Останні оновленні", "alphabeticalOrder": "Алфавітний порядок" } }, "settings": { "language": { "title": "Мова", "description": "Оберіть бажану мову інтерфейсу Windhawk.", "contribute": "<0>Надіслати свій переклад.", "credits": "Український переклад від <0>Fileak.", "creditsLink": "https://t.me/Fileak" }, "updates": { "title": "Перевірити наявність оновлення", "description": "Періодично перевіряти наявність оновлень для Windhawk та встановлених модів." }, "devMode": { "title": "Режим розробника", "description": "Відображати дії для розробників, такі як створення та редагування модів." }, "advancedSettings": "Просунуті налаштування", "hideTrayIcon": { "title": "Сховати іконку у системному треї", "description": "Вам доведеться вимкнути цю опцію, щоб вийти з Windhawk." }, "requireElevation": { "title": "Запрошувати UAC дозвіл для запуску Windhawk", "description": "Windhawk потребує права адміністратора, але для комп'ютерів з одним користувачем постійні запити від UAC набридають, тому Windhawk обходить їх. Увімкніть цей параметр щоб повернути UAC запити на запуск Windhawk." }, "dontAutoShowToolkit": { "title": "Не викликати діалог меню автоматично", "description": "За замовчуванням, Windhawk автоматично кличе діалог меню коли виявляє, що панель завдань не працює через нестабільність системи чи іншу причину. Діалог меню дозволяє закрити Windhawk, перейти у безпечний режим, та інші дії. Діалог меню також можна викликати комбінацією клавіш Ctrl+Win+W." }, "modInitDialogDelay": { "title": "Затримка діалогу ініціалізації моду", "description": "Кількість мілісекунд очікування перед показом діалогового вікна прогресу ініціалізації моду." }, "moreAdvancedSettings": { "title": "Більше просунутих налаштувань", "restartNotice": "Windhawk перезавантажиться для застосування налаштувань.", "saveButton": "Зберегти зміни та перезавантажити Windhawk", "cancelButton": "Скасувати" }, "loggingVerbosity": { "appLoggingTitle": "Докладне логування Windhawk", "engineLoggingTitle": "Докладне логування движку Windhawk", "description": "Логи можуть бути переглянуть такими утилітами, DebugView.", "none": "Ні", "error": "Помилка", "verbose": "Докладно" }, "processList": { "titleExclusion": "Список виняткових процесів", "descriptionExclusion": "Список додаткових імен/шляхів до виконуваних файлів, які Windhawk буде ігнорувати. Може бути корисно, коли Windhawk не сумісний з окремим застосунком. Зауважте, що додавання процесу до цього списку не тільки забезпечить цей процес не тільки від модифікації його Windhawk'ом, а й всіх його дочірніх процесів. Це призведе до невеликої затримки у ініціалізації модів у деяких випадках.", "titleInclusion": "Список цільових процесів", "descriptionInclusion": "Список додаткових імен/шляхів до виконуваних файлів, у які Windhawk буде вживлятися, навіть якщо вони в списку виключень.", "inclusionWithoutExclusionNotice": "Список цільових процесів нічого не робить, якщо список виняткових процесів порожній.", "inclusionWithoutTotalExclusionNotice": "Якщо ви хочете виключити всі процеси, окрім цих, ви можете задати \"*\" у списку виняткових процесів.", "processListPlaceholder": "Назви/шляхи процесів, по одному в рядку, наприклад:" } }, "about": { "title": "Windhawk v{{version}}", "beta": "beta", "subtitle": "Маркетплейс для кастомізації застосунків Windows", "credit": "Від <0>{{author}}", "update": { "title": "Доступне оновлення", "subtitle": "Поміркуйте про оновлення Windhawk для отримання найновіших функцій та усунення багів", "updateButton": "Більше деталей" }, "links": { "title": "Посилання", "homepage": "Домашня сторінка", "documentation": "Документація" }, "builtWith": { "title": "Побудовано з", "vscodium": "Community версія редактору VSCode від Microsoft", "llvmMingw": "LLVM/Clang/LLD базоване середовище mingw-w64", "minHook": "Minimalistic API hooking library для Windows", "others": "Інші інструменті та бібліотеки, та трішечки кодингу." } }, "installModal": { "title": "Встановити {{mod}}", "warningTitle": "Дійте з обережністю", "warningDescription": "Зловмисні моди можуть mods пошкодити Ваш комп'ютер, або порушити Вашу приватність. Встановлюйте моди тільки від тих авторів, яким ви довіряєте.", "modAuthor": "Автор моду", "homepage": "Домашня сторінка", "github": "GitHub", "twitter": "X (Twitter)", "verified": "верифіковано", "verifiedTooltip": "Ми верифікуємо тільки те, що цей профіль належить автору моду, але зауважте, що <0>верифіковано не те ж саме, що <0>надійно. Переконайтеся, що ви довіряєте автору моду, або уважно перевірте вихідний код перед встановленням.", "acceptButton": "Прийняти ризики та встановити", "cancelButton": "Скасувати" }, "createNewModButton": { "title": "Створити новий мод" }, "devModeAction": { "message": "Створення модів потребує деяких знань з C/C++ розробки для Windows. Якщо ви не впевнені, що це значить, мабуть, ця опції не для Вас.", "hideOptionsCheckbox": "Приховати всі опції для розробників", "hideOptionsButton": "Приховати опції", "beginCodingButton": "Почати кодити", "cancelButton": "Скасувати" }, "safeMode": { "alert": "Windhawk працює в безпечному режимі, без можливості ін'єкцій коду.", "offButton": "Вимкнути безпечний режим", "offConfirm": "Windhawk перезавантажиться для застосування налаштувань.", "offConfirmOk": "Перезавантажити Windhawk", "offConfirmCancel": "Скасувати" }, "modPreview": { "actionUnavailable": "Опція недоступна у режимі перегляду", "notCompiled": "Мод потрібен бути скомпільованим перед переглядом" }, "sidebar": { "modId": "Ідентифікатор моду", "enableMod": "Увімкнути мод", "enableLogging": "Увімкнути логування", "notCompiled": "Мод повинен бути скомпільованим", "compile": "Компілювати мод", "compilationFailed": "Помилка компіляції", "preview": "Переглянуті Мод", "showLogOutput": "Показати вивід логу", "exit": "Вийти з режиму редагування", "exitConfirmation": "Зміни з часу останньої успішної компіляції буде втрачено", "exitButtonOk": "Вийти", "exitButtonCancel": "Залишитися" } } ================================================ FILE: src/vscode-windhawk-ui/apps/vscode-windhawk-ui/src/locales/vi/DO_NOT_EDIT.txt ================================================ The files in this folder are generated automatically. To submit a translation update, please refer to this repository: https://github.com/ramensoftware/windhawk-translate ================================================ FILE: src/vscode-windhawk-ui/apps/vscode-windhawk-ui/src/locales/vi/translation.json ================================================ { "general": { "loading": "Đang tải...", "loadingFailed": "Tải thất bại, vui lòng kiểm tra kết nối internet của bạn", "loadingFailedTitle": "Tải thất bại", "loadingFailedSubtitle": "Vui lòng kiểm tra kết nối internet của bạn và thử lại", "tryAgain": "Thử lại", "updating": "Đang cập nhật...", "installing": "Đang cài đặt...", "compiling": "Đang biên dịch...", "cut": "Cắt", "copy": "Sao chép", "paste": "Dán", "selectAll": "Chọn tất cả" }, "appHeader": { "home": "Trang chủ", "explore": "Khám phá", "settings": "Cài đặt", "about": "Giới thiệu" }, "mod": { "updateAvailable": "Cập nhật có sẵn", "installed": "Đã cài đặt", "details": "Chi tiết", "update": "Cập nhật", "install": "Cài đặt", "compile": "Biên dịch", "disable": "Vô hiệu hóa", "enable": "Kích hoạt", "edit": "Chỉnh sửa", "fork": "Nhánh", "remove": "Gỡ bỏ", "removeConfirm": "Bạn có chắc chắn muốn gỡ bỏ mod này?", "removeConfirmOk": "Gỡ bỏ mod", "removeConfirmCancel": "Hủy", "notCompiled": "Mod cần được biên dịch", "editedLocally": "Mod đã được chỉnh sửa cục bộ", "noDescription": "(không có mô tả)", "users_one": "{{formattedCount}} người dùng", "users_other": "{{formattedCount}} người dùng" }, "modDetails": { "header": { "installedVersion": "Phiên bản đã cài đặt", "latestVersion": "Phiên bản mới nhất", "loading": "đang tải...", "loadingFailed": "tải thất bại", "modId": "Mã nhận diện mod", "modVersion": "Phiên bản mod", "modAuthor": { "title": "Tác giả mod", "homepage": "Trang chủ", "github": "GitHub", "twitter": "X (Twitter)" }, "processes": { "all": "Tất cả các quy trình", "allBut": "Tất cả trừ {{list}}", "except": "{{included}} ngoại trừ {{excluded}}", "tooltip": { "targets": "Các quy trình mục tiêu", "excluded": "Đã loại trừ" } }, "updateNotNeeded": "Phiên bản đã cài đặt giống hệt với phiên bản mới nhất" }, "details": { "title": "Chi tiết", "noData": "Thông tin chi tiết mod bị thiếu." }, "settings": { "title": "Cài đặt", "noData": "Không có cài đặt mod nào có sẵn.", "saveButton": "Lưu cài đặt", "sampleValue": "Giá trị mẫu", "arrayItemAdd": "Thêm mục mới", "arrayItemRemove": "Gỡ bỏ mục" }, "code": { "title": "Mã nguồn", "noData": "Mã nguồn mod bị thiếu.", "collapseExtra": "Thu gọn Readme và Cài đặt" }, "changelog": { "title": "Nhật ký thay đổi", "loadingFailed": "Tải nhật ký thay đổi thất bại. <0>Nhấp vào đây để xem trên GitHub." }, "advanced": { "title": "Nâng cao", "debugLogging": { "title": "Ghi nhật ký gỡ lỗi", "description": "Có thể hữu ích khi gặp sự cố với mod.", "none": "Không có", "modLogs": "Nhật ký mod", "detailedLogs": "Nhật ký gỡ lỗi chi tiết", "showLogButton": "Hiển thị đầu ra nhật ký" }, "modSettings": { "title": "Cài đặt mod", "description": "Dễ dàng xuất hoặc chia sẻ cài đặt mod của bạn.", "loadButton": "Tải", "loadFormattedButton": "Tải định dạng", "saveButton": "Lưu", "invalidData": "Dữ liệu JSON không hợp lệ" }, "customList": { "titleInclusion": "Danh sách bao gồm quy trình tùy chỉnh", "descriptionInclusion": "Danh sách tùy chỉnh của tên tệp thực thi/đường dẫn bổ sung mà mod sẽ nhắm mục tiêu. Danh sách này được thêm vào danh sách bao gồm mà tác giả mod đã xác định để quyết định quy trình nào sẽ nhắm mục tiêu. Đối với mỗi quy trình, mod sẽ được tải nếu đường dẫn tệp thực thi khớp với một trong các mục bao gồm và không khớp với bất kỳ mục loại trừ nào.", "titleExclusion": "Danh sách loại trừ quy trình tùy chỉnh", "descriptionExclusion": "Danh sách tùy chỉnh của tên tệp thực thi/đường dẫn bổ sung mà mod sẽ loại trừ khỏi mục tiêu. Danh sách này được thêm vào danh sách loại trừ mà tác giả mod đã xác định để quyết định quy trình nào sẽ nhắm mục tiêu. Đối với mỗi quy trình, mod sẽ được tải nếu đường dẫn tệp thực thi khớp với một trong các mục bao gồm và không khớp với bất kỳ mục loại trừ nào.", "processListPlaceholder": "Tên/đường dẫn quy trình, mỗi dòng một, ví dụ:", "invalidCharactersWarning": "Danh sách quy trình chứa các ký tự không hợp lệ sẽ bị xóa: {{invalidCharacters}}", "saveButton": "Lưu" }, "includeExcludeCustomOnly": { "title": "Bỏ qua danh sách bao gồm/loại trừ của mod", "description": "Bỏ qua danh sách bao gồm/loại trừ quy trình của mod và chỉ sử dụng các danh sách tùy chỉnh bên trên." }, "patternsMatchCriticalSystemProcesses": { "title": "Xem xét các mẫu danh sách bao gồm cho các quy trình hệ thống quan trọng", "description": "Theo mặc định, Windhawk chỉ tải các bản mod trong các quy trình hệ thống quan trọng nếu quy trình được bao gồm mà không có mẫu, ví dụ: <0>critical.exe, không phải <0>* hoặc <0>*.exe. Để biết thêm chi tiết về các quy trình hệ thống quan trọng, vui lòng tham khảo <1>tài liệu." } }, "changes": { "title": "Thay đổi phiên bản mới nhất", "noData": "Phiên bản đã cài đặt giống hệt với phiên bản mới nhất.", "splitView": "Chế độ chia đôi", "expandLines_one": "Mở rộng một dòng bị ẩn", "expandLines_other": "Mở rộng {{count}} dòng bị ẩn" } }, "modSearch": { "placeholder": "Tìm kiếm mod..." }, "home": { "browse": "Duyệt mod", "installedMods": { "title": "Các mod đã cài đặt", "noMods": "Không có mod nào được cài đặt" }, "featuredMods": { "title": "Mod nổi bật", "noMods": "Không có mod nổi bật nào chưa được cài đặt", "explore": "Khám phá các mod khác" } }, "explore": { "search": { "popularAndTopRated": "Phổ biến và được đánh giá cao", "popular": "Phổ biến", "topRated": "Được đánh giá cao", "newest": "Mới nhất", "lastUpdated": "Cập nhật gần đây", "alphabeticalOrder": "Thứ tự chữ cái" } }, "settings": { "language": { "title": "Ngôn ngữ", "description": "Chọn ngôn ngữ hiển thị ưa thích của bạn cho Windhawk.", "contribute": "<0>Đóng góp bản dịch mới.", "credits": "Bản dịch tiếng Việt của <0>Nguyen Duy Khoa.", "creditsLink": "https://github.com/NDKcute" }, "updates": { "title": "Kiểm tra cập nhật", "description": "Thường xuyên kiểm tra các phiên bản mới của Windhawk và các mod đã cài đặt." }, "devMode": { "title": "Chế độ nhà phát triển", "description": "Hiển thị các thao tác cho nhà phát triển, như tạo và chỉnh sửa mod." }, "advancedSettings": "Cài đặt nâng cao", "hideTrayIcon": { "title": "Ẩn biểu tượng khay", "description": "Bạn sẽ phải tắt tùy chọn này để thoát Windhawk." }, "requireElevation": { "title": "Yêu cầu quyền UAC để chạy Windhawk", "description": "Windhawk yêu cầu quyền quản trị viên, nhưng đối với máy tính chỉ có một người dùng, việc nhận thông báo UAC mỗi lần có thể gây phiền nhiễu, vì vậy Windhawk bỏ qua điều này. Kích hoạt tùy chọn này để yêu cầu quyền UAC để chạy Windhawk." }, "dontAutoShowToolkit": { "title": "Không tự động hiển thị hộp công cụ", "description": "Theo mặc định, Windhawk tự động hiển thị hộp công cụ khi phát hiện rằng thanh tác vụ không truy cập được, do hệ thống không ổn định hoặc lý do khác. Hộp công cụ cho phép thoát Windhawk, chuyển sang chế độ an toàn, và thực hiện các thao tác khác. Hộp công cụ cũng có thể được hiển thị bằng phím tắt Ctrl+Win+W." }, "modInitDialogDelay": { "title": "Độ trễ hộp thoại khởi tạo mod", "description": "Số mili giây chờ trước khi hiển thị hộp thoại tiến trình cho khởi tạo mod." }, "moreAdvancedSettings": { "title": "Cài đặt nâng cao hơn", "restartNotice": "Windhawk sẽ được khởi động lại để áp dụng các cài đặt.", "saveButton": "Lưu và khởi động lại Windhawk", "cancelButton": "Hủy" }, "loggingVerbosity": { "appLoggingTitle": "Mức độ ghi nhật ký của Windhawk", "engineLoggingTitle": "Mức độ ghi nhật ký của động cơ Windhawk", "description": "Nhật ký có thể được xem bằng công cụ như DebugView.", "none": "Không", "error": "Lỗi", "verbose": "Chi tiết" }, "processList": { "titleExclusion": "Danh sách loại trừ quy trình", "descriptionExclusion": "Danh sách tên/đường dẫn quy trình mà Windhawk sẽ không tiêm vào. Có thể hữu ích trong trường hợp Windhawk không tương thích với một chương trình cụ thể. Lưu ý rằng việc thêm một quy trình vào danh sách này không chỉ ngăn Windhawk tùy chỉnh nó mà còn ngăn Windhawk chặn các quy trình con mà quy trình này tạo ra. Điều này sẽ khiến Windhawk tải mod với một độ trễ nhẹ trong những trường hợp như vậy.", "descriptionExclusionWiki": "Để biết thêm thông tin về danh sách loại trừ quy trình, vui lòng tham khảo <0>tài liệu.", "excludeCriticalProcesses": "Loại trừ các quy trình hệ thống quan trọng", "excludeIncompatiblePrograms": "Loại trừ các chương trình không tương thích", "excludeGames": "Loại trừ các trò chơichơi", "titleInclusion": "Danh sách bao gồm quy trình", "descriptionInclusion": "Danh sách tên/đường dẫn quy trình mà Windhawk sẽ tiêm vào, ngay cả khi chúng nằm trong danh sách loại trừ.", "inclusionWithoutExclusionNotice": "Danh sách bao gồm quy trình không có tác dụng với danh sách loại trừ quy trình rỗng.", "inclusionWithoutTotalExclusionNotice": "Nếu bạn định loại trừ tất cả các quy trình trừ những quy trình này, bạn có thể đặt \"*\" trong danh sách loại trừ quy trình.", "processListPlaceholder": "Tên/đường dẫn quy trình, mỗi dòng một, ví dụ:", "invalidCharactersWarning": "Danh sách quy trình chứa các ký tự không hợp lệ sẽ bị xóa: {{invalidCharacters}}" } }, "about": { "title": "Windhawk v{{version}}", "beta": "beta", "subtitle": "Chợ tùy chỉnh cho các chương trình Windows", "credit": "Bởi <0>{{author}}", "update": { "title": "Có bản cập nhật", "subtitle": "Cân nhắc cập nhật Windhawk để có các tính năng mới nhất và sửa lỗi", "updateButton": "Chi tiết thêm" }, "links": { "title": "Liên kết", "homepage": "Trang chủ", "documentation": "Tài liệu" }, "builtWith": { "title": "Được xây dựng với", "vscodium": "Bản phân phối của cộng đồng từ trình soạn thảo VSCode của Microsoft", "llvmMingw": "Bộ công cụ mingw-w64 dựa trên LLVM/Clang/LLD", "minHook": "Thư viện móc API tối giản cho Windows", "others": "Các công cụ và thư viện khác, và một chút mã" } }, "installModal": { "title": "Cài đặt {{mod}}", "warningTitle": "Tiếp tục cẩn thận", "warningDescription": "Mod độc hại có thể làm hỏng máy tính của bạn hoặc vi phạm quyền riêng tư của bạn. Chỉ cài đặt mod từ những tác giả mà bạn tin tưởng.", "modAuthor": "Tác giả mod", "homepage": "Trang chủ", "github": "GitHub", "twitter": "X (Twitter)", "verified": "đã xác minh", "verifiedTooltip": "Chúng tôi đã xác minh rằng hồ sơ này thuộc về tác giả mod, nhưng lưu ý rằng <0>đã xác minh không giống với <0>đáng tin cậy. Đảm bảo rằng bạn tin tưởng tác giả mod hoặc kiểm tra kỹ mã nguồn trước khi cài đặt.", "acceptButton": "Chấp nhận Rủi ro và Cài đặt", "cancelButton": "Hủy" }, "createNewModButton": { "title": "Tạo Mod Mới" }, "devModeAction": { "message": "Việc tạo và chỉnh sửa mod yêu cầu một số kiến thức về phát triển C/C++ cho Windows. Nếu bạn không chắc điều đó nghĩa là gì, có lẽ những tùy chọn này không dành cho bạn.", "hideOptionsCheckbox": "Ẩn tất cả các tùy chọn liên quan đến phát triển", "hideOptionsButton": "Ẩn tùy chọn", "beginCodingButton": "Bắt đầu mã hóa", "cancelButton": "Hủy" }, "safeMode": { "alert": "Windhawk đang chạy ở chế độ an toàn, tất cả các chức năng tiêm mã đã tắt.", "offButton": "Tắt chế độ an toàn", "offConfirm": "Windhawk sẽ được khởi động lại để áp dụng các cài đặt.", "offConfirmOk": "Khởi động lại Windhawk", "offConfirmCancel": "Hủy" }, "modPreview": { "actionUnavailable": "Hành động không khả dụng ở chế độ xem trước", "notCompiled": "Mod cần được biên dịch trước khi có thể xem trước" }, "sidebar": { "modId": "Mã nhận diện mod", "enableMod": "Kích hoạt mod", "enableLogging": "Kích hoạt ghi nhật ký", "notCompiled": "Mod cần được biên dịch", "compile": "Biên dịch Mod", "compilationFailed": "Biên dịch thất bại", "preview": "Xem trước Mod", "showLogOutput": "Hiển thị đầu ra nhật ký", "exit": "Thoát Chế độ Chỉnh sửa", "exitConfirmation": "Những thay đổi kể từ lần biên dịch thành công cuối cùng sẽ bị mất", "exitButtonOk": "Thoát", "exitButtonCancel": "Ở lại" } } ================================================ FILE: src/vscode-windhawk-ui/apps/vscode-windhawk-ui/src/locales/zh-CN/DO_NOT_EDIT.txt ================================================ The files in this folder are generated automatically. To submit a translation update, please refer to this repository: https://github.com/ramensoftware/windhawk-translate ================================================ FILE: src/vscode-windhawk-ui/apps/vscode-windhawk-ui/src/locales/zh-CN/translation.json ================================================ { "general": { "loading": "加载中...", "loadingFailed": "加载失败,请检查您的网络连接", "loadingFailedTitle": "加载失败", "loadingFailedSubtitle": "请检查您的网络连接并重试", "tryAgain": "请重试", "updating": "更新中...", "installing": "安装中...", "compiling": "编译中...", "cut": "剪切", "copy": "复制", "paste": "粘贴", "selectAll": "全选" }, "appHeader": { "home": "主页", "explore": "搜索", "settings": "设置", "about": "关于" }, "mod": { "updateAvailable": "更新可用", "installed": "已安装", "details": "详情", "update": "更新", "install": "安装", "compile": "编译", "disable": "禁用", "enable": "启用", "edit": "编辑", "fork": "克隆", "remove": "移除", "removeConfirm": "您确定要移除此Mod吗?", "removeConfirmOk": "确认移除", "removeConfirmCancel": "取消", "notCompiled": "Mod需要编译", "editedLocally": "Mod已在本地编辑", "noDescription": "(无描述)", "users_one": "{{formattedCount}} 用户", "users_other": "{{formattedCount}} 用户" }, "modDetails": { "header": { "installedVersion": "已安装版本", "latestVersion": "最新版本", "loading": "加载中...", "loadingFailed": "加载失败", "modId": "Mod ID", "modVersion": "Mod版本", "modAuthor": { "title": "Mod开发者", "homepage": "主页", "github": "GitHub主页", "twitter": "X (Twitter)主页" }, "processes": { "all": "全部进程", "allBut": "全部进程,除了{{list}}", "except": "{{included}}除了{{excluded}}", "tooltip": { "targets": "目标进程", "excluded": "不包括" } }, "updateNotNeeded": "安装的版本与最新版本相同" }, "details": { "title": "详情", "noData": "无详情" }, "settings": { "title": "设置", "noData": "当前Mod无设置。", "saveButton": "保存", "sampleValue": "示例值", "arrayItemAdd": "增加新的条目", "arrayItemRemove": "移除条目" }, "code": { "title": "源代码", "noData": "无源代码", "collapseExtra": "折叠README和设置" }, "changelog": { "title": "更新日志", "loadingFailed": "无法加载更新日志。<0>点击此处访问Github。" }, "advanced": { "title": "高级设置", "debugLogging": { "title": "调试日志", "description": "可以帮我们迅速定位Mod存在的问题。", "none": "无", "modLogs": "Mod日志", "detailedLogs": "调试日志详情", "showLogButton": "查看日志" }, "modSettings": { "title": "Mod设置", "description": "便捷的导出和分享您的Mod设置。", "loadButton": "加载", "loadFormattedButton": "格式化后加载", "saveButton": "保存", "invalidData": "无效的JSON数据" }, "customList": { "titleInclusion": "自定义进程包含列表", "descriptionInclusion": "Mod将作用于此自定义列表中的可执行文件/路径。该列表被添加到Mod开发者定义的包含列表中,以确定目标进程。对于每个进程,如果可执行文件/路径匹配一个包含项,而不匹配任何排除项,则加载Mod。", "titleExclusion": "自定义进程排除列表", "descriptionExclusion": "Mod将作用于不在此自定义列表中的可执行文件/路径。该列表被添加到Mod开发者定义的包含列表中,以确定目标进程。对于每个进程,如果可执行文件/路径匹配一个包含项,而不匹配任何排除项,则加载Mod。", "processListPlaceholder": "进程名称/路径,每行一条内容,例如:", "invalidCharactersWarning": "进程列表包含无效字符,这些字符将会被去除:{{invalidCharacters}}", "saveButton": "保存" }, "includeExcludeCustomOnly": { "title": "忽略Mod包含/排除列表", "description": "忽略该Mod的进程包含/排除列表,仅使用上述自定义列表。" }, "patternsMatchCriticalSystemProcesses": { "title": "考虑针对关键系统进程的包含列表模式", "description": "默认情况下,Windhawk仅在关键系统进程被包含时才会加载Mod,例如像 <0>critical.exe”这样的形式,而不是 <0>或者<0>.exe这样的形式。如需了解有关关键系统进程的更多详细信息,请参阅 <1> 文档 。" } }, "changes": { "title": "新版本更新内容", "noData": "安装的版本与最新版本相同。", "splitView": "拆分视图", "expandLines_one": "展开1行隐藏行", "expandLines_other": "展开{{count}}行隐藏行" } }, "modSearch": { "placeholder": "搜索您想要的Mods..." }, "home": { "browse": "浏览Mods", "installedMods": { "title": "已安装的Mods", "noMods": "尚未安装任何Mods" }, "featuredMods": { "title": "精选Mods", "noMods": "尚未安装任何精选Mods", "explore": "浏览更多Mods" } }, "explore": { "search": { "popularAndTopRated": "高分受欢迎的", "popular": "受欢迎的", "topRated": "高分的", "newest": "最新的", "lastUpdated": "上次更新", "alphabeticalOrder": "按字母排序" } }, "settings": { "language": { "title": "语言", "description": "选择Windhawk首选的显示语言", "contribute": "<0>协助我们贡献您的翻译.", "credits": "简体中文翻译由<0>Shifeng Zhao提供。", "creditsLink": "mailto:1193008599@qq.com" }, "updates": { "title": "检查软件更新", "description": "定期检查最新版本的Windhawk和Mods" }, "devMode": { "title": "开发者选项", "description": "为开发者显示的操作,例如创建和修改Mods。" }, "advancedSettings": "高级设置", "hideTrayIcon": { "title": "隐藏托盘图标", "description": "您必须禁用此选项才能退出Windhawk。" }, "requireElevation": { "title": "需要管理员权限(UAC)来运行Windhawk", "description": "Windhawk需要管理员权限,但当您电脑只有一个账户时,每次都弹出用户账户控制(UAC)或许很影响您的正常使用,所以Windhawk选择绕过它。启用此选项可在运行Windhawk时要求用户账户控制(UAC)提升权限。" }, "dontAutoShowToolkit": { "title": "禁止自动显示Toolkit窗口。", "description": "默认情况下,当Windhawk检测到任务栏无法访问时(可能是由于系统不稳定或其他原因),会自动显示Toolkit窗口。Toolkit窗口允许退出Windhawk,切换到安全模式和进行其他操作。禁用后仍可以使用Ctrl+Win+W键盘快捷键显示Toolkit窗口。" }, "modInitDialogDelay": { "title": "Mod初始化对话延迟", "description": "在显示Mod初始化进度对话框之前需要等待的毫秒数。" }, "moreAdvancedSettings": { "title": "更多高级设置", "restartNotice": "Windhawk将重启以应用软件设置。", "saveButton": "保存并重启Windhawk", "cancelButton": "取消" }, "loggingVerbosity": { "appLoggingTitle": "Windhawk日志", "engineLoggingTitle": "Windhawk引擎的日志信息", "description": "可以使用DebugView等工具查看日志。", "none": "无", "error": "错误", "verbose": "详细日志信息" }, "processList": { "titleExclusion": "排除的进程列表", "descriptionExclusion": "这是一个Windhawk不会注入的进程名称/路径列表。当Windhawk与某个特定程序不兼容时,该列表可能会很有用。需要注意的是,将某个进程添加到这个列表中,不仅会阻止Windhawk对其进行自定义设置,还会阻止Windhawk拦截该进程所创建的子进程。在这种情况下,这将导致Windhawk加载Mods时会稍有延迟。", "descriptionExclusionWiki": "如需了解有关排除进程以及内置进程排除列表的更多详细信息,请参阅<0> 相关文档 。", "excludeCriticalProcesses": "排除关键系统进程", "excludeIncompatiblePrograms": "排除已知不兼容的程序", "excludeGames": "排除知名游戏", "titleInclusion": "包含的进程列表", "descriptionInclusion": "Windhawk将注入的进程名/路径列表,即使它们在排除列表中。", "inclusionWithoutExclusionNotice": "进程包含列表对于空的进程排除列表无效。", "inclusionWithoutTotalExclusionNotice": "如果您打算排除除这些之外的所有进程,您可以用“*”在进程排除列表中设置。", "processListPlaceholder": "进程名/路径,每行一个,例如:", "invalidCharactersWarning": "进程列表中包含无效字符,这些无效字符将会被去除:{{invalidCharacters}}" } }, "about": { "title": "Windhawk版本{{version}}", "beta": "beta版本", "subtitle": "可以魔改您Windows功能的得力软件", "credit": "<0>{{author}}团队倾情巨献", "update": { "title": "一项更新可用", "subtitle": "更新Windhawk以获得最新的功能", "updateButton": "更多详情" }, "links": { "title": "官方链接", "homepage": "官方主页", "documentation": "官方文档" }, "builtWith": { "title": "使用以下项目", "vscodium": "一个开源免费的代码编辑器", "llvmMingw": "基于LLVM、Clang和LLD的mingw-w64工具链", "minHook": "Windows平台下简单好用的API钩子库", "others": "以及其他工具和库、一些开源代码" } }, "installModal": { "title": "安装{{mod}}", "warningTitle": "请谨慎安装Mod", "warningDescription": "恶意的Mod可能会破坏您的电脑系统或侵犯您的个人隐私。确保只从您信任的开发者那里安装Mod。", "modAuthor": "Mod开发者", "homepage": "主页", "github": "GitHub主页", "twitter": "X (Twitter)主页", "verified": "已认证", "verifiedTooltip": "我们已验证此配置文件属于此Mod开发者,但请您注意<0>已认证的Mods不同于<0>可信任的Mods。请确保您信任对应的Mod开发者,或仔细检查源代码后再进行安装。", "acceptButton": "接受风险并安装", "cancelButton": "取消" }, "createNewModButton": { "title": "新建Mod" }, "devModeAction": { "message": "创建和修改Mod需要一定的Windows C/C++开发知识。如果您不了解相关知识,建议您不要进行相关操作。", "hideOptionsCheckbox": "隐藏所有与开发相关的选项", "hideOptionsButton": "隐藏选项", "beginCodingButton": "开始编写代码", "cancelButton": "取消" }, "safeMode": { "alert": "Windhawk正在安全模式下运行,所有代码注入功能已关闭。", "offButton": "退出安全模式", "offConfirm": "应用设置后Windhawk将会重启", "offConfirmOk": "重启Windhawk", "offConfirmCancel": "取消" }, "modPreview": { "actionUnavailable": "预览模式下该操作不可用", "notCompiled": "Mod需要编译后才可预览" }, "sidebar": { "modId": "Mod ID", "enableMod": "启用Mod", "enableLogging": "启用日志", "notCompiled": "Mod需要编译", "compile": "编译Mod", "compilationFailed": "编译失败", "preview": "预览Mod", "showLogOutput": "显示输出的日志", "exit": "退出编辑模式", "exitConfirmation": "是否确认退出,最后一次成功编译的改动都将丢失", "exitButtonOk": "退出", "exitButtonCancel": "取消退出" } } ================================================ FILE: src/vscode-windhawk-ui/apps/vscode-windhawk-ui/src/locales/zh-TW/DO_NOT_EDIT.txt ================================================ The files in this folder are generated automatically. To submit a translation update, please refer to this repository: https://github.com/ramensoftware/windhawk-translate ================================================ FILE: src/vscode-windhawk-ui/apps/vscode-windhawk-ui/src/locales/zh-TW/translation.json ================================================ { "general": { "loading": "正在載入...", "loadingFailed": "載入失敗,請檢查您的網路連線", "loadingFailedTitle": "載入失敗", "loadingFailedSubtitle": "請檢查您的網路連線後再試一次", "tryAgain": "重試", "updating": "正在更新...", "installing": "正在安裝...", "compiling": "正在編譯...", "cut": "剪下", "copy": "複製", "paste": "貼上", "selectAll": "全選" }, "appHeader": { "home": "首頁", "explore": "探索", "settings": "設定", "about": "關於" }, "mod": { "updateAvailable": "有可用更新", "installed": "已安裝", "details": "詳細資訊", "update": "更新", "install": "安裝", "compile": "編譯", "disable": "停用", "enable": "啟用", "edit": "編輯", "fork": "分支", "remove": "移除", "removeConfirm": "您確定要移除此模組嗎?", "removeConfirmOk": "移除模組", "removeConfirmCancel": "取消", "notCompiled": "模組需要編譯", "editedLocally": "模組已在本地編輯", "noDescription": "(無描述)", "users_one": "{{formattedCount}} 位使用者", "users_other": "{{formattedCount}} 位使用者" }, "modDetails": { "header": { "installedVersion": "已安裝版本", "latestVersion": "最新版本", "loading": "正在載入...", "loadingFailed": "載入失敗", "modId": "模組識別碼", "modVersion": "模組版本", "modAuthor": { "title": "模組作者", "homepage": "主頁", "github": "GitHub", "twitter": "X (Twitter)" }, "processes": { "all": "所有程序", "allBut": "除了 {{list}} 之外的所有程序", "except": "{{included}} 除了 {{excluded}}", "tooltip": { "targets": "目標程序", "excluded": "排除" } }, "updateNotNeeded": "已安裝的版本與最新版本相同" }, "details": { "title": "詳細資訊", "noData": "模組詳細資訊缺失。" }, "settings": { "title": "設定", "noData": "沒有可用的模組設定。", "saveButton": "保存設定", "sampleValue": "範例值", "arrayItemAdd": "新增項目", "arrayItemRemove": "移除項目" }, "code": { "title": "原始碼", "noData": "模組原始碼缺失。", "collapseExtra": "折疊 Readme 和設定" }, "changelog": { "title": "更新日誌", "loadingFailed": "加載更新日誌失敗。<0>點擊這裡 在 GitHub 上查看。" }, "advanced": { "title": "進階", "debugLogging": { "title": "除錯日誌", "description": "對於解決模組問題可能很有幫助。", "none": "無", "modLogs": "模組日誌", "detailedLogs": "詳細除錯日誌", "showLogButton": "顯示日誌輸出" }, "modSettings": { "title": "模組設定", "description": "輕鬆匯出或分享您的模組設定。", "loadButton": "載入", "loadFormattedButton": "載入格式化資料", "saveButton": "保存", "invalidData": "無效的 JSON 資料" }, "customList": { "titleInclusion": "自訂程序包含清單", "descriptionInclusion": "模組將目標鎖定的額外可執行檔名稱或路徑的自訂清單。此清單會添加到模組作者定義的包含清單中,以決定要鎖定的程序。對於每個程序,如果可執行檔路徑符合其中一個包含項目且不符合任何排除項目,則會載入模組。", "titleExclusion": "自訂程序排除清單", "descriptionExclusion": "模組將排除的額外可執行檔名稱或路徑的自訂清單。此清單會添加到模組作者定義的排除清單中,以決定要鎖定的程序。對於每個程序,如果可執行檔路徑符合其中一個包含項目且不符合任何排除項目,則會載入模組。", "processListPlaceholder": "程序名稱或路徑,每行一個,例如:", "saveButton": "儲存" }, "includeExcludeCustomOnly": { "title": "忽略模組的包含/排除清單", "description": "忽略模組的程序包含/排除清單,僅使用上面的自訂清單。" } }, "changes": { "title": "最新版本變更", "noData": "已安裝的版本與最新版本相同。", "splitView": "分割視窗", "expandLines_one": "展開一條隱藏的行", "expandLines_other": "展開 {{count}} 條隱藏的行" } }, "modSearch": { "placeholder": "搜尋模組..." }, "home": { "browse": "瀏覽模組", "installedMods": { "title": "已安裝的模組", "noMods": "未安裝任何模組" }, "featuredMods": { "title": "精選模組", "noMods": "沒有尚未安裝的精選模組", "explore": "探索其他模組" } }, "explore": { "search": { "popularAndTopRated": "熱門與高評價", "popular": "熱門", "topRated": "高評價", "newest": "最新", "lastUpdated": "最近更新", "alphabeticalOrder": "字母順序" } }, "settings": { "language": { "title": "語言", "description": "選擇您偏好的 Windhawk 顯示語言。", "contribute": "<0>貢獻新的翻譯。", "credits": "正體中文翻譯由 <0>abc0922001 提供。", "creditsLink": "mailto:abc0922001@hotmail.com" }, "updates": { "title": "檢查更新", "description": "定期檢查 Windhawk 及已安裝模組的新版本。" }, "devMode": { "title": "開發者模式", "description": "顯示開發者操作,例如新建和修改模組。" }, "advancedSettings": "進階設定", "hideTrayIcon": { "title": "隱藏系統匣圖示", "description": "您將需要禁用此選項才能退出 Windhawk。" }, "requireElevation": { "title": "需要系統管理員操作來執行 Windhawk", "description": "Windhawk 需要系統管理員權限,但對於一般使用者,每次都提示使用者帳戶控制(UAC)或許很影響您的正常使用,所以 Windhawk 繞過了它。啟用此選項需要使用者帳戶控制(UAC)來執行 Windhawk。" }, "dontAutoShowToolkit": { "title": "不要自動顯示工具箱對話框", "description": "預設情況下,當 Windhawk 檢測到由於系統不穩定或其他原因導致無法訪問任務欄時,會自動顯示工具箱對話框。工具箱對話框允許退出 Windhawk、切換到安全模式以及進行其他操作。工具箱對話框也可以通過 Ctrl+Win+W 鍵盤快捷鍵顯示。" }, "modInitDialogDelay": { "title": "模組初始化對話框延遲", "description": "等待顯示模組初始化進度對話框的毫秒數。" }, "moreAdvancedSettings": { "title": "更多進階設定", "restartNotice": "Windhawk 將重新啟動以應用設定。", "saveButton": "保存並重新啟動 Windhawk", "cancelButton": "取消" }, "loggingVerbosity": { "appLoggingTitle": "Windhawk 日誌詳細程度", "engineLoggingTitle": "Windhawk 引擎日誌詳細程度", "description": "可以使用 DebugView 等工具查看日誌。", "none": "無", "error": "錯誤", "verbose": "詳細" }, "processList": { "titleExclusion": "排除程序清單", "descriptionExclusion": "不希望 Windhawk 注入的程序名稱或路徑清單。當 Windhawk 與特定程式不相容時,可以派上用場。請注意,將程序添加到此清單不僅會阻止 Windhawk 自訂它,還會阻止 Windhawk 攔截該程序建立的子程序。這將導致 Windhawk 在這種情況下稍後載入模組。", "titleInclusion": "包含程序清單", "descriptionInclusion": "即使程序在排除清單中,Windhawk 也會注入的程序名稱或路徑清單。", "inclusionWithoutExclusionNotice": "沒有空的程序排除清單時,程序包含清單無效。", "inclusionWithoutTotalExclusionNotice": "如果您想要排除所有程序,除了這些程序,可以在程序排除清單中設定 \"*\"。", "processListPlaceholder": "程序名稱或路徑,每行一個,例如:" } }, "about": { "title": "Windhawk v{{version}}", "beta": "測試版", "subtitle": "Windows 程式的客製化市場", "credit": "作者:<0>{{author}}", "update": { "title": "有新版本可用", "subtitle": "考慮更新 Windhawk 以獲得最新功能和錯誤修正", "updateButton": "更多詳細資訊" }, "links": { "title": "連結", "homepage": "首頁", "documentation": "文件" }, "builtWith": { "title": "開發工具", "vscodium": "由社群驅動的 Microsoft VSCode 編輯器版本", "llvmMingw": "基於 LLVM/Clang/LLD 的 mingw-w64 工具鏈", "minHook": "Windows 的極簡 API 攔截庫", "others": "其他工具、庫和一些程式碼" } }, "installModal": { "title": "安裝 {{mod}}", "warningTitle": "小心操作", "warningDescription": "惡意的模組可能會損壞您的電腦或侵犯您的隱私。僅安裝您信任的作者的模組。", "modAuthor": "模組作者", "homepage": "首頁", "github": "GitHub", "twitter": "X (Twitter)", "verified": "已驗證", "verifiedTooltip": "我們已驗證此帳戶屬於模組作者,但請注意 <0>已驗證 不等於 <0>信任。請確保您信任模組作者,或在安裝前仔細檢查原始碼。", "acceptButton": "接受風險並安裝", "cancelButton": "取消" }, "createNewModButton": { "title": "建立新模組" }, "devModeAction": { "message": "建立和修改模組需要一些 Windows C/C++ 開發的知識。如果您不確定是什麼意思,這些選項可能不適合您。", "hideOptionsCheckbox": "隱藏所有開發相關選項", "hideOptionsButton": "隱藏選項", "beginCodingButton": "開始編碼", "cancelButton": "取消" }, "safeMode": { "alert": "Windhawk 正在安全模式下運行,所有程式碼注入功能已關閉。", "offButton": "關閉安全模式", "offConfirm": "Windhawk 將重新啟動以應用設定。", "offConfirmOk": "重新啟動 Windhawk", "offConfirmCancel": "取消" }, "modPreview": { "actionUnavailable": "預覽模式下無法使用此功能", "notCompiled": "模組需要編譯才能預覽" }, "sidebar": { "modId": "模組識別碼", "enableMod": "啟用模組", "enableLogging": "啟用記錄", "notCompiled": "模組需要編譯", "compile": "編譯模組", "compilationFailed": "編譯失敗", "preview": "預覽模組", "showLogOutput": "顯示記錄輸出", "exit": "退出編輯模式", "exitConfirmation": "自上次成功編譯以來的更改將丟失", "exitButtonOk": "退出", "exitButtonCancel": "保留" } } ================================================ FILE: src/vscode-windhawk-ui/apps/vscode-windhawk-ui/src/main.css ================================================ body { /* remove padding set by vscode */ padding: 0; margin: 0; /* scrollbars sometimes show up because of Ant Design's context menu, hide them, we aren't using them anyway */ overflow: hidden; } ================================================ FILE: src/vscode-windhawk-ui/apps/vscode-windhawk-ui/src/main.tsx ================================================ import { StrictMode } from 'react'; import * as ReactDOM from 'react-dom/client'; import App from './app/app'; import './main.css'; const root = ReactDOM.createRoot( document.getElementById('root') as HTMLElement ); root.render( ); ================================================ FILE: src/vscode-windhawk-ui/apps/vscode-windhawk-ui/src/polyfills.ts ================================================ /** * Polyfill stable language features. These imports will be optimized by `@babel/preset-env`. * * See: https://github.com/zloirock/core-js#babel */ import 'core-js/stable'; import 'regenerator-runtime/runtime'; ================================================ FILE: src/vscode-windhawk-ui/apps/vscode-windhawk-ui/tsconfig.app.json ================================================ { "extends": "./tsconfig.json", "compilerOptions": { "outDir": "../../dist/out-tsc", "types": ["node"] }, "files": [ "../../node_modules/@nrwl/react/typings/cssmodule.d.ts", "../../node_modules/@nrwl/react/typings/image.d.ts" ], "exclude": [ "jest.config.ts", "**/*.spec.ts", "**/*.test.ts", "**/*.spec.tsx", "**/*.test.tsx", "**/*.spec.js", "**/*.test.js", "**/*.spec.jsx", "**/*.test.jsx" ], "include": ["**/*.js", "**/*.jsx", "**/*.ts", "**/*.tsx"] } ================================================ FILE: src/vscode-windhawk-ui/apps/vscode-windhawk-ui/tsconfig.json ================================================ { "extends": "../../tsconfig.base.json", "compilerOptions": { "jsx": "react-jsx", "allowJs": true, "esModuleInterop": true, "allowSyntheticDefaultImports": true, "forceConsistentCasingInFileNames": true, "strict": true, "noImplicitOverride": true, "noPropertyAccessFromIndexSignature": true, "noImplicitReturns": true, "noFallthroughCasesInSwitch": true }, "files": [], "include": [], "references": [ { "path": "./tsconfig.app.json" }, { "path": "./tsconfig.spec.json" } ] } ================================================ FILE: src/vscode-windhawk-ui/apps/vscode-windhawk-ui/tsconfig.spec.json ================================================ { "extends": "./tsconfig.json", "compilerOptions": { "outDir": "../../dist/out-tsc", "module": "commonjs", "types": ["jest", "node"] }, "include": [ "jest.config.ts", "**/*.test.ts", "**/*.spec.ts", "**/*.test.tsx", "**/*.spec.tsx", "**/*.test.js", "**/*.spec.js", "**/*.test.jsx", "**/*.spec.jsx", "**/*.d.ts" ], "files": [ "../../node_modules/@nrwl/react/typings/cssmodule.d.ts", "../../node_modules/@nrwl/react/typings/image.d.ts" ] } ================================================ FILE: src/vscode-windhawk-ui/apps/vscode-windhawk-ui/webpack.config.js ================================================ // Reference: // https://github.com/ideafast/ideafast-portal/blob/59fb91104db81a86fc282491ac936d49fb4ef0e8/packages/itmat-ui-react/webpack.config.js#L7 const webpack = require('webpack'); const { composePlugins, withNx } = require('@nx/webpack'); const { withReact } = require('@nx/react'); const MonacoWebpackPlugin = require('monaco-editor-webpack-plugin'); const { version } = require('../../package.json'); module.exports = composePlugins( withNx(), withReact(), (config) => { config.plugins.splice(0, 0, new webpack.EnvironmentPlugin({ REACT_APP_VERSION: version, })); // Configure Monaco to only include YAML language support config.plugins.push(new MonacoWebpackPlugin({ languages: ['yaml'], })); return config; } ); ================================================ FILE: src/vscode-windhawk-ui/apps/vscode-windhawk-ui-e2e/.eslintrc.json ================================================ { "extends": ["plugin:cypress/recommended", "../../.eslintrc.json"], "ignorePatterns": ["!**/*"], "overrides": [ { "files": ["*.ts", "*.tsx", "*.js", "*.jsx"], "rules": {} }, { "files": ["src/plugins/index.js"], "rules": { "@typescript-eslint/no-var-requires": "off", "no-undef": "off" } } ] } ================================================ FILE: src/vscode-windhawk-ui/apps/vscode-windhawk-ui-e2e/cypress.config.ts ================================================ import { defineConfig } from 'cypress'; import { nxE2EPreset } from '@nrwl/cypress/plugins/cypress-preset'; export default defineConfig({ e2e: nxE2EPreset(__dirname), }); ================================================ FILE: src/vscode-windhawk-ui/apps/vscode-windhawk-ui-e2e/project.json ================================================ { "name": "vscode-windhawk-ui-e2e", "$schema": "../../node_modules/nx/schemas/project-schema.json", "sourceRoot": "apps/vscode-windhawk-ui-e2e/src", "projectType": "application", "targets": { "e2e": { "executor": "@nrwl/cypress:cypress", "options": { "cypressConfig": "apps/vscode-windhawk-ui-e2e/cypress.config.ts", "devServerTarget": "vscode-windhawk-ui:serve:e2e", "testingType": "e2e" }, "configurations": { "production": { "devServerTarget": "vscode-windhawk-ui:serve:production" } } }, "lint": { "executor": "@nrwl/linter:eslint", "outputs": ["{options.outputFile}"], "options": { "lintFilePatterns": ["apps/vscode-windhawk-ui-e2e/**/*.{js,ts}"] } } }, "tags": [], "implicitDependencies": ["vscode-windhawk-ui"] } ================================================ FILE: src/vscode-windhawk-ui/apps/vscode-windhawk-ui-e2e/src/e2e/app.cy.ts ================================================ import { getGreeting } from '../support/app.po'; describe('vscode-windhawk-ui', () => { beforeEach(() => cy.visit('/')); it('should display welcome message', () => { // Custom command example, see `../support/commands.ts` file cy.login('my-email@something.com', 'myPassword'); // Function helper example, see `../support/app.po.ts` file getGreeting().contains('Welcome vscode-windhawk-ui'); }); }); ================================================ FILE: src/vscode-windhawk-ui/apps/vscode-windhawk-ui-e2e/src/fixtures/example.json ================================================ { "name": "Using fixtures to represent data", "email": "hello@cypress.io" } ================================================ FILE: src/vscode-windhawk-ui/apps/vscode-windhawk-ui-e2e/src/support/app.po.ts ================================================ export const getGreeting = () => cy.get('h1'); ================================================ FILE: src/vscode-windhawk-ui/apps/vscode-windhawk-ui-e2e/src/support/commands.ts ================================================ // *********************************************** // This example commands.js shows you how to // create various custom commands and overwrite // existing commands. // // For more comprehensive examples of custom // commands please read more here: // https://on.cypress.io/custom-commands // *********************************************** // eslint-disable-next-line @typescript-eslint/no-namespace declare namespace Cypress { // eslint-disable-next-line @typescript-eslint/no-unused-vars interface Chainable { login(email: string, password: string): void; } } // // -- This is a parent command -- Cypress.Commands.add('login', (email, password) => { console.log('Custom command example: Login', email, password); }); // // -- This is a child command -- // Cypress.Commands.add("drag", { prevSubject: 'element'}, (subject, options) => { ... }) // // // -- This is a dual command -- // Cypress.Commands.add("dismiss", { prevSubject: 'optional'}, (subject, options) => { ... }) // // // -- This will overwrite an existing command -- // Cypress.Commands.overwrite("visit", (originalFn, url, options) => { ... }) ================================================ FILE: src/vscode-windhawk-ui/apps/vscode-windhawk-ui-e2e/src/support/e2e.ts ================================================ // *********************************************************** // This example support/index.js is processed and // loaded automatically before your test files. // // This is a great place to put global configuration and // behavior that modifies Cypress. // // You can change the location of this file or turn off // automatically serving support files with the // 'supportFile' configuration option. // // You can read more here: // https://on.cypress.io/configuration // *********************************************************** // Import commands.js using ES2015 syntax: import './commands'; ================================================ FILE: src/vscode-windhawk-ui/apps/vscode-windhawk-ui-e2e/tsconfig.json ================================================ { "extends": "../../tsconfig.base.json", "compilerOptions": { "sourceMap": false, "outDir": "../../dist/out-tsc", "allowJs": true, "types": ["cypress", "node"] }, "include": ["src/**/*.ts", "src/**/*.js", "cypress.config.ts"] } ================================================ FILE: src/vscode-windhawk-ui/babel.config.json ================================================ { "babelrcRoots": ["*"] } ================================================ FILE: src/vscode-windhawk-ui/jest.config.ts ================================================ import { getJestProjects } from '@nrwl/jest'; export default { projects: getJestProjects(), }; ================================================ FILE: src/vscode-windhawk-ui/jest.preset.js ================================================ const nxPreset = require('@nrwl/jest/preset').default; module.exports = { ...nxPreset }; ================================================ FILE: src/vscode-windhawk-ui/libs/.gitkeep ================================================ ================================================ FILE: src/vscode-windhawk-ui/nx.json ================================================ { "$schema": "./node_modules/nx/schemas/nx-schema.json", "npmScope": "vscode-windhawk-ui-nx", "affected": { "defaultBase": "master" }, "tasksRunnerOptions": { "default": { "runner": "nx/tasks-runners/default", "options": { "cacheableOperations": ["build", "lint", "test", "e2e"] } } }, "targetDefaults": { "build": { "dependsOn": ["^build"], "inputs": ["production", "^production"] }, "test": { "inputs": ["default", "^production", "{workspaceRoot}/jest.preset.js"] }, "e2e": { "inputs": ["default", "^production"] }, "lint": { "inputs": ["default", "{workspaceRoot}/.eslintrc.json"] } }, "namedInputs": { "default": ["{projectRoot}/**/*", "sharedGlobals"], "production": [ "default", "!{projectRoot}/**/?(*.)+(spec|test).[jt]s?(x)?(.snap)", "!{projectRoot}/tsconfig.spec.json", "!{projectRoot}/jest.config.[jt]s", "!{projectRoot}/.eslintrc.json" ], "sharedGlobals": [ "{workspaceRoot}/babel.config.json", "{workspaceRoot}/package.json" ] }, "generators": { "@nrwl/react": { "application": { "style": "styled-components", "unitTestRunner": "jest", "linter": "eslint", "babel": true }, "component": { "style": "styled-components" }, "library": { "style": "styled-components", "unitTestRunner": "jest", "linter": "eslint" } } }, "defaultProject": "vscode-windhawk-ui" } ================================================ FILE: src/vscode-windhawk-ui/package.json ================================================ { "name": "vscode-windhawk-ui-nx", "version": "1.7.3", "scripts": { "start": "nx serve", "watch": "nx build --configuration=development --watch", "build": "nx build", "build-ext": "npm run build && (robocopy /MIR dist/apps/vscode-windhawk-ui ../vscode-windhawk/webview) ^& IF %ERRORLEVEL% LSS 8 SET ERRORLEVEL = 0", "test": "nx test", "build-css": "less-watch-compiler --enable-js --run-once --main-file=app.less apps/vscode-windhawk-ui/src/app/ apps/vscode-windhawk-ui/src/app/", "watch-css": "npm run build-css && less-watch-compiler --enable-js --main-file=app.less apps/vscode-windhawk-ui/src/app/ apps/vscode-windhawk-ui/src/app/" }, "private": true, "dependencies": { "@fortawesome/fontawesome-svg-core": "^7.1.0", "@fortawesome/free-brands-svg-icons": "^7.1.0", "@fortawesome/free-solid-svg-icons": "^7.1.0", "@fortawesome/react-fontawesome": "^3.1.1", "@monaco-editor/react": "^4.7.0", "@types/js-yaml": "^4.0.9", "antd": "^4.24.16", "core-js": "^3.47.0", "i18next": "^25.7.0", "i18next-http-backend": "^3.0.2", "immer": "^10.2.0", "js-yaml": "^4.1.1", "prism-themes": "^1.9.0", "prismjs": "^1.30.0", "react": "18.3.1", "react-diff-view": "^3.3.2", "react-dom": "18.3.1", "react-i18next": "^16.3.5", "react-infinite-scroll-component": "^6.1.0", "react-is": "18.3.1", "react-markdown": "^10.1.0", "react-router-dom": "^7.9.6", "regenerator-runtime": "0.14.1", "rehype-raw": "^7.0.0", "rehype-sanitize": "^6.0.0", "rehype-slug": "^6.0.0", "remark-gfm": "^4.0.1", "styled-components": "6.1.19", "swr": "^2.3.7", "tslib": "^2.8.1", "unidiff": "^1.0.4", "usehooks-ts": "^3.1.1" }, "devDependencies": { "@nrwl/cli": "^14.3.2", "@nrwl/cypress": "19.8.14", "@nrwl/eslint-plugin-nx": "19.8.14", "@nrwl/jest": "19.8.14", "@nrwl/linter": "19.8.14", "@nrwl/react": "^19.4.4", "@nrwl/web": "19.8.14", "@nrwl/webpack": "^19.5.0", "@nrwl/workspace": "19.8.14", "@pmmmwh/react-refresh-webpack-plugin": "^0.6.2", "@svgr/webpack": "^8.1.0", "@testing-library/react": "16.3.0", "@types/jest": "29.5.14", "@types/node": "22.19.1", "@types/prismjs": "^1.26.5", "@types/react": "18.3.27", "@types/react-dom": "18.3.7", "@types/react-is": "18.3.1", "@types/styled-components": "5.1.36", "@typescript-eslint/eslint-plugin": "^8.48.0", "@typescript-eslint/parser": "^8.48.0", "babel-jest": "29.7.0", "babel-plugin-styled-components": "2.1.4", "cypress": "^13.17.0", "eslint": "~8.57.1", "eslint-config-prettier": "9.1.2", "eslint-plugin-cypress": "^3.6.0", "eslint-plugin-import": "2.32.0", "eslint-plugin-jsx-a11y": "6.10.2", "eslint-plugin-react": "7.37.5", "eslint-plugin-react-hooks": "4.6.2", "jest": "29.7.0", "jest-environment-jsdom": "29.7.0", "less-watch-compiler": "^1.16.3", "monaco-editor": "^0.55.1", "monaco-editor-webpack-plugin": "^7.1.1", "nx": "20.8.3", "prettier": "^3.7.3", "react-refresh": "^0.18.0", "react-test-renderer": "18.3.1", "ts-jest": "29.4.6", "ts-node": "10.9.2", "typescript": "~5.9.3", "url-loader": "^4.1.1" } } ================================================ FILE: src/vscode-windhawk-ui/tools/generators/.gitkeep ================================================ ================================================ FILE: src/vscode-windhawk-ui/tools/tsconfig.tools.json ================================================ { "extends": "../tsconfig.base.json", "compilerOptions": { "outDir": "../dist/out-tsc/tools", "rootDir": ".", "module": "commonjs", "target": "es5", "types": ["node"], "importHelpers": false }, "include": ["**/*.ts"] } ================================================ FILE: src/vscode-windhawk-ui/tsconfig.base.json ================================================ { "compileOnSave": false, "compilerOptions": { "rootDir": ".", "sourceMap": true, "declaration": false, "moduleResolution": "node", "emitDecoratorMetadata": true, "experimentalDecorators": true, "importHelpers": true, "target": "es2015", "module": "esnext", "lib": ["es2021", "dom"], "skipLibCheck": true, "skipDefaultLibCheck": true, "baseUrl": ".", "paths": {} }, "exclude": ["node_modules", "tmp"] } ================================================ FILE: src/windhawk/.clang-format ================================================ BasedOnStyle: Chromium IndentWidth: 4 # Reference: https://github.com/chromium/chromium/blob/3d90e395a5e87e305e567c097c434549f0d0874e/.clang-format # Make sure code like: # BEGIN_MESSAGE_MAP() # MESSAGE_HANDLER(WidgetHostViewHost_Update, OnUpdate) # END_MESSAGE_MAP() # gets correctly indented. MacroBlockBegin: "^\ BEGIN_MSG_MAP|\ BEGIN_MSG_MAP_EX|\ BEGIN_DLGRESIZE_MAP$" MacroBlockEnd: "^\ END_MSG_MAP|\ END_DLGRESIZE_MAP$" ================================================ FILE: src/windhawk/.gitattributes ================================================ ############################################################################### # Set default behavior to automatically normalize line endings. ############################################################################### * text=auto ############################################################################### # Set default behavior for command prompt diff. # # This is need for earlier builds of msysgit that does not have it on by # default for csharp files. # Note: This is only used by command line ############################################################################### #*.cs diff=csharp ############################################################################### # Set the merge driver for project and solution files # # Merging from the command prompt will add diff markers to the files if there # are conflicts (Merging from VS is not affected by the settings below, in VS # the diff markers are never inserted). Diff markers may cause the following # file extensions to fail to load in VS. An alternative would be to treat # these files as binary and thus will always conflict and require user # intervention with every merge. To do so, just uncomment the entries below ############################################################################### #*.sln merge=binary #*.csproj merge=binary #*.vbproj merge=binary #*.vcxproj merge=binary #*.vcproj merge=binary #*.dbproj merge=binary #*.fsproj merge=binary #*.lsproj merge=binary #*.wixproj merge=binary #*.modelproj merge=binary #*.sqlproj merge=binary #*.wwaproj merge=binary ############################################################################### # behavior for image files # # image files are treated as binary by default. ############################################################################### #*.jpg binary #*.png binary #*.gif binary ############################################################################### # diff behavior for common document formats # # Convert binary document formats to text before diffing them. This feature # is only available from the command line. Turn it on by uncommenting the # entries below. ############################################################################### #*.doc diff=astextplain #*.DOC diff=astextplain #*.docx diff=astextplain #*.DOCX diff=astextplain #*.dot diff=astextplain #*.DOT diff=astextplain #*.pdf diff=astextplain #*.PDF diff=astextplain #*.rtf diff=astextplain #*.RTF diff=astextplain ================================================ FILE: src/windhawk/.gitignore ================================================ ## Ignore Visual Studio temporary files, build results, and ## files generated by popular Visual Studio add-ons. # User-specific files *.suo *.user *.userosscache *.sln.docstates # User-specific files (MonoDevelop/Xamarin Studio) *.userprefs # Build results [Dd]ebug/ [Dd]ebugPublic/ [Rr]elease/ [Rr]eleases/ x64/ x86/ bld/ [Bb]in/ [Oo]bj/ [Ll]og/ # Visual Studio 2015 cache/options directory .vs/ # Uncomment if you have tasks that create the project's static files in wwwroot #wwwroot/ # MSTest test Results [Tt]est[Rr]esult*/ [Bb]uild[Ll]og.* # NUNIT *.VisualState.xml TestResult.xml # Build Results of an ATL Project [Dd]ebugPS/ [Rr]eleasePS/ dlldata.c # DNX project.lock.json project.fragment.lock.json artifacts/ *_i.c *_p.c *_i.h *.ilk *.meta *.obj *.pch *.pdb *.pgc *.pgd *.rsp *.sbr *.tlb *.tli *.tlh *.tmp *.tmp_proj *.log *.vspscc *.vssscc .builds *.pidb *.svclog *.scc # Chutzpah Test files _Chutzpah* # Visual C++ cache files ipch/ *.aps *.ncb *.opendb *.opensdf *.sdf *.cachefile *.VC.db *.VC.VC.opendb # Visual Studio profiler *.psess *.vsp *.vspx *.sap # TFS 2012 Local Workspace $tf/ # Guidance Automation Toolkit *.gpState # ReSharper is a .NET coding add-in _ReSharper*/ *.[Rr]e[Ss]harper *.DotSettings.user # JustCode is a .NET coding add-in .JustCode # TeamCity is a build add-in _TeamCity* # DotCover is a Code Coverage Tool *.dotCover # NCrunch _NCrunch_* .*crunch*.local.xml nCrunchTemp_* # MightyMoose *.mm.* AutoTest.Net/ # Web workbench (sass) .sass-cache/ # Installshield output folder [Ee]xpress/ # DocProject is a documentation generator add-in DocProject/buildhelp/ DocProject/Help/*.HxT DocProject/Help/*.HxC DocProject/Help/*.hhc DocProject/Help/*.hhk DocProject/Help/*.hhp DocProject/Help/Html2 DocProject/Help/html # Click-Once directory publish/ # Publish Web Output *.[Pp]ublish.xml *.azurePubxml # TODO: Comment the next line if you want to checkin your web deploy settings # but database connection strings (with potential passwords) will be unencrypted #*.pubxml *.publishproj # Microsoft Azure Web App publish settings. Comment the next line if you want to # checkin your Azure Web App publish settings, but sensitive information contained # in these scripts will be unencrypted PublishScripts/ # NuGet Packages *.nupkg # The packages folder can be ignored because of Package Restore **/packages/* # except build/, which is used as an MSBuild target. !**/packages/build/ # Uncomment if necessary however generally it will be regenerated when needed #!**/packages/repositories.config # NuGet v3's project.json files produces more ignoreable files *.nuget.props *.nuget.targets # Microsoft Azure Build Output csx/ *.build.csdef # Microsoft Azure Emulator ecf/ rcf/ # Windows Store app package directories and files AppPackages/ BundleArtifacts/ Package.StoreAssociation.xml _pkginfo.txt # Visual Studio cache files # files ending in .cache can be ignored *.[Cc]ache # but keep track of directories ending in .cache !*.[Cc]ache/ # Others ClientBin/ ~$* *~ *.dbmdl *.dbproj.schemaview *.jfm *.pfx *.publishsettings node_modules/ orleans.codegen.cs # Since there are multiple workflows, uncomment next line to ignore bower_components # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) #bower_components/ # RIA/Silverlight projects Generated_Code/ # Backup & report files from converting an old project file # to a newer Visual Studio version. Backup files are not needed, # because we have git ;-) _UpgradeReport_Files/ Backup*/ UpgradeLog*.XML UpgradeLog*.htm # SQL Server files *.mdf *.ldf # Business Intelligence projects *.rdl.data *.bim.layout *.bim_*.settings # Microsoft Fakes FakesAssemblies/ # GhostDoc plugin setting file *.GhostDoc.xml # Node.js Tools for Visual Studio .ntvs_analysis.dat # Visual Studio 6 build log *.plg # Visual Studio 6 workspace options file *.opt # Visual Studio LightSwitch build output **/*.HTMLClient/GeneratedArtifacts **/*.DesktopClient/GeneratedArtifacts **/*.DesktopClient/ModelManifest.xml **/*.Server/GeneratedArtifacts **/*.Server/ModelManifest.xml _Pvt_Extensions # Paket dependency manager .paket/paket.exe paket-files/ # FAKE - F# Make .fake/ # JetBrains Rider .idea/ *.sln.iml # CodeRush .cr/ # Python Tools for Visual Studio (PTVS) __pycache__/ *.pyc ================================================ FILE: src/windhawk/_typos.toml ================================================ [files] # Files to exclude from spell checking. extend-exclude = [ "/app/libraries/", "/app/rsrc.rc", "/engine/libraries/", "/shared/libraries/", ] [default.extend-words] # Words to ignore. wil = "wil" # Windows Implementation Library [default.extend-identifiers] # Identifiers to ignore - false positives. NIN_BALLOONUSERCLICK = "NIN_BALLOONUSERCLICK" NIN_KEYSELECT = "NIN_KEYSELECT" NIN_SELECT = "NIN_SELECT" PUNICODE_STR = "PUNICODE_STR" PUNICODE_STRING = "PUNICODE_STRING" PUNICODE_STRING64 = "PUNICODE_STRING64" MkTypLibCompatible = "MkTypLibCompatible" ================================================ FILE: src/windhawk/app/app.cpp ================================================ #include "stdafx.h" #include "functions.h" #include "logger.h" #include "main_window.h" #include "resource.h" #include "service.h" #include "storage_manager.h" #include "ui_control.h" CAppModule _Module; namespace { enum class Action { kDefault, kService, kServiceStart, kServiceStop, kRunUI, kRunUIAsAdmin, kRunUIInSafeMode, kServiceStartAndRunUI, kCheckForUpdates, kNewUpdatesFound, kAppSettingsChanged, kExit, kRestart, kRestartBg, }; void Initialize(); void Run(Action action); void RunDaemon(); void CheckForUpdates(); void NotifyNewUpdatesFound(); void NotifyAppSettingsChanged(); void ExitApp(bool wait, DWORD timeout); void RestartApp(DWORD timeout, bool trayOnly); void RestartAppBg(DWORD timeout); void EnableSafeMode(); void WaitForRunningProcessesToTerminate(DWORD timeout, bool windhawkBgOnly = false); void RunAsNewProcess(PCWSTR parameters); bool RunAsAdmin(PCWSTR parameters); bool PostCommandToPortableRunningDaemon( CMainWindow::PortableAppCommand command); void SetNamedEventForAllSessions(PCWSTR eventNamePrefix); bool SetNamedEvent(PCWSTR eventName); bool DoesParamExist(PCWSTR param); int GetIntParam(PCWSTR param); } // namespace int WINAPI wWinMain(_In_ HINSTANCE hInstance, _In_opt_ HINSTANCE hPrevInstance, _In_ LPWSTR lpCmdLine, _In_ int nShowCmd) { HRESULT hRes = ::CoInitialize(nullptr); ATLASSERT(SUCCEEDED(hRes)); hRes = _Module.Init(nullptr, hInstance); ATLASSERT(SUCCEEDED(hRes)); // Disable exception suppression in timer callbacks, as suggested by MSDN // and Bruce Dawson. // https://randomascii.wordpress.com/2012/07/05/when-even-crashing-doesnt-work/ BOOL insanity = FALSE; SetUserObjectInformation(GetCurrentProcess(), UOI_TIMERPROC_EXCEPTION_SUPPRESSION, &insanity, sizeof(insanity)); SetCurrentProcessExplicitAppUserModelID(L"RamenSoftware.Windhawk"); Action action = Action::kDefault; if (DoesParamExist(L"-service")) { action = Action::kService; } else if (DoesParamExist(L"-service-start")) { action = Action::kServiceStart; } else if (DoesParamExist(L"-service-stop")) { action = Action::kServiceStop; } else if (DoesParamExist(L"-run-ui")) { action = Action::kRunUI; } else if (DoesParamExist(L"-run-ui-as-admin")) { action = Action::kRunUIAsAdmin; } else if (DoesParamExist(L"-run-ui-in-safe-mode")) { action = Action::kRunUIInSafeMode; } else if (DoesParamExist(L"-service-start-and-run-ui")) { action = Action::kServiceStartAndRunUI; } else if (DoesParamExist(L"-check-for-updates")) { action = Action::kCheckForUpdates; } else if (DoesParamExist(L"-new-updates-found")) { action = Action::kNewUpdatesFound; } else if (DoesParamExist(L"-app-settings-changed")) { action = Action::kAppSettingsChanged; } else if (DoesParamExist(L"-exit")) { action = Action::kExit; } else if (DoesParamExist(L"-restart")) { action = Action::kRestart; } else if (DoesParamExist(L"-restart-bg")) { action = Action::kRestartBg; } HRESULT hr = S_OK; try { Initialize(); Run(action); } catch (const std::exception& e) { switch (action) { case Action::kDefault: case Action::kRunUI: case Action::kRunUIAsAdmin: case Action::kRunUIInSafeMode: case Action::kServiceStartAndRunUI: ::MessageBoxA(nullptr, e.what(), "Windhawk error", MB_ICONERROR); break; default: LOG(L"%S", e.what()); break; } hr = wil::ResultFromCaughtException(); } _Module.Term(); ::CoUninitialize(); return hr; } namespace { void Initialize() { // Make sure we can get an instance. // If not, this call will throw an exception. StorageManager::GetInstance(); } void Run(Action action) { switch (action) { case Action::kService: VERBOSE("Running service"); Service::Run(); break; case Action::kServiceStart: VERBOSE("Starting service"); Service::Start(); break; case Action::kServiceStop: VERBOSE("Stopping service"); Service::Stop(DoesParamExist(L"-also-no-autostart")); break; case Action::kRunUIAsAdmin: VERBOSE("Running UI as admin"); if (!Functions::IsRunAsAdmin()) { RunAsAdmin(L"-run-ui"); break; } [[fallthrough]]; case Action::kRunUI: VERBOSE("Running UI"); UIControl::RunUI(); break; case Action::kRunUIInSafeMode: VERBOSE("Running UI in safe mode"); ExitApp(/*wait=*/true, /*timeout=*/30000); EnableSafeMode(); UIControl::RunUI(); break; case Action::kServiceStartAndRunUI: VERBOSE("Starting service and running UI"); Service::Start(); UIControl::RunUI(); break; case Action::kCheckForUpdates: VERBOSE("Checking for updates"); CheckForUpdates(); break; case Action::kNewUpdatesFound: VERBOSE("Notifying about new updates found"); NotifyNewUpdatesFound(); break; case Action::kAppSettingsChanged: VERBOSE("Notifying about app settings changed"); NotifyAppSettingsChanged(); break; case Action::kExit: { VERBOSE("Exiting app"); DWORD timeout = GetIntParam(L"-timeout"); if (timeout == 0) { timeout = INFINITE; } ExitApp(DoesParamExist(L"-wait"), timeout); break; } case Action::kRestart: { VERBOSE("Restarting app"); DWORD timeout = GetIntParam(L"-timeout"); if (timeout == 0) { timeout = INFINITE; } RestartApp(timeout, DoesParamExist(L"-tray-only")); break; } case Action::kRestartBg: { VERBOSE("Restarting service/daemon"); DWORD timeout = GetIntParam(L"-timeout"); if (timeout == 0) { timeout = INFINITE; } RestartAppBg(timeout); break; } default: VERBOSE("Running Windhawk daemon"); RunDaemon(); break; } } void RunDaemon() { if (DoesParamExist(L"-wait")) { DWORD timeout = GetIntParam(L"-timeout"); if (timeout == 0) { timeout = INFINITE; } WaitForRunningProcessesToTerminate(timeout); } bool portable = StorageManager::GetInstance().IsPortable(); if (DoesParamExist(L"-safe-mode") || (GetSystemMetrics(SM_CLEANBOOT) != 0 && MessageBox(nullptr, Functions::LoadStrFromRsrc(IDS_SAFE_MODE_DETECTED_TEXT), Functions::LoadStrFromRsrc(IDS_SAFE_MODE_DETECTED_TITLE), MB_ICONWARNING | MB_YESNO) == IDYES)) { if (portable) { ExitApp(/*wait=*/true, /*timeout=*/30000); EnableSafeMode(); UIControl::RunUI(); } else { RunAsAdmin(L"-run-ui-in-safe-mode"); } return; } bool trayOnly = DoesParamExist(L"-tray-only"); if (!portable && !Service::IsRunning(/*waitIfStarting=*/true)) { // Start the service, which will in turn launch a new instance. if (!Functions::IsRunAsAdmin()) { RunAsAdmin(trayOnly ? L"-service-start" : L"-service-start-and-run-ui"); } else { Service::Start(); if (!trayOnly) { UIControl::RunUI(); } } return; } wil::unique_mutex_nothrow mutex( ::CreateMutex(nullptr, TRUE, L"WindhawkDaemon")); THROW_LAST_ERROR_IF_NULL(mutex); if (GetLastError() == ERROR_ALREADY_EXISTS) { if (!trayOnly) { UIControl::RunUIOrBringToFront( nullptr, !portable && !Functions::IsRunAsAdmin()); } return; } auto mutexLock = mutex.ReleaseMutex_scope_exit(); if (portable) { if (!Functions::SetDebugPrivilege(TRUE)) { LOG(L"SetDebugPrivilege failed with error %u", GetLastError()); } } // We need a custom CMessageLoop class to be able to wait // for objects in OnIdle correctly. class CMessageLoopAlwaysRunOnIdle : public CMessageLoop { public: BOOL OnIdle(int nIdleCount) override { CMessageLoop::OnIdle(nIdleCount); return TRUE; // continue } }; CMessageLoopAlwaysRunOnIdle loop; _Module.AddMessageLoop(&loop); CMainWindow wnd(trayOnly, portable); wnd.Create(nullptr); // wnd.ShowWindow(SW_SHOW); loop.Run(); _Module.RemoveMessageLoop(); } void CheckForUpdates() { bool portable = StorageManager::GetInstance().IsPortable(); UpdateChecker m_updateChecker(portable ? UpdateChecker::kFlagPortable : 0, nullptr); UpdateChecker::Result result = m_updateChecker.HandleResponse(); THROW_IF_FAILED(result.hrError); if (result.updateStatus.newUpdatesFound) { NotifyNewUpdatesFound(); } } void NotifyNewUpdatesFound() { SetNamedEventForAllSessions( L"Global\\WindhawkNewUpdatesFoundEvent-daemon-session="); } void NotifyAppSettingsChanged() { if (StorageManager::GetInstance().IsPortable()) { SetNamedEvent(L"WindhawkAppSettingsChangedEvent-daemon"); return; } SetNamedEventForAllSessions( L"Global\\WindhawkAppSettingsChangedEvent-daemon-session="); } void ExitApp(bool wait, DWORD timeout) { if (StorageManager::GetInstance().IsPortable()) { PostCommandToPortableRunningDaemon( CMainWindow::PortableAppCommand::kExit); } else { Service::Stop(false); } if (wait) { WaitForRunningProcessesToTerminate(timeout); } } void RestartApp(DWORD timeout, bool trayOnly) { bool portable = StorageManager::GetInstance().IsPortable(); if (portable) { PostCommandToPortableRunningDaemon( CMainWindow::PortableAppCommand::kExit); } else { Service::Stop(false); } WaitForRunningProcessesToTerminate(timeout); if (portable) { RunAsNewProcess(trayOnly ? L"-tray-only" : nullptr); } else { Service::Start(); if (!trayOnly) { UIControl::RunUI(); } } } void RestartAppBg(DWORD timeout) { auto uiWindows = UIControl::GetOpenUIWindows(); // Disable UI windows to prevent them from being closed by the daemon. Not a // perfect solution but it works. for (HWND hWnd : uiWindows) { EnableWindow(hWnd, false); } bool portable = StorageManager::GetInstance().IsPortable(); if (portable) { PostCommandToPortableRunningDaemon( CMainWindow::PortableAppCommand::kExit); } else { Service::Stop(false); } WaitForRunningProcessesToTerminate(timeout, /*windhawkBgOnly=*/true); for (HWND hWnd : uiWindows) { EnableWindow(hWnd, true); } if (portable) { RunAsNewProcess(L"-tray-only"); } else { Service::Start(); } } void EnableSafeMode() { StorageManager::GetInstance() .GetAppConfig(L"Settings", true) ->SetInt(L"SafeMode", 1); } void WaitForRunningProcessesToTerminate(DWORD timeout, bool windhawkBgOnly) { DWORD startTickCount = GetTickCount(); HRESULT hr; // Use QueryFullProcessImageName instead of GetModuleFileName because the // latter can return a path with a different case depending on how the // process was launched. QueryFullProcessImageName seems to be consistent // in this regard. std::filesystem::path modulePath = wil::QueryFullProcessImageName(); auto folderPath = modulePath.parent_path(); while (true) { HANDLE handlesRawArray[MAXIMUM_WAIT_OBJECTS]; wil::unique_process_handle handles[MAXIMUM_WAIT_OBJECTS]; DWORD handlesCount = 0; wil::unique_tool_help_snapshot snapshot( CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0)); THROW_LAST_ERROR_IF(!snapshot); PROCESSENTRY32 pe; pe.dwSize = sizeof(PROCESSENTRY32); THROW_IF_WIN32_BOOL_FALSE(Process32First(snapshot.get(), &pe)); do { if (pe.th32ProcessID == 0) { // Skipping System Idle Process. continue; } if (pe.th32ProcessID == GetCurrentProcessId()) { // Skipping current process. continue; } if (windhawkBgOnly) { if (_wcsicmp(pe.szExeFile, L"windhawk.exe") != 0) { continue; } } else { if (_wcsicmp(pe.szExeFile, L"uninstall.exe") == 0) { // Skipping uninstaller, which may be running but is not // part of the app. continue; } } wil::unique_process_handle process( OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION | SYNCHRONIZE, FALSE, pe.th32ProcessID)); if (process) { std::wstring fullProcessImageName; hr = wil::QueryFullProcessImageName( process.get(), 0, fullProcessImageName); if (SUCCEEDED(hr)) { // Is path inside folder: // https://stackoverflow.com/a/40441240 if (fullProcessImageName.rfind(folderPath, 0) == 0) { VERBOSE(L"Waiting for %u (%s)", pe.th32ProcessID, pe.szExeFile); handlesRawArray[handlesCount] = process.get(); handles[handlesCount] = std::move(process); handlesCount++; } } else { VERBOSE( L"QueryFullProcessImageName for %u (%s) failed with " L"error 0x%08X", pe.th32ProcessID, pe.szExeFile, hr); } } else { VERBOSE(L"OpenProcess for %u (%s) failed with error %u", pe.th32ProcessID, pe.szExeFile, GetLastError()); } } while (handlesCount < _countof(handles) && Process32Next(snapshot.get(), &pe)); if (handlesCount < _countof(handles)) { THROW_LAST_ERROR_IF(GetLastError() != ERROR_NO_MORE_FILES); } if (handlesCount > 0) { DWORD iterationTimeout = timeout; if (iterationTimeout != INFINITE) { DWORD timePassed = GetTickCount() - startTickCount; if (timePassed >= iterationTimeout) { THROW_WIN32(ERROR_TIMEOUT); } iterationTimeout -= timePassed; } VERBOSE(L"Waiting for %u processes", handlesCount); switch (WaitForMultipleObjects(handlesCount, handlesRawArray, TRUE, iterationTimeout)) { case WAIT_TIMEOUT: THROW_WIN32(ERROR_TIMEOUT); case WAIT_FAILED: THROW_LAST_ERROR(); } } if (handlesCount < _countof(handles)) { break; } } } void RunAsNewProcess(PCWSTR parameters) { auto modulePath = wil::GetModuleFileName(); std::wstring commandLine = L"\"" + modulePath + L"\""; if (parameters && *parameters != L'\0') { commandLine += L' '; commandLine += parameters; } STARTUPINFO si = {sizeof(STARTUPINFO)}; wil::unique_process_information process; THROW_IF_WIN32_BOOL_FALSE(CreateProcess( modulePath.c_str(), commandLine.data(), nullptr, nullptr, FALSE, NORMAL_PRIORITY_CLASS, nullptr, nullptr, &si, &process)); } bool RunAsAdmin(PCWSTR parameters) { auto modulePath = wil::GetModuleFileName(); if ((int)(UINT_PTR)ShellExecute(nullptr, L"runas", modulePath.c_str(), parameters, nullptr, SW_SHOWNORMAL) > 32) { return true; } THROW_LAST_ERROR_IF(GetLastError() != ERROR_CANCELLED); return false; } bool PostCommandToPortableRunningDaemon( CMainWindow::PortableAppCommand command) { CWindow hDaemonWnd(FindWindow(L"WindhawkDaemon", nullptr)); if (!hDaemonWnd) { return false; } ::AllowSetForegroundWindow(hDaemonWnd.GetWindowProcessID()); THROW_IF_WIN32_BOOL_FALSE(hDaemonWnd.PostMessage( CMainWindow::UWM_PORTABLE_APP_COMMAND, (WPARAM)command)); return true; } void SetNamedEventForAllSessions(PCWSTR eventNamePrefix) { WTS_SESSION_INFO* sessionInfo; DWORD dwCount; THROW_IF_WIN32_BOOL_FALSE(WTSEnumerateSessions(WTS_CURRENT_SERVER_HANDLE, 0, 1, &sessionInfo, &dwCount)); wil::unique_wtsmem_ptr scopedSessionInfo(sessionInfo); for (DWORD i = 0; i < dwCount; i++) { WCHAR* pszUserName; DWORD dwUserNameLen; THROW_IF_WIN32_BOOL_FALSE(WTSQuerySessionInformation( WTS_CURRENT_SERVER_HANDLE, sessionInfo[i].SessionId, WTSUserName, &pszUserName, &dwUserNameLen)); wil::unique_wtsmem_ptr scopedUserName(pszUserName); if (*pszUserName != L'\0') { auto eventName = eventNamePrefix + std::to_wstring(sessionInfo[i].SessionId); SetNamedEvent(eventName.c_str()); } } } bool SetNamedEvent(PCWSTR eventName) { wil::unique_event namedEvent( OpenEvent(EVENT_MODIFY_STATE, FALSE, eventName)); if (!namedEvent) { THROW_LAST_ERROR_IF(GetLastError() != ERROR_FILE_NOT_FOUND); return false; } namedEvent.SetEvent(); return true; } bool DoesParamExist(PCWSTR param) { for (int i = 1; i < __argc; i++) { if (_wcsicmp(__wargv[i], param) == 0) { return true; } } return false; } int GetIntParam(PCWSTR param) { for (int i = 1; i < __argc - 1; i++) { if (_wcsicmp(__wargv[i], param) == 0) { return _wtoi(__wargv[i + 1]); } } return 0; } } // namespace ================================================ FILE: src/windhawk/app/app.vcxproj ================================================ Debug ARM64 Debug Win32 Release ARM64 Release Win32 Debug x64 Release x64 16.0 {0F288B9E-2D98-4728-8C40-CD5C1D1A95FD} 10.0 Application true v143 Unicode Application false v143 Unicode Application true v143 Unicode Application true v143 Unicode Application false v143 Unicode Application false v143 Unicode true $(ProjectDir)libraries;$(ProjectDir)..\shared;$(ProjectDir)..\shared\libraries;$(IncludePath) windhawk $(ProjectDir)..\shared\libraries;$(LibraryPath) true $(ProjectDir)libraries;$(ProjectDir)..\shared;$(ProjectDir)..\shared\libraries;$(IncludePath) windhawk $(ProjectDir)..\shared\libraries;$(LibraryPath) true $(ProjectDir)libraries;$(ProjectDir)..\shared;$(ProjectDir)..\shared\libraries;$(IncludePath) windhawk $(ProjectDir)..\shared\libraries;$(LibraryPath) false $(ProjectDir)libraries;$(ProjectDir)..\shared;$(ProjectDir)..\shared\libraries;$(IncludePath) windhawk $(ProjectDir)..\shared\libraries;$(LibraryPath) false $(ProjectDir)libraries;$(ProjectDir)..\shared;$(ProjectDir)..\shared\libraries;$(IncludePath) windhawk $(ProjectDir)..\shared\libraries;$(LibraryPath) false $(ProjectDir)libraries;$(ProjectDir)..\shared;$(ProjectDir)..\shared\libraries;$(IncludePath) windhawk $(ProjectDir)..\shared\libraries;$(LibraryPath) Use Level3 MultiThreadedDebug EditAndContinue EnableFastChecks Disabled WIN32;_WINDOWS;STRICT;_DEBUG;%(PreprocessorDefinitions) stdcpplatest /d1trimfile:"$(SolutionDir)\" %(AdditionalOptions) Windows true type=%27Win32%27 name=%27Microsoft.Windows.Common-Controls%27 version=%276.0.0.0%27 processorArchitecture=%27*%27 publicKeyToken=%276595b64144ccf1df%27 language=%27*%27;%(AdditionalManifestDependencies) taskschd.lib;userenv.lib;wevtapi.lib;wtsapi32.lib;%(AdditionalDependencies) 0x0409 $(IntDir);%(AdditionalIncludeDirectories) _DEBUG;%(PreprocessorDefinitions) true false Win32 _DEBUG;%(PreprocessorDefinitions) app.h app_i.c app_p.c true $(IntDir)/app.tlb rsrc\compatibility.manifest %(AdditionalManifestFiles) Use Level3 MultiThreadedDebug EditAndContinue EnableFastChecks Disabled _WINDOWS;STRICT;_DEBUG;%(PreprocessorDefinitions) stdcpplatest /d1trimfile:"$(SolutionDir)\" %(AdditionalOptions) Windows true type=%27Win32%27 name=%27Microsoft.Windows.Common-Controls%27 version=%276.0.0.0%27 processorArchitecture=%27*%27 publicKeyToken=%276595b64144ccf1df%27 language=%27*%27;%(AdditionalManifestDependencies) taskschd.lib;userenv.lib;wevtapi.lib;wtsapi32.lib;%(AdditionalDependencies) 0x0409 $(IntDir);%(AdditionalIncludeDirectories) _DEBUG;%(PreprocessorDefinitions) true false _DEBUG;%(PreprocessorDefinitions) app.h app_i.c app_p.c true $(IntDir)/app.tlb rsrc\compatibility.manifest %(AdditionalManifestFiles) Use Level3 MultiThreadedDebug EditAndContinue EnableFastChecks Disabled _WINDOWS;STRICT;_DEBUG;%(PreprocessorDefinitions) stdcpplatest /d1trimfile:"$(SolutionDir)\" %(AdditionalOptions) Windows true type=%27Win32%27 name=%27Microsoft.Windows.Common-Controls%27 version=%276.0.0.0%27 processorArchitecture=%27*%27 publicKeyToken=%276595b64144ccf1df%27 language=%27*%27;%(AdditionalManifestDependencies) taskschd.lib;userenv.lib;wevtapi.lib;wtsapi32.lib;%(AdditionalDependencies) 0x0409 $(IntDir);%(AdditionalIncludeDirectories) _DEBUG;%(PreprocessorDefinitions) true false _DEBUG;%(PreprocessorDefinitions) app.h app_i.c app_p.c true $(IntDir)/app.tlb rsrc\compatibility.manifest %(AdditionalManifestFiles) Use Level3 MultiThreaded Sync WIN32;_WINDOWS;STRICT;NDEBUG;%(PreprocessorDefinitions) stdcpplatest /d1trimfile:"$(SolutionDir)\" %(AdditionalOptions) Windows type=%27Win32%27 name=%27Microsoft.Windows.Common-Controls%27 version=%276.0.0.0%27 processorArchitecture=%27*%27 publicKeyToken=%276595b64144ccf1df%27 language=%27*%27;%(AdditionalManifestDependencies) taskschd.lib;userenv.lib;wevtapi.lib;wtsapi32.lib;%(AdditionalDependencies) false true 0x0409 $(IntDir);%(AdditionalIncludeDirectories) NDEBUG;%(PreprocessorDefinitions) true false Win32 NDEBUG;%(PreprocessorDefinitions) app.h app_i.c app_p.c true $(IntDir)/app.tlb rsrc\compatibility.manifest %(AdditionalManifestFiles) Use Level3 MultiThreaded Sync _WINDOWS;STRICT;NDEBUG;%(PreprocessorDefinitions) stdcpplatest /d1trimfile:"$(SolutionDir)\" %(AdditionalOptions) Windows type=%27Win32%27 name=%27Microsoft.Windows.Common-Controls%27 version=%276.0.0.0%27 processorArchitecture=%27*%27 publicKeyToken=%276595b64144ccf1df%27 language=%27*%27;%(AdditionalManifestDependencies) taskschd.lib;userenv.lib;wevtapi.lib;wtsapi32.lib;%(AdditionalDependencies) false true 0x0409 $(IntDir);%(AdditionalIncludeDirectories) NDEBUG;%(PreprocessorDefinitions) true false NDEBUG;%(PreprocessorDefinitions) app.h app_i.c app_p.c true $(IntDir)/app.tlb rsrc\compatibility.manifest %(AdditionalManifestFiles) Use Level3 MultiThreaded Sync _WINDOWS;STRICT;NDEBUG;%(PreprocessorDefinitions) stdcpplatest /d1trimfile:"$(SolutionDir)\" %(AdditionalOptions) Windows type=%27Win32%27 name=%27Microsoft.Windows.Common-Controls%27 version=%276.0.0.0%27 processorArchitecture=%27*%27 publicKeyToken=%276595b64144ccf1df%27 language=%27*%27;%(AdditionalManifestDependencies) taskschd.lib;userenv.lib;wevtapi.lib;wtsapi32.lib;%(AdditionalDependencies) false true 0x0409 $(IntDir);%(AdditionalIncludeDirectories) NDEBUG;%(PreprocessorDefinitions) true false NDEBUG;%(PreprocessorDefinitions) app.h app_i.c app_p.c true $(IntDir)/app.tlb rsrc\compatibility.manifest %(AdditionalManifestFiles) Create Create Create Create Create Create This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. ================================================ FILE: src/windhawk/app/app.vcxproj.filters ================================================  {22b472b6-8e00-43cc-9d05-145bf61d6fa8} cpp;c;cxx;def;odl;idl;hpj;bat;asm {aff9dafa-81f2-49e9-8577-a319932c9c56} h;hpp;hxx;hm;inl;inc {c9908b8c-b610-454b-9f8a-fd6afc7f6daa} rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;jpg;jpeg;jpe;manifest Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Resource Files Resource Files Resource Files ================================================ FILE: src/windhawk/app/engine_control.cpp ================================================ #include "stdafx.h" #include "engine_control.h" #include "storage_manager.h" EngineControl::EngineControl() { auto engineLibraryPath = StorageManager::GetInstance().GetEnginePath() / L"windhawk.dll"; engineModule.reset(LoadLibrary(engineLibraryPath.c_str())); THROW_LAST_ERROR_IF_NULL_MSG( engineModule, "Failed to load engine library: %ls, make sure that the engine path " "that's specified in windhawk.ini is correct", engineLibraryPath.c_str()); pGlobalHookSessionStart = reinterpret_cast( GetProcAddress(engineModule.get(), "GlobalHookSessionStart")); THROW_LAST_ERROR_IF_NULL(pGlobalHookSessionStart); pGlobalHookSessionHandleNewProcesses = reinterpret_cast( GetProcAddress(engineModule.get(), "GlobalHookSessionHandleNewProcesses")); THROW_LAST_ERROR_IF_NULL(pGlobalHookSessionHandleNewProcesses); pGlobalHookSessionEnd = reinterpret_cast( GetProcAddress(engineModule.get(), "GlobalHookSessionEnd")); THROW_LAST_ERROR_IF_NULL(pGlobalHookSessionEnd); hGlobalHookSession = pGlobalHookSessionStart(); if (!hGlobalHookSession) { throw std::runtime_error("Failed to start the global hooking session"); } } EngineControl::~EngineControl() { pGlobalHookSessionEnd(hGlobalHookSession); } BOOL EngineControl::HandleNewProcesses() { return pGlobalHookSessionHandleNewProcesses(hGlobalHookSession); } ================================================ FILE: src/windhawk/app/engine_control.h ================================================ #pragma once class EngineControl { public: EngineControl(); ~EngineControl(); EngineControl(const EngineControl&) = delete; EngineControl(EngineControl&&) = delete; EngineControl& operator=(const EngineControl&) = delete; EngineControl& operator=(EngineControl&&) = delete; BOOL HandleNewProcesses(); private: using GLOBAL_HOOK_SESSION_START = HANDLE (*)(); using GLOBAL_HOOK_SESSION_HANDLE_NEW_PROCESSES = BOOL (*)(HANDLE hSession); using GLOBAL_HOOK_SESSION_END = BOOL (*)(HANDLE hSession); wil::unique_hmodule engineModule; GLOBAL_HOOK_SESSION_START pGlobalHookSessionStart; GLOBAL_HOOK_SESSION_HANDLE_NEW_PROCESSES pGlobalHookSessionHandleNewProcesses; GLOBAL_HOOK_SESSION_END pGlobalHookSessionEnd; HANDLE hGlobalHookSession; }; ================================================ FILE: src/windhawk/app/event_viewer_crash_monitor.cpp ================================================ #include "stdafx.h" #include "event_viewer_crash_monitor.h" #include "logger.h" // Based on: // https://learn.microsoft.com/en-us/windows/win32/wes/subscribing-to-events#push-subscriptions // https://learn.microsoft.com/en-us/windows/win32/wes/rendering-events EventViewerCrashMonitor::EventViewerCrashMonitor( std::wstring_view targetAppPath) : m_targetAppPath(targetAppPath) { // Get a handle to an event object that the subscription will signal when // events become available that match your query criteria. m_event.reset(CreateEvent(nullptr, TRUE, TRUE, nullptr)); THROW_LAST_ERROR_IF_NULL(m_event); PCWSTR pwsPath = L"Application"; PCWSTR pwsQuery = L"Event/System[Level=2] and Event/System[EventID=1000]"; // Subscribe to events. m_subscription.reset(EvtSubscribe(nullptr, m_event.get(), pwsPath, pwsQuery, nullptr, nullptr, nullptr, EvtSubscribeToFutureEvents)); THROW_LAST_ERROR_IF_NULL(m_subscription); LCMapStringEx( LOCALE_NAME_USER_DEFAULT, LCMAP_UPPERCASE, &m_targetAppPath[0], wil::safe_cast(m_targetAppPath.length()), &m_targetAppPath[0], wil::safe_cast(m_targetAppPath.length()), nullptr, nullptr, 0); } HANDLE EventViewerCrashMonitor::GetEventHandle() const { return m_event.get(); } int EventViewerCrashMonitor::GetAmountOfNewEvents() { int count = 0; while (true) { // Get a block of events from the result set. wil::unique_evt_handle eventHandle; DWORD dwReturned; if (!EvtNext(m_subscription.get(), 1, &eventHandle, INFINITE, 0, &dwReturned)) { THROW_LAST_ERROR_IF(GetLastError() != ERROR_NO_MORE_ITEMS); break; } try { if (DoesEventMatch(eventHandle.get())) { count++; } } catch (const std::exception& e) { LOG(L"%S", e.what()); } } ResetEvent(m_event.get()); return count; } bool EventViewerCrashMonitor::DoesEventMatch(EVT_HANDLE eventHandle) { // Identify the components of the event that you want to render. In this // case, render the user section of the event. wil::unique_evt_handle context( EvtCreateRenderContext(0, nullptr, EvtRenderContextUser)); THROW_LAST_ERROR_IF_NULL(context); // When you render the user data or system section of the event, you must // specify the EvtRenderEventValues flag. The function returns an array of // variant values for each element in the user data or system section of the // event. For user data or event data, the values are returned in the same // order as the elements are defined in the event. For system data, the // values are returned in the order defined in the EVT_SYSTEM_PROPERTY_ID // enumeration. DWORD dwBufferSize = 0; DWORD dwBufferUsed = 0; DWORD dwPropertyCount = 0; if (EvtRender(context.get(), eventHandle, EvtRenderEventValues, dwBufferSize, nullptr, &dwBufferUsed, &dwPropertyCount)) { throw std::logic_error("Unexpected result from EvtRender"); } THROW_LAST_ERROR_IF(GetLastError() != ERROR_INSUFFICIENT_BUFFER); dwBufferSize = dwBufferUsed; auto renderedValuesBuffer = std::make_unique(dwBufferSize); THROW_IF_WIN32_BOOL_FALSE(EvtRender( context.get(), eventHandle, EvtRenderEventValues, dwBufferSize, renderedValuesBuffer.get(), &dwBufferUsed, &dwPropertyCount)); if (dwPropertyCount < 11) { LOG(L"Not enough property values (%u)", dwPropertyCount); return false; } auto renderedValues = reinterpret_cast(renderedValuesBuffer.get()); if (renderedValues[10].Type != EvtVarTypeString) { LOG(L"Unexpected property value type (%u)", renderedValues[10].Type); return false; } std::wstring appPath{renderedValues[10].StringVal}; LCMapStringEx(LOCALE_NAME_USER_DEFAULT, LCMAP_UPPERCASE, &appPath[0], wil::safe_cast(appPath.length()), &appPath[0], wil::safe_cast(appPath.length()), nullptr, nullptr, 0); if (appPath != m_targetAppPath) { return false; } DWORD processId = renderedValues[8].Type == EvtVarTypeHexInt32 ? renderedValues[8].Int32Val : 0; DWORD64 processCreationTime = renderedValues[9].Type == EvtVarTypeHexInt64 ? renderedValues[9].Int64Val : 0; if (processId && processCreationTime && processId == m_lastProcessId && processCreationTime == m_lastProcessCreationTime) { return false; } m_lastProcessId = processId; m_lastProcessCreationTime = processCreationTime; return true; } ================================================ FILE: src/windhawk/app/event_viewer_crash_monitor.h ================================================ #pragma once class EventViewerCrashMonitor { public: EventViewerCrashMonitor(std::wstring_view targetAppPath); HANDLE GetEventHandle() const; int GetAmountOfNewEvents(); private: bool DoesEventMatch(EVT_HANDLE eventHandle); std::wstring m_targetAppPath; wil::unique_event m_event; wil::unique_evt_handle m_subscription; DWORD m_lastProcessId = 0; DWORD64 m_lastProcessCreationTime = 0; }; ================================================ FILE: src/windhawk/app/functions.cpp ================================================ #include "stdafx.h" #include "functions.h" namespace Functions { namespace { typedef struct _UNICODE_STRING { USHORT Length; USHORT MaximumLength; _Field_size_bytes_part_opt_(MaximumLength, Length) PWCH Buffer; } UNICODE_STRING, *PUNICODE_STRING; // wdm typedef struct _COUNTED_REASON_CONTEXT { ULONG Version; ULONG Flags; union { struct { UNICODE_STRING ResourceFileName; USHORT ResourceReasonId; ULONG StringCount; _Field_size_(StringCount) PUNICODE_STRING ReasonStrings; }; UNICODE_STRING SimpleString; }; } COUNTED_REASON_CONTEXT, *PCOUNTED_REASON_CONTEXT; #ifndef _WIN64 #pragma pack(push, 8) typedef struct _UNICODE_STRING64 { USHORT Length; USHORT MaximumLength; _Field_size_bytes_part_opt_(MaximumLength, Length) DWORD64 Buffer; } UNICODE_STRING64, *PUNICODE_STRING64; typedef struct _COUNTED_REASON_CONTEXT64 { ULONG Version; ULONG Flags; union { struct { UNICODE_STRING64 ResourceFileName; USHORT ResourceReasonId; ULONG StringCount; _Field_size_(StringCount) PUNICODE_STRING64 ReasonStrings; }; UNICODE_STRING64 SimpleString; }; } COUNTED_REASON_CONTEXT64, *PCOUNTED_REASON_CONTEXT64; #pragma pack(pop) #endif // POWER_REQUEST_TYPE typedef enum _POWER_REQUEST_TYPE_INTERNAL { PowerRequestDisplayRequiredInternal, PowerRequestSystemRequiredInternal, PowerRequestAwayModeRequiredInternal, PowerRequestExecutionRequiredInternal, // Windows 8+ PowerRequestPerfBoostRequiredInternal, // Windows 8+ PowerRequestActiveLockScreenInternal, // Windows 10 RS1+ (reserved on // Windows 8) // Values 6 and 7 are reserved for Windows 8 only PowerRequestInternalInvalid, PowerRequestInternalUnknown, PowerRequestFullScreenVideoRequired // Windows 8 only } POWER_REQUEST_TYPE_INTERNAL; typedef struct _POWER_REQUEST_ACTION { HANDLE PowerRequestHandle; POWER_REQUEST_TYPE_INTERNAL RequestType; BOOLEAN SetAction; HANDLE ProcessHandle; // Windows 8+ and only for requests created via // PlmPowerRequestCreate } POWER_REQUEST_ACTION, *PPOWER_REQUEST_ACTION; #ifndef NT_SUCCESS #define NT_SUCCESS(Status) (((NTSTATUS)(Status)) >= 0) #endif #define POWER_REQUEST_CONTEXT_NOT_SPECIFIED DIAGNOSTIC_REASON_NOT_SPECIFIED NTSTATUS NtPowerInformation(_In_ POWER_INFORMATION_LEVEL InformationLevel, _In_reads_bytes_opt_(InputBufferLength) PVOID InputBuffer, _In_ ULONG InputBufferLength, _Out_writes_bytes_opt_(OutputBufferLength) PVOID OutputBuffer, _In_ ULONG OutputBufferLength) { using NtPowerInformation_t = NTSTATUS(WINAPI*)( _In_ POWER_INFORMATION_LEVEL InformationLevel, _In_reads_bytes_opt_(InputBufferLength) PVOID InputBuffer, _In_ ULONG InputBufferLength, _Out_writes_bytes_opt_(OutputBufferLength) PVOID OutputBuffer, _In_ ULONG OutputBufferLength); static NtPowerInformation_t pNtPowerInformation = []() { HMODULE hNtdll = GetModuleHandle(L"ntdll.dll"); if (hNtdll) { return (NtPowerInformation_t)GetProcAddress(hNtdll, "NtPowerInformation"); } return (NtPowerInformation_t) nullptr; }(); if (!pNtPowerInformation) { return STATUS_UNSUCCESSFUL; } return pNtPowerInformation(InformationLevel, InputBuffer, InputBufferLength, OutputBuffer, OutputBufferLength); } } // namespace // SetPrivilege enables/disables process token privilege. // https://docs.microsoft.com/en-us/windows-hardware/drivers/debugger/debug-privilege BOOL SetPrivilege(HANDLE hToken, LPCTSTR lpszPrivilege, BOOL bEnablePrivilege) { LUID luid; BOOL bRet = FALSE; if (LookupPrivilegeValue(nullptr, lpszPrivilege, &luid)) { TOKEN_PRIVILEGES tp; tp.PrivilegeCount = 1; tp.Privileges[0].Luid = luid; tp.Privileges[0].Attributes = (bEnablePrivilege) ? SE_PRIVILEGE_ENABLED : 0; // Enable the privilege or disable all privileges. if (AdjustTokenPrivileges(hToken, FALSE, &tp, 0, nullptr, nullptr)) { // Check to see if you have proper access. // You may get "ERROR_NOT_ALL_ASSIGNED". bRet = (GetLastError() == ERROR_SUCCESS); } } return bRet; } BOOL SetDebugPrivilege(BOOL bEnablePrivilege) { wil::unique_handle token; if (!OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES, &token)) { return FALSE; } return SetPrivilege(token.get(), SE_DEBUG_NAME, bEnablePrivilege); } HANDLE CreateEventForMediumIntegrity(PCWSTR eventName, BOOL manualReset) { // Allow only EVENT_MODIFY_STATE (0x0002), only for medium integrity. PCWSTR pszStringSecurityDescriptor = L"D:(A;;0x0002;;;WD)S:(ML;;NW;;;ME)"; wil::unique_hlocal secDesc; if (!ConvertStringSecurityDescriptorToSecurityDescriptor( pszStringSecurityDescriptor, SDDL_REVISION_1, &secDesc, nullptr)) { return nullptr; } SECURITY_ATTRIBUTES secAttr = {sizeof(SECURITY_ATTRIBUTES)}; secAttr.lpSecurityDescriptor = secDesc.get(); secAttr.bInheritHandle = FALSE; return CreateEvent(&secAttr, manualReset, FALSE, eventName); } // // FUNCTION: IsRunAsAdmin() // // PURPOSE: The function checks whether the current process is run as // administrator. In other words, it dictates whether the primary access // token of the process belongs to user account that is a member of the // local Administrators group and it is elevated. // // RETURN VALUE: Returns TRUE if the primary access token of the process // belongs to user account that is a member of the local Administrators // group and it is elevated. Returns FALSE if the token does not. Returns // FALSE on failure. To get extended error information, call GetLastError. // BOOL IsRunAsAdmin() { BOOL fIsRunAsAdmin = FALSE; DWORD dwError = ERROR_SUCCESS; PSID pAdministratorsGroup = nullptr; SID_IDENTIFIER_AUTHORITY NtAuthority = SECURITY_NT_AUTHORITY; // Allocate and initialize a SID of the administrators group. if (AllocateAndInitializeSid(&NtAuthority, 2, SECURITY_BUILTIN_DOMAIN_RID, DOMAIN_ALIAS_RID_ADMINS, 0, 0, 0, 0, 0, 0, &pAdministratorsGroup)) { // Determine whether the SID of administrators group is enabled in // the primary access token of the process. if (!CheckTokenMembership(nullptr, pAdministratorsGroup, &fIsRunAsAdmin)) { dwError = GetLastError(); } FreeSid(pAdministratorsGroup); if (dwError != ERROR_SUCCESS) { SetLastError(dwError); } } return fIsRunAsAdmin; } PCWSTR LoadStrFromRsrc(UINT uStrId) { PCWSTR pStr; if (!LoadString(nullptr, uStrId, (WCHAR*)&pStr, 0)) { pStr = L"(Could not load resource)"; } return pStr; } std::vector SplitString(std::wstring_view s, WCHAR delim) { // https://stackoverflow.com/a/48403210 auto view = s | std::views::split(delim) | std::views::transform([](auto&& rng) { return std::wstring_view(rng.data(), rng.size()); }); return std::vector(view.begin(), view.end()); } std::vector SplitStringToViews(std::wstring_view s, WCHAR delim) { // https://stackoverflow.com/a/48403210 auto view = s | std::views::split(delim) | std::views::transform([](auto&& rng) { return std::wstring_view(rng.data(), rng.size()); }); return std::vector(view.begin(), view.end()); } // https://stackoverflow.com/a/29752943 std::wstring ReplaceAll(std::wstring_view source, std::wstring_view from, std::wstring_view to, bool ignoreCase) { auto findString = [ignoreCase](std::wstring_view haystack, std::wstring_view needle, size_t pos) -> size_t { if (!ignoreCase) { return haystack.find(needle, pos); } auto it = std::search( haystack.begin() + pos, haystack.end(), needle.begin(), needle.end(), [](WCHAR ch1, WCHAR ch2) { LCMapStringEx(LOCALE_NAME_USER_DEFAULT, LCMAP_UPPERCASE, &ch1, 1, &ch1, 1, nullptr, nullptr, 0); LCMapStringEx(LOCALE_NAME_USER_DEFAULT, LCMAP_UPPERCASE, &ch2, 1, &ch2, 1, nullptr, nullptr, 0); return ch1 == ch2; }); if (it == haystack.end()) { return haystack.npos; } return std::distance(haystack.begin(), it); }; std::wstring newString; size_t lastPos = 0; size_t findPos; while ((findPos = findString(source, from, lastPos)) != source.npos) { newString.append(source, lastPos, findPos - lastPos); newString += to; lastPos = findPos + from.length(); } // Care for the rest after last occurrence. newString += source.substr(lastPos); return newString; } UINT GetDpiForWindowWithFallback(HWND hWnd) { using GetDpiForWindow_t = UINT(WINAPI*)(HWND hwnd); static GetDpiForWindow_t pGetDpiForWindow = []() { HMODULE hUser32 = GetModuleHandle(L"user32.dll"); if (hUser32) { return (GetDpiForWindow_t)GetProcAddress(hUser32, "GetDpiForWindow"); } return (GetDpiForWindow_t) nullptr; }(); int iDpi = 96; if (pGetDpiForWindow) { iDpi = pGetDpiForWindow(hWnd); } else { CDC hdc = ::GetDC(nullptr); if (hdc) { iDpi = hdc.GetDeviceCaps(LOGPIXELSX); } } return iDpi; } int GetSystemMetricsForDpiWithFallback(int nIndex, UINT dpi) { using GetSystemMetricsForDpi_t = int(WINAPI*)(int nIndex, UINT dpi); static GetSystemMetricsForDpi_t pGetSystemMetricsForDpi = []() { HMODULE hUser32 = GetModuleHandle(L"user32.dll"); if (hUser32) { return (GetSystemMetricsForDpi_t)GetProcAddress( hUser32, "GetSystemMetricsForDpi"); } return (GetSystemMetricsForDpi_t) nullptr; }(); if (pGetSystemMetricsForDpi) { return pGetSystemMetricsForDpi(nIndex, dpi); } else { return GetSystemMetrics(nIndex); } } int GetSystemMetricsForWindow(HWND hWnd, int nIndex) { return GetSystemMetricsForDpiWithFallback( nIndex, GetDpiForWindowWithFallback(hWnd)); } HRESULT SetThreadDescriptionIfAvailable(HANDLE hThread, PCWSTR lpThreadDescription) { using SetThreadDescription_t = decltype(&SetThreadDescription); static SetThreadDescription_t pSetThreadDescription = []() { HMODULE hKernel32 = GetModuleHandle(L"kernel32.dll"); if (hKernel32) { return (SetThreadDescription_t)GetProcAddress( hKernel32, "SetThreadDescription"); } return (SetThreadDescription_t) nullptr; }(); if (!pSetThreadDescription) { return E_NOTIMPL; } return pSetThreadDescription(hThread, lpThreadDescription); } bool IsProcessFrozen(HANDLE hProcess) { // https://github.com/winsiderss/systeminformer/blob/044957137e1d7200431926130ea7cd6bf9d8a11f/phnt/include/ntpsapi.h#L303-L334 typedef struct _PROCESS_BASIC_INFORMATION { NTSTATUS ExitStatus; /*PPEB*/ LPVOID PebBaseAddress; ULONG_PTR AffinityMask; /*KPRIORITY*/ LONG BasePriority; HANDLE UniqueProcessId; HANDLE InheritedFromUniqueProcessId; } PROCESS_BASIC_INFORMATION, *PPROCESS_BASIC_INFORMATION; typedef struct _PROCESS_EXTENDED_BASIC_INFORMATION { SIZE_T Size; // set to sizeof structure on input PROCESS_BASIC_INFORMATION BasicInfo; union { ULONG Flags; struct { ULONG IsProtectedProcess : 1; ULONG IsWow64Process : 1; ULONG IsProcessDeleting : 1; ULONG IsCrossSessionCreate : 1; ULONG IsFrozen : 1; ULONG IsBackground : 1; ULONG IsStronglyNamed : 1; ULONG IsSecureProcess : 1; ULONG IsSubsystemProcess : 1; ULONG SpareBits : 23; }; }; } PROCESS_EXTENDED_BASIC_INFORMATION, *PPROCESS_EXTENDED_BASIC_INFORMATION; using NtQueryInformationProcess_t = NTSTATUS(WINAPI*)( _In_ HANDLE ProcessHandle, _In_ /*PROCESSINFOCLASS*/ DWORD ProcessInformationClass, _Out_writes_bytes_(ProcessInformationLength) PVOID ProcessInformation, _In_ ULONG ProcessInformationLength, _Out_opt_ PULONG ReturnLength); static NtQueryInformationProcess_t pNtQueryInformationProcess = []() { HMODULE hNtdll = LoadLibrary(L"ntdll.dll"); if (hNtdll) { return (NtQueryInformationProcess_t)GetProcAddress( hNtdll, "NtQueryInformationProcess"); } return (NtQueryInformationProcess_t) nullptr; }(); if (!pNtQueryInformationProcess) { return false; } PROCESS_EXTENDED_BASIC_INFORMATION pebi; if (0 <= pNtQueryInformationProcess(hProcess, /*ProcessBasicInformation*/ 0, &pebi, sizeof(pebi), 0) && pebi.Size >= sizeof(pebi)) { return pebi.IsFrozen != 0; } return false; } void GetNtVersionNumbers(ULONG* pNtMajorVersion, ULONG* pNtMinorVersion, ULONG* pNtBuildNumber) { using RtlGetNtVersionNumbers_t = void(WINAPI*)(ULONG * pNtMajorVersion, ULONG * pNtMinorVersion, ULONG * pNtBuildNumber); static RtlGetNtVersionNumbers_t pRtlGetNtVersionNumbers = []() { HMODULE hNtdll = GetModuleHandle(L"ntdll.dll"); if (hNtdll) { return (RtlGetNtVersionNumbers_t)GetProcAddress( hNtdll, "RtlGetNtVersionNumbers"); } return (RtlGetNtVersionNumbers_t) nullptr; }(); if (pRtlGetNtVersionNumbers) { pRtlGetNtVersionNumbers(pNtMajorVersion, pNtMinorVersion, pNtBuildNumber); // The upper 4 bits are reserved for the type of the OS build. // https://dennisbabkin.com/blog/?t=how-to-tell-the-real-version-of-windows-your-app-is-running-on *pNtBuildNumber &= ~0xF0000000; return; } // Use GetVersionEx as a fallback. #pragma warning(push) #pragma warning(disable : 4996) // disable deprecation message OSVERSIONINFO versionInfo = {sizeof(OSVERSIONINFO)}; if (GetVersionEx(&versionInfo)) { *pNtMajorVersion = versionInfo.dwMajorVersion; *pNtMinorVersion = versionInfo.dwMinorVersion; *pNtBuildNumber = versionInfo.dwBuildNumber; return; } #pragma warning(pop) *pNtMajorVersion = 0; *pNtMinorVersion = 0; *pNtBuildNumber = 0; } bool IsWindowsVersionOrGreaterWithBuildNumber(WORD wMajorVersion, WORD wMinorVersion, WORD wBuildNumber) { ULONG majorVersion = 0; ULONG minorVersion = 0; ULONG buildNumber = 0; Functions::GetNtVersionNumbers(&majorVersion, &minorVersion, &buildNumber); if (majorVersion != wMajorVersion) { return majorVersion > wMajorVersion; } if (minorVersion != wMinorVersion) { return minorVersion > wMinorVersion; } return buildNumber >= wBuildNumber; } // Based on: // https://github.com/winsiderss/systeminformer/blob/fc2a978e924f0f72f59928e74a5cfccbb48dfd10/phlib/native.c#L16472 // // rev from RtlpCreateExecutionRequiredRequest (dmex) /** * Creates a PLM execution request. This is mandatory on Windows 8 and above to * prevent processes freezing while querying process information and deadlocking * the calling process. * * \param ProcessHandle A handle to the process for which the power request is * to be created. \param PowerRequestHandle A pointer to a variable that * receives a handle to the new power request. * * \return Successful or errant status. */ NTSTATUS CreateExecutionRequiredRequest(_In_ HANDLE ProcessHandle, _Out_ PHANDLE PowerRequestHandle) { NTSTATUS status; HANDLE powerRequestHandle = nullptr; // On WoW64, NtPowerInformation only handles 4 info classes: // PowerRequestCreate, PowerRequestAction, EnergyTrackerCreate, // EnergyTrackerQuery. The rest are forwarded as-is to the native x86-64 // implementation. #ifndef _WIN64 BOOL isWow64; if (IsWow64Process(GetCurrentProcess(), &isWow64) && isWow64) { COUNTED_REASON_CONTEXT64 powerRequestReason64; memset(&powerRequestReason64, 0, sizeof(COUNTED_REASON_CONTEXT64)); powerRequestReason64.Version = POWER_REQUEST_CONTEXT_VERSION; powerRequestReason64.Flags = POWER_REQUEST_CONTEXT_NOT_SPECIFIED; DWORD64 powerRequestHandle64 = 0; status = NtPowerInformation(PlmPowerRequestCreate, &powerRequestReason64, sizeof(COUNTED_REASON_CONTEXT64), &powerRequestHandle64, sizeof(DWORD64)); powerRequestHandle = (HANDLE)powerRequestHandle64; } else { #endif COUNTED_REASON_CONTEXT powerRequestReason; memset(&powerRequestReason, 0, sizeof(COUNTED_REASON_CONTEXT)); powerRequestReason.Version = POWER_REQUEST_CONTEXT_VERSION; powerRequestReason.Flags = POWER_REQUEST_CONTEXT_NOT_SPECIFIED; status = NtPowerInformation(PlmPowerRequestCreate, &powerRequestReason, sizeof(COUNTED_REASON_CONTEXT), &powerRequestHandle, sizeof(HANDLE)); #ifndef _WIN64 } #endif if (!NT_SUCCESS(status)) return status; POWER_REQUEST_ACTION powerRequestAction; memset(&powerRequestAction, 0, sizeof(POWER_REQUEST_ACTION)); powerRequestAction.PowerRequestHandle = powerRequestHandle; powerRequestAction.RequestType = PowerRequestExecutionRequiredInternal; powerRequestAction.SetAction = TRUE; powerRequestAction.ProcessHandle = ProcessHandle; status = NtPowerInformation(PowerRequestAction, &powerRequestAction, sizeof(POWER_REQUEST_ACTION), nullptr, 0); if (NT_SUCCESS(status)) { *PowerRequestHandle = powerRequestHandle; } else { CloseHandle(powerRequestHandle); } return status; } bool IsRightToLeftLanguage(LANGID langId) { switch (PRIMARYLANGID(langId)) { case LANG_ARABIC: case LANG_FARSI: case LANG_HEBREW: case LANG_URDU: return true; default: return false; } } void ApplyDialogLayoutRtl(CWindow wnd, bool isLayoutRtl) { wnd.ModifyStyleEx(isLayoutRtl ? 0 : WS_EX_LAYOUTRTL, isLayoutRtl ? WS_EX_LAYOUTRTL : 0); ::EnumChildWindows( wnd, [](HWND hWnd, LPARAM lParam) { bool isLayoutRtl = lParam != 0; CWindow control(hWnd); CWindow parent = control.GetParent(); CRect rcParent; parent.GetClientRect(rcParent); CRect rcControl; control.GetWindowRect(rcControl); ::MapWindowPoints(NULL, parent, (POINT*)&rcControl, 2); rcControl.MoveToX(rcParent.Width() - rcControl.right); control.SetWindowPos(NULL, rcControl, SWP_NOZORDER); if (isLayoutRtl) { control.ModifyStyleEx(0, WS_EX_LAYOUTRTL); } else { // Sometimes (e.g. for Edit controls), when setting // WS_EX_LAYOUTRTL, the flag is not actually set. Other flags // are being set instead (e.g. WS_EX_RTLREADING). Below, we try // to handle such cases. DWORD dwExStyle = control.GetExStyle(); if (dwExStyle & WS_EX_LAYOUTRTL) { control.ModifyStyleEx(WS_EX_LAYOUTRTL, 0); } else if (dwExStyle & (WS_EX_RTLREADING | WS_EX_RIGHT | WS_EX_LEFTSCROLLBAR)) { control.ModifyStyleEx( WS_EX_RTLREADING | WS_EX_RIGHT | WS_EX_LEFTSCROLLBAR, 0); WCHAR szClassName[64]; if (::GetClassName(control, szClassName, _countof(szClassName))) { if (_wcsicmp(szClassName, L"Edit") == 0) control.ModifyStyle(ES_RIGHT, 0); } } } control.InvalidateRect(NULL); return TRUE; }, isLayoutRtl); wnd.InvalidateRect(NULL); } } // namespace Functions ================================================ FILE: src/windhawk/app/functions.h ================================================ #pragma once namespace Functions { BOOL SetPrivilege(HANDLE hToken, LPCTSTR lpszPrivilege, BOOL bEnablePrivilege); BOOL SetDebugPrivilege(BOOL bEnablePrivilege); HANDLE CreateEventForMediumIntegrity(PCWSTR eventName, BOOL manualReset = FALSE); BOOL IsRunAsAdmin(); PCWSTR LoadStrFromRsrc(UINT uStrId); std::vector SplitString(std::wstring_view s, WCHAR delim); std::vector SplitStringToViews(std::wstring_view s, WCHAR delim); std::wstring ReplaceAll(std::wstring_view source, std::wstring_view from, std::wstring_view to, bool ignoreCase = false); UINT GetDpiForWindowWithFallback(HWND hWnd); int GetSystemMetricsForDpiWithFallback(int nIndex, UINT dpi); int GetSystemMetricsForWindow(HWND hWnd, int nIndex); HRESULT SetThreadDescriptionIfAvailable(HANDLE hThread, PCWSTR lpThreadDescription); // Returns true for suspended UWP processes. // https://stackoverflow.com/a/50173965 bool IsProcessFrozen(HANDLE hProcess); void GetNtVersionNumbers(ULONG* pNtMajorVersion, ULONG* pNtMinorVersion, ULONG* pNtBuildNumber); bool IsWindowsVersionOrGreaterWithBuildNumber(WORD wMajorVersion, WORD wMinorVersion, WORD wBuildNumber); NTSTATUS CreateExecutionRequiredRequest(_In_ HANDLE ProcessHandle, _Out_ PHANDLE PowerRequestHandle); bool IsRightToLeftLanguage(LANGID langId); void ApplyDialogLayoutRtl(CWindow wnd, bool isLayoutRtl); } // namespace Functions ================================================ FILE: src/windhawk/app/libraries/nlohmann/json.hpp ================================================ /* __ _____ _____ _____ __| | __| | | | JSON for Modern C++ | | |__ | | | | | | version 3.9.1 |_____|_____|_____|_|___| https://github.com/nlohmann/json Licensed under the MIT License . SPDX-License-Identifier: MIT Copyright (c) 2013-2019 Niels Lohmann . 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 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. */ #ifndef INCLUDE_NLOHMANN_JSON_HPP_ #define INCLUDE_NLOHMANN_JSON_HPP_ #define NLOHMANN_JSON_VERSION_MAJOR 3 #define NLOHMANN_JSON_VERSION_MINOR 9 #define NLOHMANN_JSON_VERSION_PATCH 1 #include // all_of, find, for_each #include // nullptr_t, ptrdiff_t, size_t #include // hash, less #include // initializer_list #include // istream, ostream #include // random_access_iterator_tag #include // unique_ptr #include // accumulate #include // string, stoi, to_string #include // declval, forward, move, pair, swap #include // vector // #include #include // #include #include // transform #include // array #include // forward_list #include // inserter, front_inserter, end #include // map #include // string #include // tuple, make_tuple #include // is_arithmetic, is_same, is_enum, underlying_type, is_convertible #include // unordered_map #include // pair, declval #include // valarray // #include #include // exception #include // runtime_error #include // to_string // #include #include // size_t namespace nlohmann { namespace detail { /// struct to capture the start position of the current token struct position_t { /// the total number of characters read std::size_t chars_read_total = 0; /// the number of characters read in the current line std::size_t chars_read_current_line = 0; /// the number of lines read std::size_t lines_read = 0; /// conversion to size_t to preserve SAX interface constexpr operator size_t() const { return chars_read_total; } }; } // namespace detail } // namespace nlohmann // #include #include // pair // #include /* Hedley - https://nemequ.github.io/hedley * Created by Evan Nemerson * * To the extent possible under law, the author(s) have dedicated all * copyright and related and neighboring rights to this software to * the public domain worldwide. This software is distributed without * any warranty. * * For details, see . * SPDX-License-Identifier: CC0-1.0 */ #if !defined(JSON_HEDLEY_VERSION) || (JSON_HEDLEY_VERSION < 13) #if defined(JSON_HEDLEY_VERSION) #undef JSON_HEDLEY_VERSION #endif #define JSON_HEDLEY_VERSION 13 #if defined(JSON_HEDLEY_STRINGIFY_EX) #undef JSON_HEDLEY_STRINGIFY_EX #endif #define JSON_HEDLEY_STRINGIFY_EX(x) #x #if defined(JSON_HEDLEY_STRINGIFY) #undef JSON_HEDLEY_STRINGIFY #endif #define JSON_HEDLEY_STRINGIFY(x) JSON_HEDLEY_STRINGIFY_EX(x) #if defined(JSON_HEDLEY_CONCAT_EX) #undef JSON_HEDLEY_CONCAT_EX #endif #define JSON_HEDLEY_CONCAT_EX(a,b) a##b #if defined(JSON_HEDLEY_CONCAT) #undef JSON_HEDLEY_CONCAT #endif #define JSON_HEDLEY_CONCAT(a,b) JSON_HEDLEY_CONCAT_EX(a,b) #if defined(JSON_HEDLEY_CONCAT3_EX) #undef JSON_HEDLEY_CONCAT3_EX #endif #define JSON_HEDLEY_CONCAT3_EX(a,b,c) a##b##c #if defined(JSON_HEDLEY_CONCAT3) #undef JSON_HEDLEY_CONCAT3 #endif #define JSON_HEDLEY_CONCAT3(a,b,c) JSON_HEDLEY_CONCAT3_EX(a,b,c) #if defined(JSON_HEDLEY_VERSION_ENCODE) #undef JSON_HEDLEY_VERSION_ENCODE #endif #define JSON_HEDLEY_VERSION_ENCODE(major,minor,revision) (((major) * 1000000) + ((minor) * 1000) + (revision)) #if defined(JSON_HEDLEY_VERSION_DECODE_MAJOR) #undef JSON_HEDLEY_VERSION_DECODE_MAJOR #endif #define JSON_HEDLEY_VERSION_DECODE_MAJOR(version) ((version) / 1000000) #if defined(JSON_HEDLEY_VERSION_DECODE_MINOR) #undef JSON_HEDLEY_VERSION_DECODE_MINOR #endif #define JSON_HEDLEY_VERSION_DECODE_MINOR(version) (((version) % 1000000) / 1000) #if defined(JSON_HEDLEY_VERSION_DECODE_REVISION) #undef JSON_HEDLEY_VERSION_DECODE_REVISION #endif #define JSON_HEDLEY_VERSION_DECODE_REVISION(version) ((version) % 1000) #if defined(JSON_HEDLEY_GNUC_VERSION) #undef JSON_HEDLEY_GNUC_VERSION #endif #if defined(__GNUC__) && defined(__GNUC_PATCHLEVEL__) #define JSON_HEDLEY_GNUC_VERSION JSON_HEDLEY_VERSION_ENCODE(__GNUC__, __GNUC_MINOR__, __GNUC_PATCHLEVEL__) #elif defined(__GNUC__) #define JSON_HEDLEY_GNUC_VERSION JSON_HEDLEY_VERSION_ENCODE(__GNUC__, __GNUC_MINOR__, 0) #endif #if defined(JSON_HEDLEY_GNUC_VERSION_CHECK) #undef JSON_HEDLEY_GNUC_VERSION_CHECK #endif #if defined(JSON_HEDLEY_GNUC_VERSION) #define JSON_HEDLEY_GNUC_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_GNUC_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) #else #define JSON_HEDLEY_GNUC_VERSION_CHECK(major,minor,patch) (0) #endif #if defined(JSON_HEDLEY_MSVC_VERSION) #undef JSON_HEDLEY_MSVC_VERSION #endif #if defined(_MSC_FULL_VER) && (_MSC_FULL_VER >= 140000000) #define JSON_HEDLEY_MSVC_VERSION JSON_HEDLEY_VERSION_ENCODE(_MSC_FULL_VER / 10000000, (_MSC_FULL_VER % 10000000) / 100000, (_MSC_FULL_VER % 100000) / 100) #elif defined(_MSC_FULL_VER) #define JSON_HEDLEY_MSVC_VERSION JSON_HEDLEY_VERSION_ENCODE(_MSC_FULL_VER / 1000000, (_MSC_FULL_VER % 1000000) / 10000, (_MSC_FULL_VER % 10000) / 10) #elif defined(_MSC_VER) #define JSON_HEDLEY_MSVC_VERSION JSON_HEDLEY_VERSION_ENCODE(_MSC_VER / 100, _MSC_VER % 100, 0) #endif #if defined(JSON_HEDLEY_MSVC_VERSION_CHECK) #undef JSON_HEDLEY_MSVC_VERSION_CHECK #endif #if !defined(_MSC_VER) #define JSON_HEDLEY_MSVC_VERSION_CHECK(major,minor,patch) (0) #elif defined(_MSC_VER) && (_MSC_VER >= 1400) #define JSON_HEDLEY_MSVC_VERSION_CHECK(major,minor,patch) (_MSC_FULL_VER >= ((major * 10000000) + (minor * 100000) + (patch))) #elif defined(_MSC_VER) && (_MSC_VER >= 1200) #define JSON_HEDLEY_MSVC_VERSION_CHECK(major,minor,patch) (_MSC_FULL_VER >= ((major * 1000000) + (minor * 10000) + (patch))) #else #define JSON_HEDLEY_MSVC_VERSION_CHECK(major,minor,patch) (_MSC_VER >= ((major * 100) + (minor))) #endif #if defined(JSON_HEDLEY_INTEL_VERSION) #undef JSON_HEDLEY_INTEL_VERSION #endif #if defined(__INTEL_COMPILER) && defined(__INTEL_COMPILER_UPDATE) #define JSON_HEDLEY_INTEL_VERSION JSON_HEDLEY_VERSION_ENCODE(__INTEL_COMPILER / 100, __INTEL_COMPILER % 100, __INTEL_COMPILER_UPDATE) #elif defined(__INTEL_COMPILER) #define JSON_HEDLEY_INTEL_VERSION JSON_HEDLEY_VERSION_ENCODE(__INTEL_COMPILER / 100, __INTEL_COMPILER % 100, 0) #endif #if defined(JSON_HEDLEY_INTEL_VERSION_CHECK) #undef JSON_HEDLEY_INTEL_VERSION_CHECK #endif #if defined(JSON_HEDLEY_INTEL_VERSION) #define JSON_HEDLEY_INTEL_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_INTEL_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) #else #define JSON_HEDLEY_INTEL_VERSION_CHECK(major,minor,patch) (0) #endif #if defined(JSON_HEDLEY_PGI_VERSION) #undef JSON_HEDLEY_PGI_VERSION #endif #if defined(__PGI) && defined(__PGIC__) && defined(__PGIC_MINOR__) && defined(__PGIC_PATCHLEVEL__) #define JSON_HEDLEY_PGI_VERSION JSON_HEDLEY_VERSION_ENCODE(__PGIC__, __PGIC_MINOR__, __PGIC_PATCHLEVEL__) #endif #if defined(JSON_HEDLEY_PGI_VERSION_CHECK) #undef JSON_HEDLEY_PGI_VERSION_CHECK #endif #if defined(JSON_HEDLEY_PGI_VERSION) #define JSON_HEDLEY_PGI_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_PGI_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) #else #define JSON_HEDLEY_PGI_VERSION_CHECK(major,minor,patch) (0) #endif #if defined(JSON_HEDLEY_SUNPRO_VERSION) #undef JSON_HEDLEY_SUNPRO_VERSION #endif #if defined(__SUNPRO_C) && (__SUNPRO_C > 0x1000) #define JSON_HEDLEY_SUNPRO_VERSION JSON_HEDLEY_VERSION_ENCODE((((__SUNPRO_C >> 16) & 0xf) * 10) + ((__SUNPRO_C >> 12) & 0xf), (((__SUNPRO_C >> 8) & 0xf) * 10) + ((__SUNPRO_C >> 4) & 0xf), (__SUNPRO_C & 0xf) * 10) #elif defined(__SUNPRO_C) #define JSON_HEDLEY_SUNPRO_VERSION JSON_HEDLEY_VERSION_ENCODE((__SUNPRO_C >> 8) & 0xf, (__SUNPRO_C >> 4) & 0xf, (__SUNPRO_C) & 0xf) #elif defined(__SUNPRO_CC) && (__SUNPRO_CC > 0x1000) #define JSON_HEDLEY_SUNPRO_VERSION JSON_HEDLEY_VERSION_ENCODE((((__SUNPRO_CC >> 16) & 0xf) * 10) + ((__SUNPRO_CC >> 12) & 0xf), (((__SUNPRO_CC >> 8) & 0xf) * 10) + ((__SUNPRO_CC >> 4) & 0xf), (__SUNPRO_CC & 0xf) * 10) #elif defined(__SUNPRO_CC) #define JSON_HEDLEY_SUNPRO_VERSION JSON_HEDLEY_VERSION_ENCODE((__SUNPRO_CC >> 8) & 0xf, (__SUNPRO_CC >> 4) & 0xf, (__SUNPRO_CC) & 0xf) #endif #if defined(JSON_HEDLEY_SUNPRO_VERSION_CHECK) #undef JSON_HEDLEY_SUNPRO_VERSION_CHECK #endif #if defined(JSON_HEDLEY_SUNPRO_VERSION) #define JSON_HEDLEY_SUNPRO_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_SUNPRO_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) #else #define JSON_HEDLEY_SUNPRO_VERSION_CHECK(major,minor,patch) (0) #endif #if defined(JSON_HEDLEY_EMSCRIPTEN_VERSION) #undef JSON_HEDLEY_EMSCRIPTEN_VERSION #endif #if defined(__EMSCRIPTEN__) #define JSON_HEDLEY_EMSCRIPTEN_VERSION JSON_HEDLEY_VERSION_ENCODE(__EMSCRIPTEN_major__, __EMSCRIPTEN_minor__, __EMSCRIPTEN_tiny__) #endif #if defined(JSON_HEDLEY_EMSCRIPTEN_VERSION_CHECK) #undef JSON_HEDLEY_EMSCRIPTEN_VERSION_CHECK #endif #if defined(JSON_HEDLEY_EMSCRIPTEN_VERSION) #define JSON_HEDLEY_EMSCRIPTEN_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_EMSCRIPTEN_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) #else #define JSON_HEDLEY_EMSCRIPTEN_VERSION_CHECK(major,minor,patch) (0) #endif #if defined(JSON_HEDLEY_ARM_VERSION) #undef JSON_HEDLEY_ARM_VERSION #endif #if defined(__CC_ARM) && defined(__ARMCOMPILER_VERSION) #define JSON_HEDLEY_ARM_VERSION JSON_HEDLEY_VERSION_ENCODE(__ARMCOMPILER_VERSION / 1000000, (__ARMCOMPILER_VERSION % 1000000) / 10000, (__ARMCOMPILER_VERSION % 10000) / 100) #elif defined(__CC_ARM) && defined(__ARMCC_VERSION) #define JSON_HEDLEY_ARM_VERSION JSON_HEDLEY_VERSION_ENCODE(__ARMCC_VERSION / 1000000, (__ARMCC_VERSION % 1000000) / 10000, (__ARMCC_VERSION % 10000) / 100) #endif #if defined(JSON_HEDLEY_ARM_VERSION_CHECK) #undef JSON_HEDLEY_ARM_VERSION_CHECK #endif #if defined(JSON_HEDLEY_ARM_VERSION) #define JSON_HEDLEY_ARM_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_ARM_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) #else #define JSON_HEDLEY_ARM_VERSION_CHECK(major,minor,patch) (0) #endif #if defined(JSON_HEDLEY_IBM_VERSION) #undef JSON_HEDLEY_IBM_VERSION #endif #if defined(__ibmxl__) #define JSON_HEDLEY_IBM_VERSION JSON_HEDLEY_VERSION_ENCODE(__ibmxl_version__, __ibmxl_release__, __ibmxl_modification__) #elif defined(__xlC__) && defined(__xlC_ver__) #define JSON_HEDLEY_IBM_VERSION JSON_HEDLEY_VERSION_ENCODE(__xlC__ >> 8, __xlC__ & 0xff, (__xlC_ver__ >> 8) & 0xff) #elif defined(__xlC__) #define JSON_HEDLEY_IBM_VERSION JSON_HEDLEY_VERSION_ENCODE(__xlC__ >> 8, __xlC__ & 0xff, 0) #endif #if defined(JSON_HEDLEY_IBM_VERSION_CHECK) #undef JSON_HEDLEY_IBM_VERSION_CHECK #endif #if defined(JSON_HEDLEY_IBM_VERSION) #define JSON_HEDLEY_IBM_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_IBM_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) #else #define JSON_HEDLEY_IBM_VERSION_CHECK(major,minor,patch) (0) #endif #if defined(JSON_HEDLEY_TI_VERSION) #undef JSON_HEDLEY_TI_VERSION #endif #if \ defined(__TI_COMPILER_VERSION__) && \ ( \ defined(__TMS470__) || defined(__TI_ARM__) || \ defined(__MSP430__) || \ defined(__TMS320C2000__) \ ) #if (__TI_COMPILER_VERSION__ >= 16000000) #define JSON_HEDLEY_TI_VERSION JSON_HEDLEY_VERSION_ENCODE(__TI_COMPILER_VERSION__ / 1000000, (__TI_COMPILER_VERSION__ % 1000000) / 1000, (__TI_COMPILER_VERSION__ % 1000)) #endif #endif #if defined(JSON_HEDLEY_TI_VERSION_CHECK) #undef JSON_HEDLEY_TI_VERSION_CHECK #endif #if defined(JSON_HEDLEY_TI_VERSION) #define JSON_HEDLEY_TI_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_TI_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) #else #define JSON_HEDLEY_TI_VERSION_CHECK(major,minor,patch) (0) #endif #if defined(JSON_HEDLEY_TI_CL2000_VERSION) #undef JSON_HEDLEY_TI_CL2000_VERSION #endif #if defined(__TI_COMPILER_VERSION__) && defined(__TMS320C2000__) #define JSON_HEDLEY_TI_CL2000_VERSION JSON_HEDLEY_VERSION_ENCODE(__TI_COMPILER_VERSION__ / 1000000, (__TI_COMPILER_VERSION__ % 1000000) / 1000, (__TI_COMPILER_VERSION__ % 1000)) #endif #if defined(JSON_HEDLEY_TI_CL2000_VERSION_CHECK) #undef JSON_HEDLEY_TI_CL2000_VERSION_CHECK #endif #if defined(JSON_HEDLEY_TI_CL2000_VERSION) #define JSON_HEDLEY_TI_CL2000_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_TI_CL2000_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) #else #define JSON_HEDLEY_TI_CL2000_VERSION_CHECK(major,minor,patch) (0) #endif #if defined(JSON_HEDLEY_TI_CL430_VERSION) #undef JSON_HEDLEY_TI_CL430_VERSION #endif #if defined(__TI_COMPILER_VERSION__) && defined(__MSP430__) #define JSON_HEDLEY_TI_CL430_VERSION JSON_HEDLEY_VERSION_ENCODE(__TI_COMPILER_VERSION__ / 1000000, (__TI_COMPILER_VERSION__ % 1000000) / 1000, (__TI_COMPILER_VERSION__ % 1000)) #endif #if defined(JSON_HEDLEY_TI_CL430_VERSION_CHECK) #undef JSON_HEDLEY_TI_CL430_VERSION_CHECK #endif #if defined(JSON_HEDLEY_TI_CL430_VERSION) #define JSON_HEDLEY_TI_CL430_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_TI_CL430_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) #else #define JSON_HEDLEY_TI_CL430_VERSION_CHECK(major,minor,patch) (0) #endif #if defined(JSON_HEDLEY_TI_ARMCL_VERSION) #undef JSON_HEDLEY_TI_ARMCL_VERSION #endif #if defined(__TI_COMPILER_VERSION__) && (defined(__TMS470__) || defined(__TI_ARM__)) #define JSON_HEDLEY_TI_ARMCL_VERSION JSON_HEDLEY_VERSION_ENCODE(__TI_COMPILER_VERSION__ / 1000000, (__TI_COMPILER_VERSION__ % 1000000) / 1000, (__TI_COMPILER_VERSION__ % 1000)) #endif #if defined(JSON_HEDLEY_TI_ARMCL_VERSION_CHECK) #undef JSON_HEDLEY_TI_ARMCL_VERSION_CHECK #endif #if defined(JSON_HEDLEY_TI_ARMCL_VERSION) #define JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_TI_ARMCL_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) #else #define JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(major,minor,patch) (0) #endif #if defined(JSON_HEDLEY_TI_CL6X_VERSION) #undef JSON_HEDLEY_TI_CL6X_VERSION #endif #if defined(__TI_COMPILER_VERSION__) && defined(__TMS320C6X__) #define JSON_HEDLEY_TI_CL6X_VERSION JSON_HEDLEY_VERSION_ENCODE(__TI_COMPILER_VERSION__ / 1000000, (__TI_COMPILER_VERSION__ % 1000000) / 1000, (__TI_COMPILER_VERSION__ % 1000)) #endif #if defined(JSON_HEDLEY_TI_CL6X_VERSION_CHECK) #undef JSON_HEDLEY_TI_CL6X_VERSION_CHECK #endif #if defined(JSON_HEDLEY_TI_CL6X_VERSION) #define JSON_HEDLEY_TI_CL6X_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_TI_CL6X_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) #else #define JSON_HEDLEY_TI_CL6X_VERSION_CHECK(major,minor,patch) (0) #endif #if defined(JSON_HEDLEY_TI_CL7X_VERSION) #undef JSON_HEDLEY_TI_CL7X_VERSION #endif #if defined(__TI_COMPILER_VERSION__) && defined(__C7000__) #define JSON_HEDLEY_TI_CL7X_VERSION JSON_HEDLEY_VERSION_ENCODE(__TI_COMPILER_VERSION__ / 1000000, (__TI_COMPILER_VERSION__ % 1000000) / 1000, (__TI_COMPILER_VERSION__ % 1000)) #endif #if defined(JSON_HEDLEY_TI_CL7X_VERSION_CHECK) #undef JSON_HEDLEY_TI_CL7X_VERSION_CHECK #endif #if defined(JSON_HEDLEY_TI_CL7X_VERSION) #define JSON_HEDLEY_TI_CL7X_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_TI_CL7X_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) #else #define JSON_HEDLEY_TI_CL7X_VERSION_CHECK(major,minor,patch) (0) #endif #if defined(JSON_HEDLEY_TI_CLPRU_VERSION) #undef JSON_HEDLEY_TI_CLPRU_VERSION #endif #if defined(__TI_COMPILER_VERSION__) && defined(__PRU__) #define JSON_HEDLEY_TI_CLPRU_VERSION JSON_HEDLEY_VERSION_ENCODE(__TI_COMPILER_VERSION__ / 1000000, (__TI_COMPILER_VERSION__ % 1000000) / 1000, (__TI_COMPILER_VERSION__ % 1000)) #endif #if defined(JSON_HEDLEY_TI_CLPRU_VERSION_CHECK) #undef JSON_HEDLEY_TI_CLPRU_VERSION_CHECK #endif #if defined(JSON_HEDLEY_TI_CLPRU_VERSION) #define JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_TI_CLPRU_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) #else #define JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(major,minor,patch) (0) #endif #if defined(JSON_HEDLEY_CRAY_VERSION) #undef JSON_HEDLEY_CRAY_VERSION #endif #if defined(_CRAYC) #if defined(_RELEASE_PATCHLEVEL) #define JSON_HEDLEY_CRAY_VERSION JSON_HEDLEY_VERSION_ENCODE(_RELEASE_MAJOR, _RELEASE_MINOR, _RELEASE_PATCHLEVEL) #else #define JSON_HEDLEY_CRAY_VERSION JSON_HEDLEY_VERSION_ENCODE(_RELEASE_MAJOR, _RELEASE_MINOR, 0) #endif #endif #if defined(JSON_HEDLEY_CRAY_VERSION_CHECK) #undef JSON_HEDLEY_CRAY_VERSION_CHECK #endif #if defined(JSON_HEDLEY_CRAY_VERSION) #define JSON_HEDLEY_CRAY_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_CRAY_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) #else #define JSON_HEDLEY_CRAY_VERSION_CHECK(major,minor,patch) (0) #endif #if defined(JSON_HEDLEY_IAR_VERSION) #undef JSON_HEDLEY_IAR_VERSION #endif #if defined(__IAR_SYSTEMS_ICC__) #if __VER__ > 1000 #define JSON_HEDLEY_IAR_VERSION JSON_HEDLEY_VERSION_ENCODE((__VER__ / 1000000), ((__VER__ / 1000) % 1000), (__VER__ % 1000)) #else #define JSON_HEDLEY_IAR_VERSION JSON_HEDLEY_VERSION_ENCODE(VER / 100, __VER__ % 100, 0) #endif #endif #if defined(JSON_HEDLEY_IAR_VERSION_CHECK) #undef JSON_HEDLEY_IAR_VERSION_CHECK #endif #if defined(JSON_HEDLEY_IAR_VERSION) #define JSON_HEDLEY_IAR_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_IAR_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) #else #define JSON_HEDLEY_IAR_VERSION_CHECK(major,minor,patch) (0) #endif #if defined(JSON_HEDLEY_TINYC_VERSION) #undef JSON_HEDLEY_TINYC_VERSION #endif #if defined(__TINYC__) #define JSON_HEDLEY_TINYC_VERSION JSON_HEDLEY_VERSION_ENCODE(__TINYC__ / 1000, (__TINYC__ / 100) % 10, __TINYC__ % 100) #endif #if defined(JSON_HEDLEY_TINYC_VERSION_CHECK) #undef JSON_HEDLEY_TINYC_VERSION_CHECK #endif #if defined(JSON_HEDLEY_TINYC_VERSION) #define JSON_HEDLEY_TINYC_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_TINYC_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) #else #define JSON_HEDLEY_TINYC_VERSION_CHECK(major,minor,patch) (0) #endif #if defined(JSON_HEDLEY_DMC_VERSION) #undef JSON_HEDLEY_DMC_VERSION #endif #if defined(__DMC__) #define JSON_HEDLEY_DMC_VERSION JSON_HEDLEY_VERSION_ENCODE(__DMC__ >> 8, (__DMC__ >> 4) & 0xf, __DMC__ & 0xf) #endif #if defined(JSON_HEDLEY_DMC_VERSION_CHECK) #undef JSON_HEDLEY_DMC_VERSION_CHECK #endif #if defined(JSON_HEDLEY_DMC_VERSION) #define JSON_HEDLEY_DMC_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_DMC_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) #else #define JSON_HEDLEY_DMC_VERSION_CHECK(major,minor,patch) (0) #endif #if defined(JSON_HEDLEY_COMPCERT_VERSION) #undef JSON_HEDLEY_COMPCERT_VERSION #endif #if defined(__COMPCERT_VERSION__) #define JSON_HEDLEY_COMPCERT_VERSION JSON_HEDLEY_VERSION_ENCODE(__COMPCERT_VERSION__ / 10000, (__COMPCERT_VERSION__ / 100) % 100, __COMPCERT_VERSION__ % 100) #endif #if defined(JSON_HEDLEY_COMPCERT_VERSION_CHECK) #undef JSON_HEDLEY_COMPCERT_VERSION_CHECK #endif #if defined(JSON_HEDLEY_COMPCERT_VERSION) #define JSON_HEDLEY_COMPCERT_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_COMPCERT_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) #else #define JSON_HEDLEY_COMPCERT_VERSION_CHECK(major,minor,patch) (0) #endif #if defined(JSON_HEDLEY_PELLES_VERSION) #undef JSON_HEDLEY_PELLES_VERSION #endif #if defined(__POCC__) #define JSON_HEDLEY_PELLES_VERSION JSON_HEDLEY_VERSION_ENCODE(__POCC__ / 100, __POCC__ % 100, 0) #endif #if defined(JSON_HEDLEY_PELLES_VERSION_CHECK) #undef JSON_HEDLEY_PELLES_VERSION_CHECK #endif #if defined(JSON_HEDLEY_PELLES_VERSION) #define JSON_HEDLEY_PELLES_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_PELLES_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) #else #define JSON_HEDLEY_PELLES_VERSION_CHECK(major,minor,patch) (0) #endif #if defined(JSON_HEDLEY_GCC_VERSION) #undef JSON_HEDLEY_GCC_VERSION #endif #if \ defined(JSON_HEDLEY_GNUC_VERSION) && \ !defined(__clang__) && \ !defined(JSON_HEDLEY_INTEL_VERSION) && \ !defined(JSON_HEDLEY_PGI_VERSION) && \ !defined(JSON_HEDLEY_ARM_VERSION) && \ !defined(JSON_HEDLEY_TI_VERSION) && \ !defined(JSON_HEDLEY_TI_ARMCL_VERSION) && \ !defined(JSON_HEDLEY_TI_CL430_VERSION) && \ !defined(JSON_HEDLEY_TI_CL2000_VERSION) && \ !defined(JSON_HEDLEY_TI_CL6X_VERSION) && \ !defined(JSON_HEDLEY_TI_CL7X_VERSION) && \ !defined(JSON_HEDLEY_TI_CLPRU_VERSION) && \ !defined(__COMPCERT__) #define JSON_HEDLEY_GCC_VERSION JSON_HEDLEY_GNUC_VERSION #endif #if defined(JSON_HEDLEY_GCC_VERSION_CHECK) #undef JSON_HEDLEY_GCC_VERSION_CHECK #endif #if defined(JSON_HEDLEY_GCC_VERSION) #define JSON_HEDLEY_GCC_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_GCC_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) #else #define JSON_HEDLEY_GCC_VERSION_CHECK(major,minor,patch) (0) #endif #if defined(JSON_HEDLEY_HAS_ATTRIBUTE) #undef JSON_HEDLEY_HAS_ATTRIBUTE #endif #if defined(__has_attribute) #define JSON_HEDLEY_HAS_ATTRIBUTE(attribute) __has_attribute(attribute) #else #define JSON_HEDLEY_HAS_ATTRIBUTE(attribute) (0) #endif #if defined(JSON_HEDLEY_GNUC_HAS_ATTRIBUTE) #undef JSON_HEDLEY_GNUC_HAS_ATTRIBUTE #endif #if defined(__has_attribute) #define JSON_HEDLEY_GNUC_HAS_ATTRIBUTE(attribute,major,minor,patch) __has_attribute(attribute) #else #define JSON_HEDLEY_GNUC_HAS_ATTRIBUTE(attribute,major,minor,patch) JSON_HEDLEY_GNUC_VERSION_CHECK(major,minor,patch) #endif #if defined(JSON_HEDLEY_GCC_HAS_ATTRIBUTE) #undef JSON_HEDLEY_GCC_HAS_ATTRIBUTE #endif #if defined(__has_attribute) #define JSON_HEDLEY_GCC_HAS_ATTRIBUTE(attribute,major,minor,patch) __has_attribute(attribute) #else #define JSON_HEDLEY_GCC_HAS_ATTRIBUTE(attribute,major,minor,patch) JSON_HEDLEY_GCC_VERSION_CHECK(major,minor,patch) #endif #if defined(JSON_HEDLEY_HAS_CPP_ATTRIBUTE) #undef JSON_HEDLEY_HAS_CPP_ATTRIBUTE #endif #if \ defined(__has_cpp_attribute) && \ defined(__cplusplus) && \ (!defined(JSON_HEDLEY_SUNPRO_VERSION) || JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,15,0)) #define JSON_HEDLEY_HAS_CPP_ATTRIBUTE(attribute) __has_cpp_attribute(attribute) #else #define JSON_HEDLEY_HAS_CPP_ATTRIBUTE(attribute) (0) #endif #if defined(JSON_HEDLEY_HAS_CPP_ATTRIBUTE_NS) #undef JSON_HEDLEY_HAS_CPP_ATTRIBUTE_NS #endif #if !defined(__cplusplus) || !defined(__has_cpp_attribute) #define JSON_HEDLEY_HAS_CPP_ATTRIBUTE_NS(ns,attribute) (0) #elif \ !defined(JSON_HEDLEY_PGI_VERSION) && \ !defined(JSON_HEDLEY_IAR_VERSION) && \ (!defined(JSON_HEDLEY_SUNPRO_VERSION) || JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,15,0)) && \ (!defined(JSON_HEDLEY_MSVC_VERSION) || JSON_HEDLEY_MSVC_VERSION_CHECK(19,20,0)) #define JSON_HEDLEY_HAS_CPP_ATTRIBUTE_NS(ns,attribute) JSON_HEDLEY_HAS_CPP_ATTRIBUTE(ns::attribute) #else #define JSON_HEDLEY_HAS_CPP_ATTRIBUTE_NS(ns,attribute) (0) #endif #if defined(JSON_HEDLEY_GNUC_HAS_CPP_ATTRIBUTE) #undef JSON_HEDLEY_GNUC_HAS_CPP_ATTRIBUTE #endif #if defined(__has_cpp_attribute) && defined(__cplusplus) #define JSON_HEDLEY_GNUC_HAS_CPP_ATTRIBUTE(attribute,major,minor,patch) __has_cpp_attribute(attribute) #else #define JSON_HEDLEY_GNUC_HAS_CPP_ATTRIBUTE(attribute,major,minor,patch) JSON_HEDLEY_GNUC_VERSION_CHECK(major,minor,patch) #endif #if defined(JSON_HEDLEY_GCC_HAS_CPP_ATTRIBUTE) #undef JSON_HEDLEY_GCC_HAS_CPP_ATTRIBUTE #endif #if defined(__has_cpp_attribute) && defined(__cplusplus) #define JSON_HEDLEY_GCC_HAS_CPP_ATTRIBUTE(attribute,major,minor,patch) __has_cpp_attribute(attribute) #else #define JSON_HEDLEY_GCC_HAS_CPP_ATTRIBUTE(attribute,major,minor,patch) JSON_HEDLEY_GCC_VERSION_CHECK(major,minor,patch) #endif #if defined(JSON_HEDLEY_HAS_BUILTIN) #undef JSON_HEDLEY_HAS_BUILTIN #endif #if defined(__has_builtin) #define JSON_HEDLEY_HAS_BUILTIN(builtin) __has_builtin(builtin) #else #define JSON_HEDLEY_HAS_BUILTIN(builtin) (0) #endif #if defined(JSON_HEDLEY_GNUC_HAS_BUILTIN) #undef JSON_HEDLEY_GNUC_HAS_BUILTIN #endif #if defined(__has_builtin) #define JSON_HEDLEY_GNUC_HAS_BUILTIN(builtin,major,minor,patch) __has_builtin(builtin) #else #define JSON_HEDLEY_GNUC_HAS_BUILTIN(builtin,major,minor,patch) JSON_HEDLEY_GNUC_VERSION_CHECK(major,minor,patch) #endif #if defined(JSON_HEDLEY_GCC_HAS_BUILTIN) #undef JSON_HEDLEY_GCC_HAS_BUILTIN #endif #if defined(__has_builtin) #define JSON_HEDLEY_GCC_HAS_BUILTIN(builtin,major,minor,patch) __has_builtin(builtin) #else #define JSON_HEDLEY_GCC_HAS_BUILTIN(builtin,major,minor,patch) JSON_HEDLEY_GCC_VERSION_CHECK(major,minor,patch) #endif #if defined(JSON_HEDLEY_HAS_FEATURE) #undef JSON_HEDLEY_HAS_FEATURE #endif #if defined(__has_feature) #define JSON_HEDLEY_HAS_FEATURE(feature) __has_feature(feature) #else #define JSON_HEDLEY_HAS_FEATURE(feature) (0) #endif #if defined(JSON_HEDLEY_GNUC_HAS_FEATURE) #undef JSON_HEDLEY_GNUC_HAS_FEATURE #endif #if defined(__has_feature) #define JSON_HEDLEY_GNUC_HAS_FEATURE(feature,major,minor,patch) __has_feature(feature) #else #define JSON_HEDLEY_GNUC_HAS_FEATURE(feature,major,minor,patch) JSON_HEDLEY_GNUC_VERSION_CHECK(major,minor,patch) #endif #if defined(JSON_HEDLEY_GCC_HAS_FEATURE) #undef JSON_HEDLEY_GCC_HAS_FEATURE #endif #if defined(__has_feature) #define JSON_HEDLEY_GCC_HAS_FEATURE(feature,major,minor,patch) __has_feature(feature) #else #define JSON_HEDLEY_GCC_HAS_FEATURE(feature,major,minor,patch) JSON_HEDLEY_GCC_VERSION_CHECK(major,minor,patch) #endif #if defined(JSON_HEDLEY_HAS_EXTENSION) #undef JSON_HEDLEY_HAS_EXTENSION #endif #if defined(__has_extension) #define JSON_HEDLEY_HAS_EXTENSION(extension) __has_extension(extension) #else #define JSON_HEDLEY_HAS_EXTENSION(extension) (0) #endif #if defined(JSON_HEDLEY_GNUC_HAS_EXTENSION) #undef JSON_HEDLEY_GNUC_HAS_EXTENSION #endif #if defined(__has_extension) #define JSON_HEDLEY_GNUC_HAS_EXTENSION(extension,major,minor,patch) __has_extension(extension) #else #define JSON_HEDLEY_GNUC_HAS_EXTENSION(extension,major,minor,patch) JSON_HEDLEY_GNUC_VERSION_CHECK(major,minor,patch) #endif #if defined(JSON_HEDLEY_GCC_HAS_EXTENSION) #undef JSON_HEDLEY_GCC_HAS_EXTENSION #endif #if defined(__has_extension) #define JSON_HEDLEY_GCC_HAS_EXTENSION(extension,major,minor,patch) __has_extension(extension) #else #define JSON_HEDLEY_GCC_HAS_EXTENSION(extension,major,minor,patch) JSON_HEDLEY_GCC_VERSION_CHECK(major,minor,patch) #endif #if defined(JSON_HEDLEY_HAS_DECLSPEC_ATTRIBUTE) #undef JSON_HEDLEY_HAS_DECLSPEC_ATTRIBUTE #endif #if defined(__has_declspec_attribute) #define JSON_HEDLEY_HAS_DECLSPEC_ATTRIBUTE(attribute) __has_declspec_attribute(attribute) #else #define JSON_HEDLEY_HAS_DECLSPEC_ATTRIBUTE(attribute) (0) #endif #if defined(JSON_HEDLEY_GNUC_HAS_DECLSPEC_ATTRIBUTE) #undef JSON_HEDLEY_GNUC_HAS_DECLSPEC_ATTRIBUTE #endif #if defined(__has_declspec_attribute) #define JSON_HEDLEY_GNUC_HAS_DECLSPEC_ATTRIBUTE(attribute,major,minor,patch) __has_declspec_attribute(attribute) #else #define JSON_HEDLEY_GNUC_HAS_DECLSPEC_ATTRIBUTE(attribute,major,minor,patch) JSON_HEDLEY_GNUC_VERSION_CHECK(major,minor,patch) #endif #if defined(JSON_HEDLEY_GCC_HAS_DECLSPEC_ATTRIBUTE) #undef JSON_HEDLEY_GCC_HAS_DECLSPEC_ATTRIBUTE #endif #if defined(__has_declspec_attribute) #define JSON_HEDLEY_GCC_HAS_DECLSPEC_ATTRIBUTE(attribute,major,minor,patch) __has_declspec_attribute(attribute) #else #define JSON_HEDLEY_GCC_HAS_DECLSPEC_ATTRIBUTE(attribute,major,minor,patch) JSON_HEDLEY_GCC_VERSION_CHECK(major,minor,patch) #endif #if defined(JSON_HEDLEY_HAS_WARNING) #undef JSON_HEDLEY_HAS_WARNING #endif #if defined(__has_warning) #define JSON_HEDLEY_HAS_WARNING(warning) __has_warning(warning) #else #define JSON_HEDLEY_HAS_WARNING(warning) (0) #endif #if defined(JSON_HEDLEY_GNUC_HAS_WARNING) #undef JSON_HEDLEY_GNUC_HAS_WARNING #endif #if defined(__has_warning) #define JSON_HEDLEY_GNUC_HAS_WARNING(warning,major,minor,patch) __has_warning(warning) #else #define JSON_HEDLEY_GNUC_HAS_WARNING(warning,major,minor,patch) JSON_HEDLEY_GNUC_VERSION_CHECK(major,minor,patch) #endif #if defined(JSON_HEDLEY_GCC_HAS_WARNING) #undef JSON_HEDLEY_GCC_HAS_WARNING #endif #if defined(__has_warning) #define JSON_HEDLEY_GCC_HAS_WARNING(warning,major,minor,patch) __has_warning(warning) #else #define JSON_HEDLEY_GCC_HAS_WARNING(warning,major,minor,patch) JSON_HEDLEY_GCC_VERSION_CHECK(major,minor,patch) #endif /* JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_ is for HEDLEY INTERNAL USE ONLY. API subject to change without notice. */ #if defined(JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_) #undef JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_ #endif #if defined(__cplusplus) # if JSON_HEDLEY_HAS_WARNING("-Wc++98-compat") # if JSON_HEDLEY_HAS_WARNING("-Wc++17-extensions") # define JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_(xpr) \ JSON_HEDLEY_DIAGNOSTIC_PUSH \ _Pragma("clang diagnostic ignored \"-Wc++98-compat\"") \ _Pragma("clang diagnostic ignored \"-Wc++17-extensions\"") \ xpr \ JSON_HEDLEY_DIAGNOSTIC_POP # else # define JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_(xpr) \ JSON_HEDLEY_DIAGNOSTIC_PUSH \ _Pragma("clang diagnostic ignored \"-Wc++98-compat\"") \ xpr \ JSON_HEDLEY_DIAGNOSTIC_POP # endif # endif #endif #if !defined(JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_) #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_(x) x #endif #if defined(JSON_HEDLEY_CONST_CAST) #undef JSON_HEDLEY_CONST_CAST #endif #if defined(__cplusplus) # define JSON_HEDLEY_CONST_CAST(T, expr) (const_cast(expr)) #elif \ JSON_HEDLEY_HAS_WARNING("-Wcast-qual") || \ JSON_HEDLEY_GCC_VERSION_CHECK(4,6,0) || \ JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) # define JSON_HEDLEY_CONST_CAST(T, expr) (__extension__ ({ \ JSON_HEDLEY_DIAGNOSTIC_PUSH \ JSON_HEDLEY_DIAGNOSTIC_DISABLE_CAST_QUAL \ ((T) (expr)); \ JSON_HEDLEY_DIAGNOSTIC_POP \ })) #else # define JSON_HEDLEY_CONST_CAST(T, expr) ((T) (expr)) #endif #if defined(JSON_HEDLEY_REINTERPRET_CAST) #undef JSON_HEDLEY_REINTERPRET_CAST #endif #if defined(__cplusplus) #define JSON_HEDLEY_REINTERPRET_CAST(T, expr) (reinterpret_cast(expr)) #else #define JSON_HEDLEY_REINTERPRET_CAST(T, expr) ((T) (expr)) #endif #if defined(JSON_HEDLEY_STATIC_CAST) #undef JSON_HEDLEY_STATIC_CAST #endif #if defined(__cplusplus) #define JSON_HEDLEY_STATIC_CAST(T, expr) (static_cast(expr)) #else #define JSON_HEDLEY_STATIC_CAST(T, expr) ((T) (expr)) #endif #if defined(JSON_HEDLEY_CPP_CAST) #undef JSON_HEDLEY_CPP_CAST #endif #if defined(__cplusplus) # if JSON_HEDLEY_HAS_WARNING("-Wold-style-cast") # define JSON_HEDLEY_CPP_CAST(T, expr) \ JSON_HEDLEY_DIAGNOSTIC_PUSH \ _Pragma("clang diagnostic ignored \"-Wold-style-cast\"") \ ((T) (expr)) \ JSON_HEDLEY_DIAGNOSTIC_POP # elif JSON_HEDLEY_IAR_VERSION_CHECK(8,3,0) # define JSON_HEDLEY_CPP_CAST(T, expr) \ JSON_HEDLEY_DIAGNOSTIC_PUSH \ _Pragma("diag_suppress=Pe137") \ JSON_HEDLEY_DIAGNOSTIC_POP \ # else # define JSON_HEDLEY_CPP_CAST(T, expr) ((T) (expr)) # endif #else # define JSON_HEDLEY_CPP_CAST(T, expr) (expr) #endif #if \ (defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L)) || \ defined(__clang__) || \ JSON_HEDLEY_GCC_VERSION_CHECK(3,0,0) || \ JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ JSON_HEDLEY_IAR_VERSION_CHECK(8,0,0) || \ JSON_HEDLEY_PGI_VERSION_CHECK(18,4,0) || \ JSON_HEDLEY_ARM_VERSION_CHECK(4,1,0) || \ JSON_HEDLEY_TI_VERSION_CHECK(15,12,0) || \ JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(4,7,0) || \ JSON_HEDLEY_TI_CL430_VERSION_CHECK(2,0,1) || \ JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,1,0) || \ JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,0,0) || \ JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \ JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(2,1,0) || \ JSON_HEDLEY_CRAY_VERSION_CHECK(5,0,0) || \ JSON_HEDLEY_TINYC_VERSION_CHECK(0,9,17) || \ JSON_HEDLEY_SUNPRO_VERSION_CHECK(8,0,0) || \ (JSON_HEDLEY_IBM_VERSION_CHECK(10,1,0) && defined(__C99_PRAGMA_OPERATOR)) #define JSON_HEDLEY_PRAGMA(value) _Pragma(#value) #elif JSON_HEDLEY_MSVC_VERSION_CHECK(15,0,0) #define JSON_HEDLEY_PRAGMA(value) __pragma(value) #else #define JSON_HEDLEY_PRAGMA(value) #endif #if defined(JSON_HEDLEY_DIAGNOSTIC_PUSH) #undef JSON_HEDLEY_DIAGNOSTIC_PUSH #endif #if defined(JSON_HEDLEY_DIAGNOSTIC_POP) #undef JSON_HEDLEY_DIAGNOSTIC_POP #endif #if defined(__clang__) #define JSON_HEDLEY_DIAGNOSTIC_PUSH _Pragma("clang diagnostic push") #define JSON_HEDLEY_DIAGNOSTIC_POP _Pragma("clang diagnostic pop") #elif JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) #define JSON_HEDLEY_DIAGNOSTIC_PUSH _Pragma("warning(push)") #define JSON_HEDLEY_DIAGNOSTIC_POP _Pragma("warning(pop)") #elif JSON_HEDLEY_GCC_VERSION_CHECK(4,6,0) #define JSON_HEDLEY_DIAGNOSTIC_PUSH _Pragma("GCC diagnostic push") #define JSON_HEDLEY_DIAGNOSTIC_POP _Pragma("GCC diagnostic pop") #elif JSON_HEDLEY_MSVC_VERSION_CHECK(15,0,0) #define JSON_HEDLEY_DIAGNOSTIC_PUSH __pragma(warning(push)) #define JSON_HEDLEY_DIAGNOSTIC_POP __pragma(warning(pop)) #elif JSON_HEDLEY_ARM_VERSION_CHECK(5,6,0) #define JSON_HEDLEY_DIAGNOSTIC_PUSH _Pragma("push") #define JSON_HEDLEY_DIAGNOSTIC_POP _Pragma("pop") #elif \ JSON_HEDLEY_TI_VERSION_CHECK(15,12,0) || \ JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(5,2,0) || \ JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,4,0) || \ JSON_HEDLEY_TI_CL6X_VERSION_CHECK(8,1,0) || \ JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \ JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(2,1,0) #define JSON_HEDLEY_DIAGNOSTIC_PUSH _Pragma("diag_push") #define JSON_HEDLEY_DIAGNOSTIC_POP _Pragma("diag_pop") #elif JSON_HEDLEY_PELLES_VERSION_CHECK(2,90,0) #define JSON_HEDLEY_DIAGNOSTIC_PUSH _Pragma("warning(push)") #define JSON_HEDLEY_DIAGNOSTIC_POP _Pragma("warning(pop)") #else #define JSON_HEDLEY_DIAGNOSTIC_PUSH #define JSON_HEDLEY_DIAGNOSTIC_POP #endif #if defined(JSON_HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED) #undef JSON_HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED #endif #if JSON_HEDLEY_HAS_WARNING("-Wdeprecated-declarations") #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED _Pragma("clang diagnostic ignored \"-Wdeprecated-declarations\"") #elif JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED _Pragma("warning(disable:1478 1786)") #elif JSON_HEDLEY_PGI_VERSION_CHECK(17,10,0) #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED _Pragma("diag_suppress 1215,1444") #elif JSON_HEDLEY_GCC_VERSION_CHECK(4,3,0) #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED _Pragma("GCC diagnostic ignored \"-Wdeprecated-declarations\"") #elif JSON_HEDLEY_MSVC_VERSION_CHECK(15,0,0) #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED __pragma(warning(disable:4996)) #elif \ JSON_HEDLEY_TI_VERSION_CHECK(15,12,0) || \ (JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(4,8,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(5,2,0) || \ (JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,4,0) || \ (JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,3,0) || \ (JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,2,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,5,0) || \ JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \ JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(2,1,0) #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED _Pragma("diag_suppress 1291,1718") #elif JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,13,0) && !defined(__cplusplus) #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED _Pragma("error_messages(off,E_DEPRECATED_ATT,E_DEPRECATED_ATT_MESS)") #elif JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,13,0) && defined(__cplusplus) #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED _Pragma("error_messages(off,symdeprecated,symdeprecated2)") #elif JSON_HEDLEY_IAR_VERSION_CHECK(8,0,0) #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED _Pragma("diag_suppress=Pe1444,Pe1215") #elif JSON_HEDLEY_PELLES_VERSION_CHECK(2,90,0) #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED _Pragma("warn(disable:2241)") #else #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED #endif #if defined(JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_PRAGMAS) #undef JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_PRAGMAS #endif #if JSON_HEDLEY_HAS_WARNING("-Wunknown-pragmas") #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_PRAGMAS _Pragma("clang diagnostic ignored \"-Wunknown-pragmas\"") #elif JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_PRAGMAS _Pragma("warning(disable:161)") #elif JSON_HEDLEY_PGI_VERSION_CHECK(17,10,0) #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_PRAGMAS _Pragma("diag_suppress 1675") #elif JSON_HEDLEY_GCC_VERSION_CHECK(4,3,0) #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_PRAGMAS _Pragma("GCC diagnostic ignored \"-Wunknown-pragmas\"") #elif JSON_HEDLEY_MSVC_VERSION_CHECK(15,0,0) #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_PRAGMAS __pragma(warning(disable:4068)) #elif \ JSON_HEDLEY_TI_VERSION_CHECK(16,9,0) || \ JSON_HEDLEY_TI_CL6X_VERSION_CHECK(8,0,0) || \ JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \ JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(2,3,0) #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_PRAGMAS _Pragma("diag_suppress 163") #elif JSON_HEDLEY_TI_CL6X_VERSION_CHECK(8,0,0) #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_PRAGMAS _Pragma("diag_suppress 163") #elif JSON_HEDLEY_IAR_VERSION_CHECK(8,0,0) #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_PRAGMAS _Pragma("diag_suppress=Pe161") #else #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_PRAGMAS #endif #if defined(JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_CPP_ATTRIBUTES) #undef JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_CPP_ATTRIBUTES #endif #if JSON_HEDLEY_HAS_WARNING("-Wunknown-attributes") #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_CPP_ATTRIBUTES _Pragma("clang diagnostic ignored \"-Wunknown-attributes\"") #elif JSON_HEDLEY_GCC_VERSION_CHECK(4,6,0) #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_CPP_ATTRIBUTES _Pragma("GCC diagnostic ignored \"-Wdeprecated-declarations\"") #elif JSON_HEDLEY_INTEL_VERSION_CHECK(17,0,0) #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_CPP_ATTRIBUTES _Pragma("warning(disable:1292)") #elif JSON_HEDLEY_MSVC_VERSION_CHECK(19,0,0) #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_CPP_ATTRIBUTES __pragma(warning(disable:5030)) #elif JSON_HEDLEY_PGI_VERSION_CHECK(17,10,0) #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_CPP_ATTRIBUTES _Pragma("diag_suppress 1097") #elif JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,14,0) && defined(__cplusplus) #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_CPP_ATTRIBUTES _Pragma("error_messages(off,attrskipunsup)") #elif \ JSON_HEDLEY_TI_VERSION_CHECK(18,1,0) || \ JSON_HEDLEY_TI_CL6X_VERSION_CHECK(8,3,0) || \ JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_CPP_ATTRIBUTES _Pragma("diag_suppress 1173") #elif JSON_HEDLEY_IAR_VERSION_CHECK(8,0,0) #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_CPP_ATTRIBUTES _Pragma("diag_suppress=Pe1097") #else #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_CPP_ATTRIBUTES #endif #if defined(JSON_HEDLEY_DIAGNOSTIC_DISABLE_CAST_QUAL) #undef JSON_HEDLEY_DIAGNOSTIC_DISABLE_CAST_QUAL #endif #if JSON_HEDLEY_HAS_WARNING("-Wcast-qual") #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_CAST_QUAL _Pragma("clang diagnostic ignored \"-Wcast-qual\"") #elif JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_CAST_QUAL _Pragma("warning(disable:2203 2331)") #elif JSON_HEDLEY_GCC_VERSION_CHECK(3,0,0) #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_CAST_QUAL _Pragma("GCC diagnostic ignored \"-Wcast-qual\"") #else #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_CAST_QUAL #endif #if defined(JSON_HEDLEY_DEPRECATED) #undef JSON_HEDLEY_DEPRECATED #endif #if defined(JSON_HEDLEY_DEPRECATED_FOR) #undef JSON_HEDLEY_DEPRECATED_FOR #endif #if JSON_HEDLEY_MSVC_VERSION_CHECK(14,0,0) #define JSON_HEDLEY_DEPRECATED(since) __declspec(deprecated("Since " # since)) #define JSON_HEDLEY_DEPRECATED_FOR(since, replacement) __declspec(deprecated("Since " #since "; use " #replacement)) #elif defined(__cplusplus) && (__cplusplus >= 201402L) #define JSON_HEDLEY_DEPRECATED(since) JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_([[deprecated("Since " #since)]]) #define JSON_HEDLEY_DEPRECATED_FOR(since, replacement) JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_([[deprecated("Since " #since "; use " #replacement)]]) #elif \ JSON_HEDLEY_HAS_EXTENSION(attribute_deprecated_with_message) || \ JSON_HEDLEY_GCC_VERSION_CHECK(4,5,0) || \ JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ JSON_HEDLEY_ARM_VERSION_CHECK(5,6,0) || \ JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,13,0) || \ JSON_HEDLEY_PGI_VERSION_CHECK(17,10,0) || \ JSON_HEDLEY_TI_VERSION_CHECK(18,1,0) || \ JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(18,1,0) || \ JSON_HEDLEY_TI_CL6X_VERSION_CHECK(8,3,0) || \ JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \ JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(2,3,0) #define JSON_HEDLEY_DEPRECATED(since) __attribute__((__deprecated__("Since " #since))) #define JSON_HEDLEY_DEPRECATED_FOR(since, replacement) __attribute__((__deprecated__("Since " #since "; use " #replacement))) #elif \ JSON_HEDLEY_HAS_ATTRIBUTE(deprecated) || \ JSON_HEDLEY_GCC_VERSION_CHECK(3,1,0) || \ JSON_HEDLEY_ARM_VERSION_CHECK(4,1,0) || \ JSON_HEDLEY_TI_VERSION_CHECK(15,12,0) || \ (JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(4,8,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(5,2,0) || \ (JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,4,0) || \ (JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,3,0) || \ (JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,2,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,5,0) || \ JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \ JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(2,1,0) #define JSON_HEDLEY_DEPRECATED(since) __attribute__((__deprecated__)) #define JSON_HEDLEY_DEPRECATED_FOR(since, replacement) __attribute__((__deprecated__)) #elif \ JSON_HEDLEY_MSVC_VERSION_CHECK(13,10,0) || \ JSON_HEDLEY_PELLES_VERSION_CHECK(6,50,0) #define JSON_HEDLEY_DEPRECATED(since) __declspec(deprecated) #define JSON_HEDLEY_DEPRECATED_FOR(since, replacement) __declspec(deprecated) #elif JSON_HEDLEY_IAR_VERSION_CHECK(8,0,0) #define JSON_HEDLEY_DEPRECATED(since) _Pragma("deprecated") #define JSON_HEDLEY_DEPRECATED_FOR(since, replacement) _Pragma("deprecated") #else #define JSON_HEDLEY_DEPRECATED(since) #define JSON_HEDLEY_DEPRECATED_FOR(since, replacement) #endif #if defined(JSON_HEDLEY_UNAVAILABLE) #undef JSON_HEDLEY_UNAVAILABLE #endif #if \ JSON_HEDLEY_HAS_ATTRIBUTE(warning) || \ JSON_HEDLEY_GCC_VERSION_CHECK(4,3,0) || \ JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) #define JSON_HEDLEY_UNAVAILABLE(available_since) __attribute__((__warning__("Not available until " #available_since))) #else #define JSON_HEDLEY_UNAVAILABLE(available_since) #endif #if defined(JSON_HEDLEY_WARN_UNUSED_RESULT) #undef JSON_HEDLEY_WARN_UNUSED_RESULT #endif #if defined(JSON_HEDLEY_WARN_UNUSED_RESULT_MSG) #undef JSON_HEDLEY_WARN_UNUSED_RESULT_MSG #endif #if (JSON_HEDLEY_HAS_CPP_ATTRIBUTE(nodiscard) >= 201907L) #define JSON_HEDLEY_WARN_UNUSED_RESULT JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_([[nodiscard]]) #define JSON_HEDLEY_WARN_UNUSED_RESULT_MSG(msg) JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_([[nodiscard(msg)]]) #elif JSON_HEDLEY_HAS_CPP_ATTRIBUTE(nodiscard) #define JSON_HEDLEY_WARN_UNUSED_RESULT JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_([[nodiscard]]) #define JSON_HEDLEY_WARN_UNUSED_RESULT_MSG(msg) JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_([[nodiscard]]) #elif \ JSON_HEDLEY_HAS_ATTRIBUTE(warn_unused_result) || \ JSON_HEDLEY_GCC_VERSION_CHECK(3,4,0) || \ JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ JSON_HEDLEY_TI_VERSION_CHECK(15,12,0) || \ (JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(4,8,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(5,2,0) || \ (JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,4,0) || \ (JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,3,0) || \ (JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,2,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,5,0) || \ JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \ JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(2,1,0) || \ (JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,15,0) && defined(__cplusplus)) || \ JSON_HEDLEY_PGI_VERSION_CHECK(17,10,0) #define JSON_HEDLEY_WARN_UNUSED_RESULT __attribute__((__warn_unused_result__)) #define JSON_HEDLEY_WARN_UNUSED_RESULT_MSG(msg) __attribute__((__warn_unused_result__)) #elif defined(_Check_return_) /* SAL */ #define JSON_HEDLEY_WARN_UNUSED_RESULT _Check_return_ #define JSON_HEDLEY_WARN_UNUSED_RESULT_MSG(msg) _Check_return_ #else #define JSON_HEDLEY_WARN_UNUSED_RESULT #define JSON_HEDLEY_WARN_UNUSED_RESULT_MSG(msg) #endif #if defined(JSON_HEDLEY_SENTINEL) #undef JSON_HEDLEY_SENTINEL #endif #if \ JSON_HEDLEY_HAS_ATTRIBUTE(sentinel) || \ JSON_HEDLEY_GCC_VERSION_CHECK(4,0,0) || \ JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ JSON_HEDLEY_ARM_VERSION_CHECK(5,4,0) #define JSON_HEDLEY_SENTINEL(position) __attribute__((__sentinel__(position))) #else #define JSON_HEDLEY_SENTINEL(position) #endif #if defined(JSON_HEDLEY_NO_RETURN) #undef JSON_HEDLEY_NO_RETURN #endif #if JSON_HEDLEY_IAR_VERSION_CHECK(8,0,0) #define JSON_HEDLEY_NO_RETURN __noreturn #elif JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) #define JSON_HEDLEY_NO_RETURN __attribute__((__noreturn__)) #elif defined(__STDC_VERSION__) && __STDC_VERSION__ >= 201112L #define JSON_HEDLEY_NO_RETURN _Noreturn #elif defined(__cplusplus) && (__cplusplus >= 201103L) #define JSON_HEDLEY_NO_RETURN JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_([[noreturn]]) #elif \ JSON_HEDLEY_HAS_ATTRIBUTE(noreturn) || \ JSON_HEDLEY_GCC_VERSION_CHECK(3,2,0) || \ JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,11,0) || \ JSON_HEDLEY_ARM_VERSION_CHECK(4,1,0) || \ JSON_HEDLEY_IBM_VERSION_CHECK(10,1,0) || \ JSON_HEDLEY_TI_VERSION_CHECK(15,12,0) || \ (JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(4,8,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(5,2,0) || \ (JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,4,0) || \ (JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,3,0) || \ (JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,2,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,5,0) || \ JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \ JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(2,1,0) #define JSON_HEDLEY_NO_RETURN __attribute__((__noreturn__)) #elif JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,10,0) #define JSON_HEDLEY_NO_RETURN _Pragma("does_not_return") #elif JSON_HEDLEY_MSVC_VERSION_CHECK(13,10,0) #define JSON_HEDLEY_NO_RETURN __declspec(noreturn) #elif JSON_HEDLEY_TI_CL6X_VERSION_CHECK(6,0,0) && defined(__cplusplus) #define JSON_HEDLEY_NO_RETURN _Pragma("FUNC_NEVER_RETURNS;") #elif JSON_HEDLEY_COMPCERT_VERSION_CHECK(3,2,0) #define JSON_HEDLEY_NO_RETURN __attribute((noreturn)) #elif JSON_HEDLEY_PELLES_VERSION_CHECK(9,0,0) #define JSON_HEDLEY_NO_RETURN __declspec(noreturn) #else #define JSON_HEDLEY_NO_RETURN #endif #if defined(JSON_HEDLEY_NO_ESCAPE) #undef JSON_HEDLEY_NO_ESCAPE #endif #if JSON_HEDLEY_HAS_ATTRIBUTE(noescape) #define JSON_HEDLEY_NO_ESCAPE __attribute__((__noescape__)) #else #define JSON_HEDLEY_NO_ESCAPE #endif #if defined(JSON_HEDLEY_UNREACHABLE) #undef JSON_HEDLEY_UNREACHABLE #endif #if defined(JSON_HEDLEY_UNREACHABLE_RETURN) #undef JSON_HEDLEY_UNREACHABLE_RETURN #endif #if defined(JSON_HEDLEY_ASSUME) #undef JSON_HEDLEY_ASSUME #endif #if \ JSON_HEDLEY_MSVC_VERSION_CHECK(13,10,0) || \ JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) #define JSON_HEDLEY_ASSUME(expr) __assume(expr) #elif JSON_HEDLEY_HAS_BUILTIN(__builtin_assume) #define JSON_HEDLEY_ASSUME(expr) __builtin_assume(expr) #elif \ JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,2,0) || \ JSON_HEDLEY_TI_CL6X_VERSION_CHECK(4,0,0) #if defined(__cplusplus) #define JSON_HEDLEY_ASSUME(expr) std::_nassert(expr) #else #define JSON_HEDLEY_ASSUME(expr) _nassert(expr) #endif #endif #if \ (JSON_HEDLEY_HAS_BUILTIN(__builtin_unreachable) && (!defined(JSON_HEDLEY_ARM_VERSION))) || \ JSON_HEDLEY_GCC_VERSION_CHECK(4,5,0) || \ JSON_HEDLEY_PGI_VERSION_CHECK(18,10,0) || \ JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ JSON_HEDLEY_IBM_VERSION_CHECK(13,1,5) #define JSON_HEDLEY_UNREACHABLE() __builtin_unreachable() #elif defined(JSON_HEDLEY_ASSUME) #define JSON_HEDLEY_UNREACHABLE() JSON_HEDLEY_ASSUME(0) #endif #if !defined(JSON_HEDLEY_ASSUME) #if defined(JSON_HEDLEY_UNREACHABLE) #define JSON_HEDLEY_ASSUME(expr) JSON_HEDLEY_STATIC_CAST(void, ((expr) ? 1 : (JSON_HEDLEY_UNREACHABLE(), 1))) #else #define JSON_HEDLEY_ASSUME(expr) JSON_HEDLEY_STATIC_CAST(void, expr) #endif #endif #if defined(JSON_HEDLEY_UNREACHABLE) #if \ JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,2,0) || \ JSON_HEDLEY_TI_CL6X_VERSION_CHECK(4,0,0) #define JSON_HEDLEY_UNREACHABLE_RETURN(value) return (JSON_HEDLEY_STATIC_CAST(void, JSON_HEDLEY_ASSUME(0)), (value)) #else #define JSON_HEDLEY_UNREACHABLE_RETURN(value) JSON_HEDLEY_UNREACHABLE() #endif #else #define JSON_HEDLEY_UNREACHABLE_RETURN(value) return (value) #endif #if !defined(JSON_HEDLEY_UNREACHABLE) #define JSON_HEDLEY_UNREACHABLE() JSON_HEDLEY_ASSUME(0) #endif JSON_HEDLEY_DIAGNOSTIC_PUSH #if JSON_HEDLEY_HAS_WARNING("-Wpedantic") #pragma clang diagnostic ignored "-Wpedantic" #endif #if JSON_HEDLEY_HAS_WARNING("-Wc++98-compat-pedantic") && defined(__cplusplus) #pragma clang diagnostic ignored "-Wc++98-compat-pedantic" #endif #if JSON_HEDLEY_GCC_HAS_WARNING("-Wvariadic-macros",4,0,0) #if defined(__clang__) #pragma clang diagnostic ignored "-Wvariadic-macros" #elif defined(JSON_HEDLEY_GCC_VERSION) #pragma GCC diagnostic ignored "-Wvariadic-macros" #endif #endif #if defined(JSON_HEDLEY_NON_NULL) #undef JSON_HEDLEY_NON_NULL #endif #if \ JSON_HEDLEY_HAS_ATTRIBUTE(nonnull) || \ JSON_HEDLEY_GCC_VERSION_CHECK(3,3,0) || \ JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ JSON_HEDLEY_ARM_VERSION_CHECK(4,1,0) #define JSON_HEDLEY_NON_NULL(...) __attribute__((__nonnull__(__VA_ARGS__))) #else #define JSON_HEDLEY_NON_NULL(...) #endif JSON_HEDLEY_DIAGNOSTIC_POP #if defined(JSON_HEDLEY_PRINTF_FORMAT) #undef JSON_HEDLEY_PRINTF_FORMAT #endif #if defined(__MINGW32__) && JSON_HEDLEY_GCC_HAS_ATTRIBUTE(format,4,4,0) && !defined(__USE_MINGW_ANSI_STDIO) #define JSON_HEDLEY_PRINTF_FORMAT(string_idx,first_to_check) __attribute__((__format__(ms_printf, string_idx, first_to_check))) #elif defined(__MINGW32__) && JSON_HEDLEY_GCC_HAS_ATTRIBUTE(format,4,4,0) && defined(__USE_MINGW_ANSI_STDIO) #define JSON_HEDLEY_PRINTF_FORMAT(string_idx,first_to_check) __attribute__((__format__(gnu_printf, string_idx, first_to_check))) #elif \ JSON_HEDLEY_HAS_ATTRIBUTE(format) || \ JSON_HEDLEY_GCC_VERSION_CHECK(3,1,0) || \ JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ JSON_HEDLEY_ARM_VERSION_CHECK(5,6,0) || \ JSON_HEDLEY_IBM_VERSION_CHECK(10,1,0) || \ JSON_HEDLEY_TI_VERSION_CHECK(15,12,0) || \ (JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(4,8,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(5,2,0) || \ (JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,4,0) || \ (JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,3,0) || \ (JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,2,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,5,0) || \ JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \ JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(2,1,0) #define JSON_HEDLEY_PRINTF_FORMAT(string_idx,first_to_check) __attribute__((__format__(__printf__, string_idx, first_to_check))) #elif JSON_HEDLEY_PELLES_VERSION_CHECK(6,0,0) #define JSON_HEDLEY_PRINTF_FORMAT(string_idx,first_to_check) __declspec(vaformat(printf,string_idx,first_to_check)) #else #define JSON_HEDLEY_PRINTF_FORMAT(string_idx,first_to_check) #endif #if defined(JSON_HEDLEY_CONSTEXPR) #undef JSON_HEDLEY_CONSTEXPR #endif #if defined(__cplusplus) #if __cplusplus >= 201103L #define JSON_HEDLEY_CONSTEXPR JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_(constexpr) #endif #endif #if !defined(JSON_HEDLEY_CONSTEXPR) #define JSON_HEDLEY_CONSTEXPR #endif #if defined(JSON_HEDLEY_PREDICT) #undef JSON_HEDLEY_PREDICT #endif #if defined(JSON_HEDLEY_LIKELY) #undef JSON_HEDLEY_LIKELY #endif #if defined(JSON_HEDLEY_UNLIKELY) #undef JSON_HEDLEY_UNLIKELY #endif #if defined(JSON_HEDLEY_UNPREDICTABLE) #undef JSON_HEDLEY_UNPREDICTABLE #endif #if JSON_HEDLEY_HAS_BUILTIN(__builtin_unpredictable) #define JSON_HEDLEY_UNPREDICTABLE(expr) __builtin_unpredictable((expr)) #endif #if \ JSON_HEDLEY_HAS_BUILTIN(__builtin_expect_with_probability) || \ JSON_HEDLEY_GCC_VERSION_CHECK(9,0,0) # define JSON_HEDLEY_PREDICT(expr, value, probability) __builtin_expect_with_probability( (expr), (value), (probability)) # define JSON_HEDLEY_PREDICT_TRUE(expr, probability) __builtin_expect_with_probability(!!(expr), 1 , (probability)) # define JSON_HEDLEY_PREDICT_FALSE(expr, probability) __builtin_expect_with_probability(!!(expr), 0 , (probability)) # define JSON_HEDLEY_LIKELY(expr) __builtin_expect (!!(expr), 1 ) # define JSON_HEDLEY_UNLIKELY(expr) __builtin_expect (!!(expr), 0 ) #elif \ JSON_HEDLEY_HAS_BUILTIN(__builtin_expect) || \ JSON_HEDLEY_GCC_VERSION_CHECK(3,0,0) || \ JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ (JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,15,0) && defined(__cplusplus)) || \ JSON_HEDLEY_ARM_VERSION_CHECK(4,1,0) || \ JSON_HEDLEY_IBM_VERSION_CHECK(10,1,0) || \ JSON_HEDLEY_TI_VERSION_CHECK(15,12,0) || \ JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(4,7,0) || \ JSON_HEDLEY_TI_CL430_VERSION_CHECK(3,1,0) || \ JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,1,0) || \ JSON_HEDLEY_TI_CL6X_VERSION_CHECK(6,1,0) || \ JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \ JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(2,1,0) || \ JSON_HEDLEY_TINYC_VERSION_CHECK(0,9,27) || \ JSON_HEDLEY_CRAY_VERSION_CHECK(8,1,0) # define JSON_HEDLEY_PREDICT(expr, expected, probability) \ (((probability) >= 0.9) ? __builtin_expect((expr), (expected)) : (JSON_HEDLEY_STATIC_CAST(void, expected), (expr))) # define JSON_HEDLEY_PREDICT_TRUE(expr, probability) \ (__extension__ ({ \ double hedley_probability_ = (probability); \ ((hedley_probability_ >= 0.9) ? __builtin_expect(!!(expr), 1) : ((hedley_probability_ <= 0.1) ? __builtin_expect(!!(expr), 0) : !!(expr))); \ })) # define JSON_HEDLEY_PREDICT_FALSE(expr, probability) \ (__extension__ ({ \ double hedley_probability_ = (probability); \ ((hedley_probability_ >= 0.9) ? __builtin_expect(!!(expr), 0) : ((hedley_probability_ <= 0.1) ? __builtin_expect(!!(expr), 1) : !!(expr))); \ })) # define JSON_HEDLEY_LIKELY(expr) __builtin_expect(!!(expr), 1) # define JSON_HEDLEY_UNLIKELY(expr) __builtin_expect(!!(expr), 0) #else # define JSON_HEDLEY_PREDICT(expr, expected, probability) (JSON_HEDLEY_STATIC_CAST(void, expected), (expr)) # define JSON_HEDLEY_PREDICT_TRUE(expr, probability) (!!(expr)) # define JSON_HEDLEY_PREDICT_FALSE(expr, probability) (!!(expr)) # define JSON_HEDLEY_LIKELY(expr) (!!(expr)) # define JSON_HEDLEY_UNLIKELY(expr) (!!(expr)) #endif #if !defined(JSON_HEDLEY_UNPREDICTABLE) #define JSON_HEDLEY_UNPREDICTABLE(expr) JSON_HEDLEY_PREDICT(expr, 1, 0.5) #endif #if defined(JSON_HEDLEY_MALLOC) #undef JSON_HEDLEY_MALLOC #endif #if \ JSON_HEDLEY_HAS_ATTRIBUTE(malloc) || \ JSON_HEDLEY_GCC_VERSION_CHECK(3,1,0) || \ JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,11,0) || \ JSON_HEDLEY_ARM_VERSION_CHECK(4,1,0) || \ JSON_HEDLEY_IBM_VERSION_CHECK(12,1,0) || \ JSON_HEDLEY_TI_VERSION_CHECK(15,12,0) || \ (JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(4,8,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(5,2,0) || \ (JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,4,0) || \ (JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,3,0) || \ (JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,2,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,5,0) || \ JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \ JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(2,1,0) #define JSON_HEDLEY_MALLOC __attribute__((__malloc__)) #elif JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,10,0) #define JSON_HEDLEY_MALLOC _Pragma("returns_new_memory") #elif JSON_HEDLEY_MSVC_VERSION_CHECK(14, 0, 0) #define JSON_HEDLEY_MALLOC __declspec(restrict) #else #define JSON_HEDLEY_MALLOC #endif #if defined(JSON_HEDLEY_PURE) #undef JSON_HEDLEY_PURE #endif #if \ JSON_HEDLEY_HAS_ATTRIBUTE(pure) || \ JSON_HEDLEY_GCC_VERSION_CHECK(2,96,0) || \ JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,11,0) || \ JSON_HEDLEY_ARM_VERSION_CHECK(4,1,0) || \ JSON_HEDLEY_IBM_VERSION_CHECK(10,1,0) || \ JSON_HEDLEY_TI_VERSION_CHECK(15,12,0) || \ (JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(4,8,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(5,2,0) || \ (JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,4,0) || \ (JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,3,0) || \ (JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,2,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,5,0) || \ JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \ JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(2,1,0) || \ JSON_HEDLEY_PGI_VERSION_CHECK(17,10,0) # define JSON_HEDLEY_PURE __attribute__((__pure__)) #elif JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,10,0) # define JSON_HEDLEY_PURE _Pragma("does_not_write_global_data") #elif defined(__cplusplus) && \ ( \ JSON_HEDLEY_TI_CL430_VERSION_CHECK(2,0,1) || \ JSON_HEDLEY_TI_CL6X_VERSION_CHECK(4,0,0) || \ JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) \ ) # define JSON_HEDLEY_PURE _Pragma("FUNC_IS_PURE;") #else # define JSON_HEDLEY_PURE #endif #if defined(JSON_HEDLEY_CONST) #undef JSON_HEDLEY_CONST #endif #if \ JSON_HEDLEY_HAS_ATTRIBUTE(const) || \ JSON_HEDLEY_GCC_VERSION_CHECK(2,5,0) || \ JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,11,0) || \ JSON_HEDLEY_ARM_VERSION_CHECK(4,1,0) || \ JSON_HEDLEY_IBM_VERSION_CHECK(10,1,0) || \ JSON_HEDLEY_TI_VERSION_CHECK(15,12,0) || \ (JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(4,8,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(5,2,0) || \ (JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,4,0) || \ (JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,3,0) || \ (JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,2,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,5,0) || \ JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \ JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(2,1,0) || \ JSON_HEDLEY_PGI_VERSION_CHECK(17,10,0) #define JSON_HEDLEY_CONST __attribute__((__const__)) #elif \ JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,10,0) #define JSON_HEDLEY_CONST _Pragma("no_side_effect") #else #define JSON_HEDLEY_CONST JSON_HEDLEY_PURE #endif #if defined(JSON_HEDLEY_RESTRICT) #undef JSON_HEDLEY_RESTRICT #endif #if defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L) && !defined(__cplusplus) #define JSON_HEDLEY_RESTRICT restrict #elif \ JSON_HEDLEY_GCC_VERSION_CHECK(3,1,0) || \ JSON_HEDLEY_MSVC_VERSION_CHECK(14,0,0) || \ JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ JSON_HEDLEY_ARM_VERSION_CHECK(4,1,0) || \ JSON_HEDLEY_IBM_VERSION_CHECK(10,1,0) || \ JSON_HEDLEY_PGI_VERSION_CHECK(17,10,0) || \ JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,3,0) || \ JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,2,4) || \ JSON_HEDLEY_TI_CL6X_VERSION_CHECK(8,1,0) || \ JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \ (JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,14,0) && defined(__cplusplus)) || \ JSON_HEDLEY_IAR_VERSION_CHECK(8,0,0) || \ defined(__clang__) #define JSON_HEDLEY_RESTRICT __restrict #elif JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,3,0) && !defined(__cplusplus) #define JSON_HEDLEY_RESTRICT _Restrict #else #define JSON_HEDLEY_RESTRICT #endif #if defined(JSON_HEDLEY_INLINE) #undef JSON_HEDLEY_INLINE #endif #if \ (defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L)) || \ (defined(__cplusplus) && (__cplusplus >= 199711L)) #define JSON_HEDLEY_INLINE inline #elif \ defined(JSON_HEDLEY_GCC_VERSION) || \ JSON_HEDLEY_ARM_VERSION_CHECK(6,2,0) #define JSON_HEDLEY_INLINE __inline__ #elif \ JSON_HEDLEY_MSVC_VERSION_CHECK(12,0,0) || \ JSON_HEDLEY_ARM_VERSION_CHECK(4,1,0) || \ JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(5,1,0) || \ JSON_HEDLEY_TI_CL430_VERSION_CHECK(3,1,0) || \ JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,2,0) || \ JSON_HEDLEY_TI_CL6X_VERSION_CHECK(8,0,0) || \ JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \ JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(2,1,0) #define JSON_HEDLEY_INLINE __inline #else #define JSON_HEDLEY_INLINE #endif #if defined(JSON_HEDLEY_ALWAYS_INLINE) #undef JSON_HEDLEY_ALWAYS_INLINE #endif #if \ JSON_HEDLEY_HAS_ATTRIBUTE(always_inline) || \ JSON_HEDLEY_GCC_VERSION_CHECK(4,0,0) || \ JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,11,0) || \ JSON_HEDLEY_ARM_VERSION_CHECK(4,1,0) || \ JSON_HEDLEY_IBM_VERSION_CHECK(10,1,0) || \ JSON_HEDLEY_TI_VERSION_CHECK(15,12,0) || \ (JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(4,8,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(5,2,0) || \ (JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,4,0) || \ (JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,3,0) || \ (JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,2,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,5,0) || \ JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \ JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(2,1,0) # define JSON_HEDLEY_ALWAYS_INLINE __attribute__((__always_inline__)) JSON_HEDLEY_INLINE #elif JSON_HEDLEY_MSVC_VERSION_CHECK(12,0,0) # define JSON_HEDLEY_ALWAYS_INLINE __forceinline #elif defined(__cplusplus) && \ ( \ JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(5,2,0) || \ JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,3,0) || \ JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,4,0) || \ JSON_HEDLEY_TI_CL6X_VERSION_CHECK(6,1,0) || \ JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \ JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(2,1,0) \ ) # define JSON_HEDLEY_ALWAYS_INLINE _Pragma("FUNC_ALWAYS_INLINE;") #elif JSON_HEDLEY_IAR_VERSION_CHECK(8,0,0) # define JSON_HEDLEY_ALWAYS_INLINE _Pragma("inline=forced") #else # define JSON_HEDLEY_ALWAYS_INLINE JSON_HEDLEY_INLINE #endif #if defined(JSON_HEDLEY_NEVER_INLINE) #undef JSON_HEDLEY_NEVER_INLINE #endif #if \ JSON_HEDLEY_HAS_ATTRIBUTE(noinline) || \ JSON_HEDLEY_GCC_VERSION_CHECK(4,0,0) || \ JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,11,0) || \ JSON_HEDLEY_ARM_VERSION_CHECK(4,1,0) || \ JSON_HEDLEY_IBM_VERSION_CHECK(10,1,0) || \ JSON_HEDLEY_TI_VERSION_CHECK(15,12,0) || \ (JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(4,8,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(5,2,0) || \ (JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,4,0) || \ (JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,3,0) || \ (JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,2,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,5,0) || \ JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \ JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(2,1,0) #define JSON_HEDLEY_NEVER_INLINE __attribute__((__noinline__)) #elif JSON_HEDLEY_MSVC_VERSION_CHECK(13,10,0) #define JSON_HEDLEY_NEVER_INLINE __declspec(noinline) #elif JSON_HEDLEY_PGI_VERSION_CHECK(10,2,0) #define JSON_HEDLEY_NEVER_INLINE _Pragma("noinline") #elif JSON_HEDLEY_TI_CL6X_VERSION_CHECK(6,0,0) && defined(__cplusplus) #define JSON_HEDLEY_NEVER_INLINE _Pragma("FUNC_CANNOT_INLINE;") #elif JSON_HEDLEY_IAR_VERSION_CHECK(8,0,0) #define JSON_HEDLEY_NEVER_INLINE _Pragma("inline=never") #elif JSON_HEDLEY_COMPCERT_VERSION_CHECK(3,2,0) #define JSON_HEDLEY_NEVER_INLINE __attribute((noinline)) #elif JSON_HEDLEY_PELLES_VERSION_CHECK(9,0,0) #define JSON_HEDLEY_NEVER_INLINE __declspec(noinline) #else #define JSON_HEDLEY_NEVER_INLINE #endif #if defined(JSON_HEDLEY_PRIVATE) #undef JSON_HEDLEY_PRIVATE #endif #if defined(JSON_HEDLEY_PUBLIC) #undef JSON_HEDLEY_PUBLIC #endif #if defined(JSON_HEDLEY_IMPORT) #undef JSON_HEDLEY_IMPORT #endif #if defined(_WIN32) || defined(__CYGWIN__) # define JSON_HEDLEY_PRIVATE # define JSON_HEDLEY_PUBLIC __declspec(dllexport) # define JSON_HEDLEY_IMPORT __declspec(dllimport) #else # if \ JSON_HEDLEY_HAS_ATTRIBUTE(visibility) || \ JSON_HEDLEY_GCC_VERSION_CHECK(3,3,0) || \ JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,11,0) || \ JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ JSON_HEDLEY_ARM_VERSION_CHECK(4,1,0) || \ JSON_HEDLEY_IBM_VERSION_CHECK(13,1,0) || \ ( \ defined(__TI_EABI__) && \ ( \ (JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,2,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,5,0) \ ) \ ) # define JSON_HEDLEY_PRIVATE __attribute__((__visibility__("hidden"))) # define JSON_HEDLEY_PUBLIC __attribute__((__visibility__("default"))) # else # define JSON_HEDLEY_PRIVATE # define JSON_HEDLEY_PUBLIC # endif # define JSON_HEDLEY_IMPORT extern #endif #if defined(JSON_HEDLEY_NO_THROW) #undef JSON_HEDLEY_NO_THROW #endif #if \ JSON_HEDLEY_HAS_ATTRIBUTE(nothrow) || \ JSON_HEDLEY_GCC_VERSION_CHECK(3,3,0) || \ JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) #define JSON_HEDLEY_NO_THROW __attribute__((__nothrow__)) #elif \ JSON_HEDLEY_MSVC_VERSION_CHECK(13,1,0) || \ JSON_HEDLEY_ARM_VERSION_CHECK(4,1,0) #define JSON_HEDLEY_NO_THROW __declspec(nothrow) #else #define JSON_HEDLEY_NO_THROW #endif #if defined(JSON_HEDLEY_FALL_THROUGH) #undef JSON_HEDLEY_FALL_THROUGH #endif #if \ JSON_HEDLEY_HAS_ATTRIBUTE(fallthrough) || \ JSON_HEDLEY_GCC_VERSION_CHECK(7,0,0) #define JSON_HEDLEY_FALL_THROUGH __attribute__((__fallthrough__)) #elif JSON_HEDLEY_HAS_CPP_ATTRIBUTE_NS(clang,fallthrough) #define JSON_HEDLEY_FALL_THROUGH JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_([[clang::fallthrough]]) #elif JSON_HEDLEY_HAS_CPP_ATTRIBUTE(fallthrough) #define JSON_HEDLEY_FALL_THROUGH JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_([[fallthrough]]) #elif defined(__fallthrough) /* SAL */ #define JSON_HEDLEY_FALL_THROUGH __fallthrough #else #define JSON_HEDLEY_FALL_THROUGH #endif #if defined(JSON_HEDLEY_RETURNS_NON_NULL) #undef JSON_HEDLEY_RETURNS_NON_NULL #endif #if \ JSON_HEDLEY_HAS_ATTRIBUTE(returns_nonnull) || \ JSON_HEDLEY_GCC_VERSION_CHECK(4,9,0) #define JSON_HEDLEY_RETURNS_NON_NULL __attribute__((__returns_nonnull__)) #elif defined(_Ret_notnull_) /* SAL */ #define JSON_HEDLEY_RETURNS_NON_NULL _Ret_notnull_ #else #define JSON_HEDLEY_RETURNS_NON_NULL #endif #if defined(JSON_HEDLEY_ARRAY_PARAM) #undef JSON_HEDLEY_ARRAY_PARAM #endif #if \ defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L) && \ !defined(__STDC_NO_VLA__) && \ !defined(__cplusplus) && \ !defined(JSON_HEDLEY_PGI_VERSION) && \ !defined(JSON_HEDLEY_TINYC_VERSION) #define JSON_HEDLEY_ARRAY_PARAM(name) (name) #else #define JSON_HEDLEY_ARRAY_PARAM(name) #endif #if defined(JSON_HEDLEY_IS_CONSTANT) #undef JSON_HEDLEY_IS_CONSTANT #endif #if defined(JSON_HEDLEY_REQUIRE_CONSTEXPR) #undef JSON_HEDLEY_REQUIRE_CONSTEXPR #endif /* JSON_HEDLEY_IS_CONSTEXPR_ is for HEDLEY INTERNAL USE ONLY. API subject to change without notice. */ #if defined(JSON_HEDLEY_IS_CONSTEXPR_) #undef JSON_HEDLEY_IS_CONSTEXPR_ #endif #if \ JSON_HEDLEY_HAS_BUILTIN(__builtin_constant_p) || \ JSON_HEDLEY_GCC_VERSION_CHECK(3,4,0) || \ JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ JSON_HEDLEY_TINYC_VERSION_CHECK(0,9,19) || \ JSON_HEDLEY_ARM_VERSION_CHECK(4,1,0) || \ JSON_HEDLEY_IBM_VERSION_CHECK(13,1,0) || \ JSON_HEDLEY_TI_CL6X_VERSION_CHECK(6,1,0) || \ (JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,10,0) && !defined(__cplusplus)) || \ JSON_HEDLEY_CRAY_VERSION_CHECK(8,1,0) #define JSON_HEDLEY_IS_CONSTANT(expr) __builtin_constant_p(expr) #endif #if !defined(__cplusplus) # if \ JSON_HEDLEY_HAS_BUILTIN(__builtin_types_compatible_p) || \ JSON_HEDLEY_GCC_VERSION_CHECK(3,4,0) || \ JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ JSON_HEDLEY_IBM_VERSION_CHECK(13,1,0) || \ JSON_HEDLEY_CRAY_VERSION_CHECK(8,1,0) || \ JSON_HEDLEY_ARM_VERSION_CHECK(5,4,0) || \ JSON_HEDLEY_TINYC_VERSION_CHECK(0,9,24) #if defined(__INTPTR_TYPE__) #define JSON_HEDLEY_IS_CONSTEXPR_(expr) __builtin_types_compatible_p(__typeof__((1 ? (void*) ((__INTPTR_TYPE__) ((expr) * 0)) : (int*) 0)), int*) #else #include #define JSON_HEDLEY_IS_CONSTEXPR_(expr) __builtin_types_compatible_p(__typeof__((1 ? (void*) ((intptr_t) ((expr) * 0)) : (int*) 0)), int*) #endif # elif \ ( \ defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 201112L) && \ !defined(JSON_HEDLEY_SUNPRO_VERSION) && \ !defined(JSON_HEDLEY_PGI_VERSION) && \ !defined(JSON_HEDLEY_IAR_VERSION)) || \ JSON_HEDLEY_HAS_EXTENSION(c_generic_selections) || \ JSON_HEDLEY_GCC_VERSION_CHECK(4,9,0) || \ JSON_HEDLEY_INTEL_VERSION_CHECK(17,0,0) || \ JSON_HEDLEY_IBM_VERSION_CHECK(12,1,0) || \ JSON_HEDLEY_ARM_VERSION_CHECK(5,3,0) #if defined(__INTPTR_TYPE__) #define JSON_HEDLEY_IS_CONSTEXPR_(expr) _Generic((1 ? (void*) ((__INTPTR_TYPE__) ((expr) * 0)) : (int*) 0), int*: 1, void*: 0) #else #include #define JSON_HEDLEY_IS_CONSTEXPR_(expr) _Generic((1 ? (void*) ((intptr_t) * 0) : (int*) 0), int*: 1, void*: 0) #endif # elif \ defined(JSON_HEDLEY_GCC_VERSION) || \ defined(JSON_HEDLEY_INTEL_VERSION) || \ defined(JSON_HEDLEY_TINYC_VERSION) || \ defined(JSON_HEDLEY_TI_ARMCL_VERSION) || \ JSON_HEDLEY_TI_CL430_VERSION_CHECK(18,12,0) || \ defined(JSON_HEDLEY_TI_CL2000_VERSION) || \ defined(JSON_HEDLEY_TI_CL6X_VERSION) || \ defined(JSON_HEDLEY_TI_CL7X_VERSION) || \ defined(JSON_HEDLEY_TI_CLPRU_VERSION) || \ defined(__clang__) # define JSON_HEDLEY_IS_CONSTEXPR_(expr) ( \ sizeof(void) != \ sizeof(*( \ 1 ? \ ((void*) ((expr) * 0L) ) : \ ((struct { char v[sizeof(void) * 2]; } *) 1) \ ) \ ) \ ) # endif #endif #if defined(JSON_HEDLEY_IS_CONSTEXPR_) #if !defined(JSON_HEDLEY_IS_CONSTANT) #define JSON_HEDLEY_IS_CONSTANT(expr) JSON_HEDLEY_IS_CONSTEXPR_(expr) #endif #define JSON_HEDLEY_REQUIRE_CONSTEXPR(expr) (JSON_HEDLEY_IS_CONSTEXPR_(expr) ? (expr) : (-1)) #else #if !defined(JSON_HEDLEY_IS_CONSTANT) #define JSON_HEDLEY_IS_CONSTANT(expr) (0) #endif #define JSON_HEDLEY_REQUIRE_CONSTEXPR(expr) (expr) #endif #if defined(JSON_HEDLEY_BEGIN_C_DECLS) #undef JSON_HEDLEY_BEGIN_C_DECLS #endif #if defined(JSON_HEDLEY_END_C_DECLS) #undef JSON_HEDLEY_END_C_DECLS #endif #if defined(JSON_HEDLEY_C_DECL) #undef JSON_HEDLEY_C_DECL #endif #if defined(__cplusplus) #define JSON_HEDLEY_BEGIN_C_DECLS extern "C" { #define JSON_HEDLEY_END_C_DECLS } #define JSON_HEDLEY_C_DECL extern "C" #else #define JSON_HEDLEY_BEGIN_C_DECLS #define JSON_HEDLEY_END_C_DECLS #define JSON_HEDLEY_C_DECL #endif #if defined(JSON_HEDLEY_STATIC_ASSERT) #undef JSON_HEDLEY_STATIC_ASSERT #endif #if \ !defined(__cplusplus) && ( \ (defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 201112L)) || \ JSON_HEDLEY_HAS_FEATURE(c_static_assert) || \ JSON_HEDLEY_GCC_VERSION_CHECK(6,0,0) || \ JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ defined(_Static_assert) \ ) # define JSON_HEDLEY_STATIC_ASSERT(expr, message) _Static_assert(expr, message) #elif \ (defined(__cplusplus) && (__cplusplus >= 201103L)) || \ JSON_HEDLEY_MSVC_VERSION_CHECK(16,0,0) # define JSON_HEDLEY_STATIC_ASSERT(expr, message) JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_(static_assert(expr, message)) #else # define JSON_HEDLEY_STATIC_ASSERT(expr, message) #endif #if defined(JSON_HEDLEY_NULL) #undef JSON_HEDLEY_NULL #endif #if defined(__cplusplus) #if __cplusplus >= 201103L #define JSON_HEDLEY_NULL JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_(nullptr) #elif defined(NULL) #define JSON_HEDLEY_NULL NULL #else #define JSON_HEDLEY_NULL JSON_HEDLEY_STATIC_CAST(void*, 0) #endif #elif defined(NULL) #define JSON_HEDLEY_NULL NULL #else #define JSON_HEDLEY_NULL ((void*) 0) #endif #if defined(JSON_HEDLEY_MESSAGE) #undef JSON_HEDLEY_MESSAGE #endif #if JSON_HEDLEY_HAS_WARNING("-Wunknown-pragmas") # define JSON_HEDLEY_MESSAGE(msg) \ JSON_HEDLEY_DIAGNOSTIC_PUSH \ JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_PRAGMAS \ JSON_HEDLEY_PRAGMA(message msg) \ JSON_HEDLEY_DIAGNOSTIC_POP #elif \ JSON_HEDLEY_GCC_VERSION_CHECK(4,4,0) || \ JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) # define JSON_HEDLEY_MESSAGE(msg) JSON_HEDLEY_PRAGMA(message msg) #elif JSON_HEDLEY_CRAY_VERSION_CHECK(5,0,0) # define JSON_HEDLEY_MESSAGE(msg) JSON_HEDLEY_PRAGMA(_CRI message msg) #elif JSON_HEDLEY_IAR_VERSION_CHECK(8,0,0) # define JSON_HEDLEY_MESSAGE(msg) JSON_HEDLEY_PRAGMA(message(msg)) #elif JSON_HEDLEY_PELLES_VERSION_CHECK(2,0,0) # define JSON_HEDLEY_MESSAGE(msg) JSON_HEDLEY_PRAGMA(message(msg)) #else # define JSON_HEDLEY_MESSAGE(msg) #endif #if defined(JSON_HEDLEY_WARNING) #undef JSON_HEDLEY_WARNING #endif #if JSON_HEDLEY_HAS_WARNING("-Wunknown-pragmas") # define JSON_HEDLEY_WARNING(msg) \ JSON_HEDLEY_DIAGNOSTIC_PUSH \ JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_PRAGMAS \ JSON_HEDLEY_PRAGMA(clang warning msg) \ JSON_HEDLEY_DIAGNOSTIC_POP #elif \ JSON_HEDLEY_GCC_VERSION_CHECK(4,8,0) || \ JSON_HEDLEY_PGI_VERSION_CHECK(18,4,0) || \ JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) # define JSON_HEDLEY_WARNING(msg) JSON_HEDLEY_PRAGMA(GCC warning msg) #elif JSON_HEDLEY_MSVC_VERSION_CHECK(15,0,0) # define JSON_HEDLEY_WARNING(msg) JSON_HEDLEY_PRAGMA(message(msg)) #else # define JSON_HEDLEY_WARNING(msg) JSON_HEDLEY_MESSAGE(msg) #endif #if defined(JSON_HEDLEY_REQUIRE) #undef JSON_HEDLEY_REQUIRE #endif #if defined(JSON_HEDLEY_REQUIRE_MSG) #undef JSON_HEDLEY_REQUIRE_MSG #endif #if JSON_HEDLEY_HAS_ATTRIBUTE(diagnose_if) # if JSON_HEDLEY_HAS_WARNING("-Wgcc-compat") # define JSON_HEDLEY_REQUIRE(expr) \ JSON_HEDLEY_DIAGNOSTIC_PUSH \ _Pragma("clang diagnostic ignored \"-Wgcc-compat\"") \ __attribute__((diagnose_if(!(expr), #expr, "error"))) \ JSON_HEDLEY_DIAGNOSTIC_POP # define JSON_HEDLEY_REQUIRE_MSG(expr,msg) \ JSON_HEDLEY_DIAGNOSTIC_PUSH \ _Pragma("clang diagnostic ignored \"-Wgcc-compat\"") \ __attribute__((diagnose_if(!(expr), msg, "error"))) \ JSON_HEDLEY_DIAGNOSTIC_POP # else # define JSON_HEDLEY_REQUIRE(expr) __attribute__((diagnose_if(!(expr), #expr, "error"))) # define JSON_HEDLEY_REQUIRE_MSG(expr,msg) __attribute__((diagnose_if(!(expr), msg, "error"))) # endif #else # define JSON_HEDLEY_REQUIRE(expr) # define JSON_HEDLEY_REQUIRE_MSG(expr,msg) #endif #if defined(JSON_HEDLEY_FLAGS) #undef JSON_HEDLEY_FLAGS #endif #if JSON_HEDLEY_HAS_ATTRIBUTE(flag_enum) #define JSON_HEDLEY_FLAGS __attribute__((__flag_enum__)) #endif #if defined(JSON_HEDLEY_FLAGS_CAST) #undef JSON_HEDLEY_FLAGS_CAST #endif #if JSON_HEDLEY_INTEL_VERSION_CHECK(19,0,0) # define JSON_HEDLEY_FLAGS_CAST(T, expr) (__extension__ ({ \ JSON_HEDLEY_DIAGNOSTIC_PUSH \ _Pragma("warning(disable:188)") \ ((T) (expr)); \ JSON_HEDLEY_DIAGNOSTIC_POP \ })) #else # define JSON_HEDLEY_FLAGS_CAST(T, expr) JSON_HEDLEY_STATIC_CAST(T, expr) #endif #if defined(JSON_HEDLEY_EMPTY_BASES) #undef JSON_HEDLEY_EMPTY_BASES #endif #if JSON_HEDLEY_MSVC_VERSION_CHECK(19,0,23918) && !JSON_HEDLEY_MSVC_VERSION_CHECK(20,0,0) #define JSON_HEDLEY_EMPTY_BASES __declspec(empty_bases) #else #define JSON_HEDLEY_EMPTY_BASES #endif /* Remaining macros are deprecated. */ #if defined(JSON_HEDLEY_GCC_NOT_CLANG_VERSION_CHECK) #undef JSON_HEDLEY_GCC_NOT_CLANG_VERSION_CHECK #endif #if defined(__clang__) #define JSON_HEDLEY_GCC_NOT_CLANG_VERSION_CHECK(major,minor,patch) (0) #else #define JSON_HEDLEY_GCC_NOT_CLANG_VERSION_CHECK(major,minor,patch) JSON_HEDLEY_GCC_VERSION_CHECK(major,minor,patch) #endif #if defined(JSON_HEDLEY_CLANG_HAS_ATTRIBUTE) #undef JSON_HEDLEY_CLANG_HAS_ATTRIBUTE #endif #define JSON_HEDLEY_CLANG_HAS_ATTRIBUTE(attribute) JSON_HEDLEY_HAS_ATTRIBUTE(attribute) #if defined(JSON_HEDLEY_CLANG_HAS_CPP_ATTRIBUTE) #undef JSON_HEDLEY_CLANG_HAS_CPP_ATTRIBUTE #endif #define JSON_HEDLEY_CLANG_HAS_CPP_ATTRIBUTE(attribute) JSON_HEDLEY_HAS_CPP_ATTRIBUTE(attribute) #if defined(JSON_HEDLEY_CLANG_HAS_BUILTIN) #undef JSON_HEDLEY_CLANG_HAS_BUILTIN #endif #define JSON_HEDLEY_CLANG_HAS_BUILTIN(builtin) JSON_HEDLEY_HAS_BUILTIN(builtin) #if defined(JSON_HEDLEY_CLANG_HAS_FEATURE) #undef JSON_HEDLEY_CLANG_HAS_FEATURE #endif #define JSON_HEDLEY_CLANG_HAS_FEATURE(feature) JSON_HEDLEY_HAS_FEATURE(feature) #if defined(JSON_HEDLEY_CLANG_HAS_EXTENSION) #undef JSON_HEDLEY_CLANG_HAS_EXTENSION #endif #define JSON_HEDLEY_CLANG_HAS_EXTENSION(extension) JSON_HEDLEY_HAS_EXTENSION(extension) #if defined(JSON_HEDLEY_CLANG_HAS_DECLSPEC_DECLSPEC_ATTRIBUTE) #undef JSON_HEDLEY_CLANG_HAS_DECLSPEC_DECLSPEC_ATTRIBUTE #endif #define JSON_HEDLEY_CLANG_HAS_DECLSPEC_ATTRIBUTE(attribute) JSON_HEDLEY_HAS_DECLSPEC_ATTRIBUTE(attribute) #if defined(JSON_HEDLEY_CLANG_HAS_WARNING) #undef JSON_HEDLEY_CLANG_HAS_WARNING #endif #define JSON_HEDLEY_CLANG_HAS_WARNING(warning) JSON_HEDLEY_HAS_WARNING(warning) #endif /* !defined(JSON_HEDLEY_VERSION) || (JSON_HEDLEY_VERSION < X) */ // This file contains all internal macro definitions // You MUST include macro_unscope.hpp at the end of json.hpp to undef all of them // exclude unsupported compilers #if !defined(JSON_SKIP_UNSUPPORTED_COMPILER_CHECK) #if defined(__clang__) #if (__clang_major__ * 10000 + __clang_minor__ * 100 + __clang_patchlevel__) < 30400 #error "unsupported Clang version - see https://github.com/nlohmann/json#supported-compilers" #endif #elif defined(__GNUC__) && !(defined(__ICC) || defined(__INTEL_COMPILER)) #if (__GNUC__ * 10000 + __GNUC_MINOR__ * 100 + __GNUC_PATCHLEVEL__) < 40800 #error "unsupported GCC version - see https://github.com/nlohmann/json#supported-compilers" #endif #endif #endif // C++ language standard detection #if (defined(__cplusplus) && __cplusplus >= 202002L) || (defined(_MSVC_LANG) && _MSVC_LANG >= 202002L) #define JSON_HAS_CPP_20 #define JSON_HAS_CPP_17 #define JSON_HAS_CPP_14 #elif (defined(__cplusplus) && __cplusplus >= 201703L) || (defined(_HAS_CXX17) && _HAS_CXX17 == 1) // fix for issue #464 #define JSON_HAS_CPP_17 #define JSON_HAS_CPP_14 #elif (defined(__cplusplus) && __cplusplus >= 201402L) || (defined(_HAS_CXX14) && _HAS_CXX14 == 1) #define JSON_HAS_CPP_14 #endif // disable float-equal warnings on GCC/clang #if defined(__clang__) || defined(__GNUC__) || defined(__GNUG__) #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wfloat-equal" #endif // disable documentation warnings on clang #if defined(__clang__) #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wdocumentation" #endif // allow to disable exceptions #if (defined(__cpp_exceptions) || defined(__EXCEPTIONS) || defined(_CPPUNWIND)) && !defined(JSON_NOEXCEPTION) #define JSON_THROW(exception) throw exception #define JSON_TRY try #define JSON_CATCH(exception) catch(exception) #define JSON_INTERNAL_CATCH(exception) catch(exception) #else #include #define JSON_THROW(exception) std::abort() #define JSON_TRY if(true) #define JSON_CATCH(exception) if(false) #define JSON_INTERNAL_CATCH(exception) if(false) #endif // override exception macros #if defined(JSON_THROW_USER) #undef JSON_THROW #define JSON_THROW JSON_THROW_USER #endif #if defined(JSON_TRY_USER) #undef JSON_TRY #define JSON_TRY JSON_TRY_USER #endif #if defined(JSON_CATCH_USER) #undef JSON_CATCH #define JSON_CATCH JSON_CATCH_USER #undef JSON_INTERNAL_CATCH #define JSON_INTERNAL_CATCH JSON_CATCH_USER #endif #if defined(JSON_INTERNAL_CATCH_USER) #undef JSON_INTERNAL_CATCH #define JSON_INTERNAL_CATCH JSON_INTERNAL_CATCH_USER #endif // allow to override assert #if !defined(JSON_ASSERT) #include // assert #define JSON_ASSERT(x) assert(x) #endif /*! @brief macro to briefly define a mapping between an enum and JSON @def NLOHMANN_JSON_SERIALIZE_ENUM @since version 3.4.0 */ #define NLOHMANN_JSON_SERIALIZE_ENUM(ENUM_TYPE, ...) \ template \ inline void to_json(BasicJsonType& j, const ENUM_TYPE& e) \ { \ static_assert(std::is_enum::value, #ENUM_TYPE " must be an enum!"); \ static const std::pair m[] = __VA_ARGS__; \ auto it = std::find_if(std::begin(m), std::end(m), \ [e](const std::pair& ej_pair) -> bool \ { \ return ej_pair.first == e; \ }); \ j = ((it != std::end(m)) ? it : std::begin(m))->second; \ } \ template \ inline void from_json(const BasicJsonType& j, ENUM_TYPE& e) \ { \ static_assert(std::is_enum::value, #ENUM_TYPE " must be an enum!"); \ static const std::pair m[] = __VA_ARGS__; \ auto it = std::find_if(std::begin(m), std::end(m), \ [&j](const std::pair& ej_pair) -> bool \ { \ return ej_pair.second == j; \ }); \ e = ((it != std::end(m)) ? it : std::begin(m))->first; \ } // Ugly macros to avoid uglier copy-paste when specializing basic_json. They // may be removed in the future once the class is split. #define NLOHMANN_BASIC_JSON_TPL_DECLARATION \ template class ObjectType, \ template class ArrayType, \ class StringType, class BooleanType, class NumberIntegerType, \ class NumberUnsignedType, class NumberFloatType, \ template class AllocatorType, \ template class JSONSerializer, \ class BinaryType> #define NLOHMANN_BASIC_JSON_TPL \ basic_json // Macros to simplify conversion from/to types #define NLOHMANN_JSON_EXPAND( x ) x #define NLOHMANN_JSON_GET_MACRO(_1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _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, _50, _51, _52, _53, _54, _55, _56, _57, _58, _59, _60, _61, _62, _63, _64, NAME,...) NAME #define NLOHMANN_JSON_PASTE(...) NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_GET_MACRO(__VA_ARGS__, \ NLOHMANN_JSON_PASTE64, \ NLOHMANN_JSON_PASTE63, \ NLOHMANN_JSON_PASTE62, \ NLOHMANN_JSON_PASTE61, \ NLOHMANN_JSON_PASTE60, \ NLOHMANN_JSON_PASTE59, \ NLOHMANN_JSON_PASTE58, \ NLOHMANN_JSON_PASTE57, \ NLOHMANN_JSON_PASTE56, \ NLOHMANN_JSON_PASTE55, \ NLOHMANN_JSON_PASTE54, \ NLOHMANN_JSON_PASTE53, \ NLOHMANN_JSON_PASTE52, \ NLOHMANN_JSON_PASTE51, \ NLOHMANN_JSON_PASTE50, \ NLOHMANN_JSON_PASTE49, \ NLOHMANN_JSON_PASTE48, \ NLOHMANN_JSON_PASTE47, \ NLOHMANN_JSON_PASTE46, \ NLOHMANN_JSON_PASTE45, \ NLOHMANN_JSON_PASTE44, \ NLOHMANN_JSON_PASTE43, \ NLOHMANN_JSON_PASTE42, \ NLOHMANN_JSON_PASTE41, \ NLOHMANN_JSON_PASTE40, \ NLOHMANN_JSON_PASTE39, \ NLOHMANN_JSON_PASTE38, \ NLOHMANN_JSON_PASTE37, \ NLOHMANN_JSON_PASTE36, \ NLOHMANN_JSON_PASTE35, \ NLOHMANN_JSON_PASTE34, \ NLOHMANN_JSON_PASTE33, \ NLOHMANN_JSON_PASTE32, \ NLOHMANN_JSON_PASTE31, \ NLOHMANN_JSON_PASTE30, \ NLOHMANN_JSON_PASTE29, \ NLOHMANN_JSON_PASTE28, \ NLOHMANN_JSON_PASTE27, \ NLOHMANN_JSON_PASTE26, \ NLOHMANN_JSON_PASTE25, \ NLOHMANN_JSON_PASTE24, \ NLOHMANN_JSON_PASTE23, \ NLOHMANN_JSON_PASTE22, \ NLOHMANN_JSON_PASTE21, \ NLOHMANN_JSON_PASTE20, \ NLOHMANN_JSON_PASTE19, \ NLOHMANN_JSON_PASTE18, \ NLOHMANN_JSON_PASTE17, \ NLOHMANN_JSON_PASTE16, \ NLOHMANN_JSON_PASTE15, \ NLOHMANN_JSON_PASTE14, \ NLOHMANN_JSON_PASTE13, \ NLOHMANN_JSON_PASTE12, \ NLOHMANN_JSON_PASTE11, \ NLOHMANN_JSON_PASTE10, \ NLOHMANN_JSON_PASTE9, \ NLOHMANN_JSON_PASTE8, \ NLOHMANN_JSON_PASTE7, \ NLOHMANN_JSON_PASTE6, \ NLOHMANN_JSON_PASTE5, \ NLOHMANN_JSON_PASTE4, \ NLOHMANN_JSON_PASTE3, \ NLOHMANN_JSON_PASTE2, \ NLOHMANN_JSON_PASTE1)(__VA_ARGS__)) #define NLOHMANN_JSON_PASTE2(func, v1) func(v1) #define NLOHMANN_JSON_PASTE3(func, v1, v2) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE2(func, v2) #define NLOHMANN_JSON_PASTE4(func, v1, v2, v3) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE3(func, v2, v3) #define NLOHMANN_JSON_PASTE5(func, v1, v2, v3, v4) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE4(func, v2, v3, v4) #define NLOHMANN_JSON_PASTE6(func, v1, v2, v3, v4, v5) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE5(func, v2, v3, v4, v5) #define NLOHMANN_JSON_PASTE7(func, v1, v2, v3, v4, v5, v6) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE6(func, v2, v3, v4, v5, v6) #define NLOHMANN_JSON_PASTE8(func, v1, v2, v3, v4, v5, v6, v7) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE7(func, v2, v3, v4, v5, v6, v7) #define NLOHMANN_JSON_PASTE9(func, v1, v2, v3, v4, v5, v6, v7, v8) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE8(func, v2, v3, v4, v5, v6, v7, v8) #define NLOHMANN_JSON_PASTE10(func, v1, v2, v3, v4, v5, v6, v7, v8, v9) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE9(func, v2, v3, v4, v5, v6, v7, v8, v9) #define NLOHMANN_JSON_PASTE11(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE10(func, v2, v3, v4, v5, v6, v7, v8, v9, v10) #define NLOHMANN_JSON_PASTE12(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE11(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11) #define NLOHMANN_JSON_PASTE13(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE12(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12) #define NLOHMANN_JSON_PASTE14(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE13(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13) #define NLOHMANN_JSON_PASTE15(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE14(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14) #define NLOHMANN_JSON_PASTE16(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE15(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15) #define NLOHMANN_JSON_PASTE17(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE16(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16) #define NLOHMANN_JSON_PASTE18(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE17(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17) #define NLOHMANN_JSON_PASTE19(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE18(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18) #define NLOHMANN_JSON_PASTE20(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE19(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19) #define NLOHMANN_JSON_PASTE21(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE20(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20) #define NLOHMANN_JSON_PASTE22(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE21(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21) #define NLOHMANN_JSON_PASTE23(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE22(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22) #define NLOHMANN_JSON_PASTE24(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE23(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23) #define NLOHMANN_JSON_PASTE25(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE24(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24) #define NLOHMANN_JSON_PASTE26(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE25(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25) #define NLOHMANN_JSON_PASTE27(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE26(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26) #define NLOHMANN_JSON_PASTE28(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE27(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27) #define NLOHMANN_JSON_PASTE29(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE28(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28) #define NLOHMANN_JSON_PASTE30(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE29(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29) #define NLOHMANN_JSON_PASTE31(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE30(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30) #define NLOHMANN_JSON_PASTE32(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE31(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31) #define NLOHMANN_JSON_PASTE33(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE32(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32) #define NLOHMANN_JSON_PASTE34(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE33(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33) #define NLOHMANN_JSON_PASTE35(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE34(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34) #define NLOHMANN_JSON_PASTE36(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE35(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35) #define NLOHMANN_JSON_PASTE37(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE36(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36) #define NLOHMANN_JSON_PASTE38(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE37(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37) #define NLOHMANN_JSON_PASTE39(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE38(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38) #define NLOHMANN_JSON_PASTE40(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE39(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39) #define NLOHMANN_JSON_PASTE41(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE40(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40) #define NLOHMANN_JSON_PASTE42(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE41(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41) #define NLOHMANN_JSON_PASTE43(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE42(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42) #define NLOHMANN_JSON_PASTE44(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE43(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43) #define NLOHMANN_JSON_PASTE45(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE44(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44) #define NLOHMANN_JSON_PASTE46(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE45(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45) #define NLOHMANN_JSON_PASTE47(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE46(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46) #define NLOHMANN_JSON_PASTE48(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE47(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47) #define NLOHMANN_JSON_PASTE49(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE48(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48) #define NLOHMANN_JSON_PASTE50(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE49(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49) #define NLOHMANN_JSON_PASTE51(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE50(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50) #define NLOHMANN_JSON_PASTE52(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE51(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51) #define NLOHMANN_JSON_PASTE53(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE52(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52) #define NLOHMANN_JSON_PASTE54(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE53(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53) #define NLOHMANN_JSON_PASTE55(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE54(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54) #define NLOHMANN_JSON_PASTE56(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE55(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55) #define NLOHMANN_JSON_PASTE57(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55, v56) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE56(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55, v56) #define NLOHMANN_JSON_PASTE58(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55, v56, v57) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE57(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55, v56, v57) #define NLOHMANN_JSON_PASTE59(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55, v56, v57, v58) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE58(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55, v56, v57, v58) #define NLOHMANN_JSON_PASTE60(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55, v56, v57, v58, v59) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE59(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55, v56, v57, v58, v59) #define NLOHMANN_JSON_PASTE61(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55, v56, v57, v58, v59, v60) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE60(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55, v56, v57, v58, v59, v60) #define NLOHMANN_JSON_PASTE62(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55, v56, v57, v58, v59, v60, v61) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE61(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55, v56, v57, v58, v59, v60, v61) #define NLOHMANN_JSON_PASTE63(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55, v56, v57, v58, v59, v60, v61, v62) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE62(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55, v56, v57, v58, v59, v60, v61, v62) #define NLOHMANN_JSON_PASTE64(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55, v56, v57, v58, v59, v60, v61, v62, v63) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE63(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55, v56, v57, v58, v59, v60, v61, v62, v63) #define NLOHMANN_JSON_TO(v1) nlohmann_json_j[#v1] = nlohmann_json_t.v1; #define NLOHMANN_JSON_FROM(v1) nlohmann_json_j.at(#v1).get_to(nlohmann_json_t.v1); /*! @brief macro @def NLOHMANN_DEFINE_TYPE_INTRUSIVE @since version 3.9.0 */ #define NLOHMANN_DEFINE_TYPE_INTRUSIVE(Type, ...) \ friend void to_json(nlohmann::json& nlohmann_json_j, const Type& nlohmann_json_t) { NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_TO, __VA_ARGS__)) } \ friend void from_json(const nlohmann::json& nlohmann_json_j, Type& nlohmann_json_t) { NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_FROM, __VA_ARGS__)) } /*! @brief macro @def NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE @since version 3.9.0 */ #define NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(Type, ...) \ inline void to_json(nlohmann::json& nlohmann_json_j, const Type& nlohmann_json_t) { NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_TO, __VA_ARGS__)) } \ inline void from_json(const nlohmann::json& nlohmann_json_j, Type& nlohmann_json_t) { NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_FROM, __VA_ARGS__)) } #ifndef JSON_USE_IMPLICIT_CONVERSIONS #define JSON_USE_IMPLICIT_CONVERSIONS 1 #endif #if JSON_USE_IMPLICIT_CONVERSIONS #define JSON_EXPLICIT #else #define JSON_EXPLICIT explicit #endif namespace nlohmann { namespace detail { //////////////// // exceptions // //////////////// /*! @brief general exception of the @ref basic_json class This class is an extension of `std::exception` objects with a member @a id for exception ids. It is used as the base class for all exceptions thrown by the @ref basic_json class. This class can hence be used as "wildcard" to catch exceptions. Subclasses: - @ref parse_error for exceptions indicating a parse error - @ref invalid_iterator for exceptions indicating errors with iterators - @ref type_error for exceptions indicating executing a member function with a wrong type - @ref out_of_range for exceptions indicating access out of the defined range - @ref other_error for exceptions indicating other library errors @internal @note To have nothrow-copy-constructible exceptions, we internally use `std::runtime_error` which can cope with arbitrary-length error messages. Intermediate strings are built with static functions and then passed to the actual constructor. @endinternal @liveexample{The following code shows how arbitrary library exceptions can be caught.,exception} @since version 3.0.0 */ class exception : public std::exception { public: /// returns the explanatory string JSON_HEDLEY_RETURNS_NON_NULL const char* what() const noexcept override { return m.what(); } /// the id of the exception const int id; protected: JSON_HEDLEY_NON_NULL(3) exception(int id_, const char* what_arg) : id(id_), m(what_arg) {} static std::string name(const std::string& ename, int id_) { return "[json.exception." + ename + "." + std::to_string(id_) + "] "; } private: /// an exception object as storage for error messages std::runtime_error m; }; /*! @brief exception indicating a parse error This exception is thrown by the library when a parse error occurs. Parse errors can occur during the deserialization of JSON text, CBOR, MessagePack, as well as when using JSON Patch. Member @a byte holds the byte index of the last read character in the input file. Exceptions have ids 1xx. name / id | example message | description ------------------------------ | --------------- | ------------------------- json.exception.parse_error.101 | parse error at 2: unexpected end of input; expected string literal | This error indicates a syntax error while deserializing a JSON text. The error message describes that an unexpected token (character) was encountered, and the member @a byte indicates the error position. json.exception.parse_error.102 | parse error at 14: missing or wrong low surrogate | JSON uses the `\uxxxx` format to describe Unicode characters. Code points above above 0xFFFF are split into two `\uxxxx` entries ("surrogate pairs"). This error indicates that the surrogate pair is incomplete or contains an invalid code point. json.exception.parse_error.103 | parse error: code points above 0x10FFFF are invalid | Unicode supports code points up to 0x10FFFF. Code points above 0x10FFFF are invalid. json.exception.parse_error.104 | parse error: JSON patch must be an array of objects | [RFC 6902](https://tools.ietf.org/html/rfc6902) requires a JSON Patch document to be a JSON document that represents an array of objects. json.exception.parse_error.105 | parse error: operation must have string member 'op' | An operation of a JSON Patch document must contain exactly one "op" member, whose value indicates the operation to perform. Its value must be one of "add", "remove", "replace", "move", "copy", or "test"; other values are errors. json.exception.parse_error.106 | parse error: array index '01' must not begin with '0' | An array index in a JSON Pointer ([RFC 6901](https://tools.ietf.org/html/rfc6901)) may be `0` or any number without a leading `0`. json.exception.parse_error.107 | parse error: JSON pointer must be empty or begin with '/' - was: 'foo' | A JSON Pointer must be a Unicode string containing a sequence of zero or more reference tokens, each prefixed by a `/` character. json.exception.parse_error.108 | parse error: escape character '~' must be followed with '0' or '1' | In a JSON Pointer, only `~0` and `~1` are valid escape sequences. json.exception.parse_error.109 | parse error: array index 'one' is not a number | A JSON Pointer array index must be a number. json.exception.parse_error.110 | parse error at 1: cannot read 2 bytes from vector | When parsing CBOR or MessagePack, the byte vector ends before the complete value has been read. json.exception.parse_error.112 | parse error at 1: error reading CBOR; last byte: 0xF8 | Not all types of CBOR or MessagePack are supported. This exception occurs if an unsupported byte was read. json.exception.parse_error.113 | parse error at 2: expected a CBOR string; last byte: 0x98 | While parsing a map key, a value that is not a string has been read. json.exception.parse_error.114 | parse error: Unsupported BSON record type 0x0F | The parsing of the corresponding BSON record type is not implemented (yet). json.exception.parse_error.115 | parse error at byte 5: syntax error while parsing UBJSON high-precision number: invalid number text: 1A | A UBJSON high-precision number could not be parsed. @note For an input with n bytes, 1 is the index of the first character and n+1 is the index of the terminating null byte or the end of file. This also holds true when reading a byte vector (CBOR or MessagePack). @liveexample{The following code shows how a `parse_error` exception can be caught.,parse_error} @sa - @ref exception for the base class of the library exceptions @sa - @ref invalid_iterator for exceptions indicating errors with iterators @sa - @ref type_error for exceptions indicating executing a member function with a wrong type @sa - @ref out_of_range for exceptions indicating access out of the defined range @sa - @ref other_error for exceptions indicating other library errors @since version 3.0.0 */ class parse_error : public exception { public: /*! @brief create a parse error exception @param[in] id_ the id of the exception @param[in] pos the position where the error occurred (or with chars_read_total=0 if the position cannot be determined) @param[in] what_arg the explanatory string @return parse_error object */ static parse_error create(int id_, const position_t& pos, const std::string& what_arg) { std::string w = exception::name("parse_error", id_) + "parse error" + position_string(pos) + ": " + what_arg; return parse_error(id_, pos.chars_read_total, w.c_str()); } static parse_error create(int id_, std::size_t byte_, const std::string& what_arg) { std::string w = exception::name("parse_error", id_) + "parse error" + (byte_ != 0 ? (" at byte " + std::to_string(byte_)) : "") + ": " + what_arg; return parse_error(id_, byte_, w.c_str()); } /*! @brief byte index of the parse error The byte index of the last read character in the input file. @note For an input with n bytes, 1 is the index of the first character and n+1 is the index of the terminating null byte or the end of file. This also holds true when reading a byte vector (CBOR or MessagePack). */ const std::size_t byte; private: parse_error(int id_, std::size_t byte_, const char* what_arg) : exception(id_, what_arg), byte(byte_) {} static std::string position_string(const position_t& pos) { return " at line " + std::to_string(pos.lines_read + 1) + ", column " + std::to_string(pos.chars_read_current_line); } }; /*! @brief exception indicating errors with iterators This exception is thrown if iterators passed to a library function do not match the expected semantics. Exceptions have ids 2xx. name / id | example message | description ----------------------------------- | --------------- | ------------------------- json.exception.invalid_iterator.201 | iterators are not compatible | The iterators passed to constructor @ref basic_json(InputIT first, InputIT last) are not compatible, meaning they do not belong to the same container. Therefore, the range (@a first, @a last) is invalid. json.exception.invalid_iterator.202 | iterator does not fit current value | In an erase or insert function, the passed iterator @a pos does not belong to the JSON value for which the function was called. It hence does not define a valid position for the deletion/insertion. json.exception.invalid_iterator.203 | iterators do not fit current value | Either iterator passed to function @ref erase(IteratorType first, IteratorType last) does not belong to the JSON value from which values shall be erased. It hence does not define a valid range to delete values from. json.exception.invalid_iterator.204 | iterators out of range | When an iterator range for a primitive type (number, boolean, or string) is passed to a constructor or an erase function, this range has to be exactly (@ref begin(), @ref end()), because this is the only way the single stored value is expressed. All other ranges are invalid. json.exception.invalid_iterator.205 | iterator out of range | When an iterator for a primitive type (number, boolean, or string) is passed to an erase function, the iterator has to be the @ref begin() iterator, because it is the only way to address the stored value. All other iterators are invalid. json.exception.invalid_iterator.206 | cannot construct with iterators from null | The iterators passed to constructor @ref basic_json(InputIT first, InputIT last) belong to a JSON null value and hence to not define a valid range. json.exception.invalid_iterator.207 | cannot use key() for non-object iterators | The key() member function can only be used on iterators belonging to a JSON object, because other types do not have a concept of a key. json.exception.invalid_iterator.208 | cannot use operator[] for object iterators | The operator[] to specify a concrete offset cannot be used on iterators belonging to a JSON object, because JSON objects are unordered. json.exception.invalid_iterator.209 | cannot use offsets with object iterators | The offset operators (+, -, +=, -=) cannot be used on iterators belonging to a JSON object, because JSON objects are unordered. json.exception.invalid_iterator.210 | iterators do not fit | The iterator range passed to the insert function are not compatible, meaning they do not belong to the same container. Therefore, the range (@a first, @a last) is invalid. json.exception.invalid_iterator.211 | passed iterators may not belong to container | The iterator range passed to the insert function must not be a subrange of the container to insert to. json.exception.invalid_iterator.212 | cannot compare iterators of different containers | When two iterators are compared, they must belong to the same container. json.exception.invalid_iterator.213 | cannot compare order of object iterators | The order of object iterators cannot be compared, because JSON objects are unordered. json.exception.invalid_iterator.214 | cannot get value | Cannot get value for iterator: Either the iterator belongs to a null value or it is an iterator to a primitive type (number, boolean, or string), but the iterator is different to @ref begin(). @liveexample{The following code shows how an `invalid_iterator` exception can be caught.,invalid_iterator} @sa - @ref exception for the base class of the library exceptions @sa - @ref parse_error for exceptions indicating a parse error @sa - @ref type_error for exceptions indicating executing a member function with a wrong type @sa - @ref out_of_range for exceptions indicating access out of the defined range @sa - @ref other_error for exceptions indicating other library errors @since version 3.0.0 */ class invalid_iterator : public exception { public: static invalid_iterator create(int id_, const std::string& what_arg) { std::string w = exception::name("invalid_iterator", id_) + what_arg; return invalid_iterator(id_, w.c_str()); } private: JSON_HEDLEY_NON_NULL(3) invalid_iterator(int id_, const char* what_arg) : exception(id_, what_arg) {} }; /*! @brief exception indicating executing a member function with a wrong type This exception is thrown in case of a type error; that is, a library function is executed on a JSON value whose type does not match the expected semantics. Exceptions have ids 3xx. name / id | example message | description ----------------------------- | --------------- | ------------------------- json.exception.type_error.301 | cannot create object from initializer list | To create an object from an initializer list, the initializer list must consist only of a list of pairs whose first element is a string. When this constraint is violated, an array is created instead. json.exception.type_error.302 | type must be object, but is array | During implicit or explicit value conversion, the JSON type must be compatible to the target type. For instance, a JSON string can only be converted into string types, but not into numbers or boolean types. json.exception.type_error.303 | incompatible ReferenceType for get_ref, actual type is object | To retrieve a reference to a value stored in a @ref basic_json object with @ref get_ref, the type of the reference must match the value type. For instance, for a JSON array, the @a ReferenceType must be @ref array_t &. json.exception.type_error.304 | cannot use at() with string | The @ref at() member functions can only be executed for certain JSON types. json.exception.type_error.305 | cannot use operator[] with string | The @ref operator[] member functions can only be executed for certain JSON types. json.exception.type_error.306 | cannot use value() with string | The @ref value() member functions can only be executed for certain JSON types. json.exception.type_error.307 | cannot use erase() with string | The @ref erase() member functions can only be executed for certain JSON types. json.exception.type_error.308 | cannot use push_back() with string | The @ref push_back() and @ref operator+= member functions can only be executed for certain JSON types. json.exception.type_error.309 | cannot use insert() with | The @ref insert() member functions can only be executed for certain JSON types. json.exception.type_error.310 | cannot use swap() with number | The @ref swap() member functions can only be executed for certain JSON types. json.exception.type_error.311 | cannot use emplace_back() with string | The @ref emplace_back() member function can only be executed for certain JSON types. json.exception.type_error.312 | cannot use update() with string | The @ref update() member functions can only be executed for certain JSON types. json.exception.type_error.313 | invalid value to unflatten | The @ref unflatten function converts an object whose keys are JSON Pointers back into an arbitrary nested JSON value. The JSON Pointers must not overlap, because then the resulting value would not be well defined. json.exception.type_error.314 | only objects can be unflattened | The @ref unflatten function only works for an object whose keys are JSON Pointers. json.exception.type_error.315 | values in object must be primitive | The @ref unflatten function only works for an object whose keys are JSON Pointers and whose values are primitive. json.exception.type_error.316 | invalid UTF-8 byte at index 10: 0x7E | The @ref dump function only works with UTF-8 encoded strings; that is, if you assign a `std::string` to a JSON value, make sure it is UTF-8 encoded. | json.exception.type_error.317 | JSON value cannot be serialized to requested format | The dynamic type of the object cannot be represented in the requested serialization format (e.g. a raw `true` or `null` JSON object cannot be serialized to BSON) | @liveexample{The following code shows how a `type_error` exception can be caught.,type_error} @sa - @ref exception for the base class of the library exceptions @sa - @ref parse_error for exceptions indicating a parse error @sa - @ref invalid_iterator for exceptions indicating errors with iterators @sa - @ref out_of_range for exceptions indicating access out of the defined range @sa - @ref other_error for exceptions indicating other library errors @since version 3.0.0 */ class type_error : public exception { public: static type_error create(int id_, const std::string& what_arg) { std::string w = exception::name("type_error", id_) + what_arg; return type_error(id_, w.c_str()); } private: JSON_HEDLEY_NON_NULL(3) type_error(int id_, const char* what_arg) : exception(id_, what_arg) {} }; /*! @brief exception indicating access out of the defined range This exception is thrown in case a library function is called on an input parameter that exceeds the expected range, for instance in case of array indices or nonexisting object keys. Exceptions have ids 4xx. name / id | example message | description ------------------------------- | --------------- | ------------------------- json.exception.out_of_range.401 | array index 3 is out of range | The provided array index @a i is larger than @a size-1. json.exception.out_of_range.402 | array index '-' (3) is out of range | The special array index `-` in a JSON Pointer never describes a valid element of the array, but the index past the end. That is, it can only be used to add elements at this position, but not to read it. json.exception.out_of_range.403 | key 'foo' not found | The provided key was not found in the JSON object. json.exception.out_of_range.404 | unresolved reference token 'foo' | A reference token in a JSON Pointer could not be resolved. json.exception.out_of_range.405 | JSON pointer has no parent | The JSON Patch operations 'remove' and 'add' can not be applied to the root element of the JSON value. json.exception.out_of_range.406 | number overflow parsing '10E1000' | A parsed number could not be stored as without changing it to NaN or INF. json.exception.out_of_range.407 | number overflow serializing '9223372036854775808' | UBJSON and BSON only support integer numbers up to 9223372036854775807. (until version 3.8.0) | json.exception.out_of_range.408 | excessive array size: 8658170730974374167 | The size (following `#`) of an UBJSON array or object exceeds the maximal capacity. | json.exception.out_of_range.409 | BSON key cannot contain code point U+0000 (at byte 2) | Key identifiers to be serialized to BSON cannot contain code point U+0000, since the key is stored as zero-terminated c-string | @liveexample{The following code shows how an `out_of_range` exception can be caught.,out_of_range} @sa - @ref exception for the base class of the library exceptions @sa - @ref parse_error for exceptions indicating a parse error @sa - @ref invalid_iterator for exceptions indicating errors with iterators @sa - @ref type_error for exceptions indicating executing a member function with a wrong type @sa - @ref other_error for exceptions indicating other library errors @since version 3.0.0 */ class out_of_range : public exception { public: static out_of_range create(int id_, const std::string& what_arg) { std::string w = exception::name("out_of_range", id_) + what_arg; return out_of_range(id_, w.c_str()); } private: JSON_HEDLEY_NON_NULL(3) out_of_range(int id_, const char* what_arg) : exception(id_, what_arg) {} }; /*! @brief exception indicating other library errors This exception is thrown in case of errors that cannot be classified with the other exception types. Exceptions have ids 5xx. name / id | example message | description ------------------------------ | --------------- | ------------------------- json.exception.other_error.501 | unsuccessful: {"op":"test","path":"/baz", "value":"bar"} | A JSON Patch operation 'test' failed. The unsuccessful operation is also printed. @sa - @ref exception for the base class of the library exceptions @sa - @ref parse_error for exceptions indicating a parse error @sa - @ref invalid_iterator for exceptions indicating errors with iterators @sa - @ref type_error for exceptions indicating executing a member function with a wrong type @sa - @ref out_of_range for exceptions indicating access out of the defined range @liveexample{The following code shows how an `other_error` exception can be caught.,other_error} @since version 3.0.0 */ class other_error : public exception { public: static other_error create(int id_, const std::string& what_arg) { std::string w = exception::name("other_error", id_) + what_arg; return other_error(id_, w.c_str()); } private: JSON_HEDLEY_NON_NULL(3) other_error(int id_, const char* what_arg) : exception(id_, what_arg) {} }; } // namespace detail } // namespace nlohmann // #include // #include #include // size_t #include // conditional, enable_if, false_type, integral_constant, is_constructible, is_integral, is_same, remove_cv, remove_reference, true_type namespace nlohmann { namespace detail { // alias templates to reduce boilerplate template using enable_if_t = typename std::enable_if::type; template using uncvref_t = typename std::remove_cv::type>::type; // implementation of C++14 index_sequence and affiliates // source: https://stackoverflow.com/a/32223343 template struct index_sequence { using type = index_sequence; using value_type = std::size_t; static constexpr std::size_t size() noexcept { return sizeof...(Ints); } }; template struct merge_and_renumber; template struct merge_and_renumber, index_sequence> : index_sequence < I1..., (sizeof...(I1) + I2)... > {}; template struct make_index_sequence : merge_and_renumber < typename make_index_sequence < N / 2 >::type, typename make_index_sequence < N - N / 2 >::type > {}; template<> struct make_index_sequence<0> : index_sequence<> {}; template<> struct make_index_sequence<1> : index_sequence<0> {}; template using index_sequence_for = make_index_sequence; // dispatch utility (taken from ranges-v3) template struct priority_tag : priority_tag < N - 1 > {}; template<> struct priority_tag<0> {}; // taken from ranges-v3 template struct static_const { static constexpr T value{}; }; template constexpr T static_const::value; } // namespace detail } // namespace nlohmann // #include #include // numeric_limits #include // false_type, is_constructible, is_integral, is_same, true_type #include // declval // #include #include // random_access_iterator_tag // #include namespace nlohmann { namespace detail { template struct make_void { using type = void; }; template using void_t = typename make_void::type; } // namespace detail } // namespace nlohmann // #include namespace nlohmann { namespace detail { template struct iterator_types {}; template struct iterator_types < It, void_t> { using difference_type = typename It::difference_type; using value_type = typename It::value_type; using pointer = typename It::pointer; using reference = typename It::reference; using iterator_category = typename It::iterator_category; }; // This is required as some compilers implement std::iterator_traits in a way that // doesn't work with SFINAE. See https://github.com/nlohmann/json/issues/1341. template struct iterator_traits { }; template struct iterator_traits < T, enable_if_t < !std::is_pointer::value >> : iterator_types { }; template struct iterator_traits::value>> { using iterator_category = std::random_access_iterator_tag; using value_type = T; using difference_type = ptrdiff_t; using pointer = T*; using reference = T&; }; } // namespace detail } // namespace nlohmann // #include // #include // #include #include // #include // https://en.cppreference.com/w/cpp/experimental/is_detected namespace nlohmann { namespace detail { struct nonesuch { nonesuch() = delete; ~nonesuch() = delete; nonesuch(nonesuch const&) = delete; nonesuch(nonesuch const&&) = delete; void operator=(nonesuch const&) = delete; void operator=(nonesuch&&) = delete; }; template class Op, class... Args> struct detector { using value_t = std::false_type; using type = Default; }; template class Op, class... Args> struct detector>, Op, Args...> { using value_t = std::true_type; using type = Op; }; template class Op, class... Args> using is_detected = typename detector::value_t; template class Op, class... Args> using detected_t = typename detector::type; template class Op, class... Args> using detected_or = detector; template class Op, class... Args> using detected_or_t = typename detected_or::type; template class Op, class... Args> using is_detected_exact = std::is_same>; template class Op, class... Args> using is_detected_convertible = std::is_convertible, To>; } // namespace detail } // namespace nlohmann // #include #ifndef INCLUDE_NLOHMANN_JSON_FWD_HPP_ #define INCLUDE_NLOHMANN_JSON_FWD_HPP_ #include // int64_t, uint64_t #include // map #include // allocator #include // string #include // vector /*! @brief namespace for Niels Lohmann @see https://github.com/nlohmann @since version 1.0.0 */ namespace nlohmann { /*! @brief default JSONSerializer template argument This serializer ignores the template arguments and uses ADL ([argument-dependent lookup](https://en.cppreference.com/w/cpp/language/adl)) for serialization. */ template struct adl_serializer; template class ObjectType = std::map, template class ArrayType = std::vector, class StringType = std::string, class BooleanType = bool, class NumberIntegerType = std::int64_t, class NumberUnsignedType = std::uint64_t, class NumberFloatType = double, template class AllocatorType = std::allocator, template class JSONSerializer = adl_serializer, class BinaryType = std::vector> class basic_json; /*! @brief JSON Pointer A JSON pointer defines a string syntax for identifying a specific value within a JSON document. It can be used with functions `at` and `operator[]`. Furthermore, JSON pointers are the base for JSON patches. @sa [RFC 6901](https://tools.ietf.org/html/rfc6901) @since version 2.0.0 */ template class json_pointer; /*! @brief default JSON class This type is the default specialization of the @ref basic_json class which uses the standard template types. @since version 1.0.0 */ using json = basic_json<>; template struct ordered_map; /*! @brief ordered JSON class This type preserves the insertion order of object keys. @since version 3.9.0 */ using ordered_json = basic_json; } // namespace nlohmann #endif // INCLUDE_NLOHMANN_JSON_FWD_HPP_ namespace nlohmann { /*! @brief detail namespace with internal helper functions This namespace collects functions that should not be exposed, implementations of some @ref basic_json methods, and meta-programming helpers. @since version 2.1.0 */ namespace detail { ///////////// // helpers // ///////////// // Note to maintainers: // // Every trait in this file expects a non CV-qualified type. // The only exceptions are in the 'aliases for detected' section // (i.e. those of the form: decltype(T::member_function(std::declval()))) // // In this case, T has to be properly CV-qualified to constraint the function arguments // (e.g. to_json(BasicJsonType&, const T&)) template struct is_basic_json : std::false_type {}; NLOHMANN_BASIC_JSON_TPL_DECLARATION struct is_basic_json : std::true_type {}; ////////////////////// // json_ref helpers // ////////////////////// template class json_ref; template struct is_json_ref : std::false_type {}; template struct is_json_ref> : std::true_type {}; ////////////////////////// // aliases for detected // ////////////////////////// template using mapped_type_t = typename T::mapped_type; template using key_type_t = typename T::key_type; template using value_type_t = typename T::value_type; template using difference_type_t = typename T::difference_type; template using pointer_t = typename T::pointer; template using reference_t = typename T::reference; template using iterator_category_t = typename T::iterator_category; template using iterator_t = typename T::iterator; template using to_json_function = decltype(T::to_json(std::declval()...)); template using from_json_function = decltype(T::from_json(std::declval()...)); template using get_template_function = decltype(std::declval().template get()); // trait checking if JSONSerializer::from_json(json const&, udt&) exists template struct has_from_json : std::false_type {}; // trait checking if j.get is valid // use this trait instead of std::is_constructible or std::is_convertible, // both rely on, or make use of implicit conversions, and thus fail when T // has several constructors/operator= (see https://github.com/nlohmann/json/issues/958) template struct is_getable { static constexpr bool value = is_detected::value; }; template struct has_from_json < BasicJsonType, T, enable_if_t < !is_basic_json::value >> { using serializer = typename BasicJsonType::template json_serializer; static constexpr bool value = is_detected_exact::value; }; // This trait checks if JSONSerializer::from_json(json const&) exists // this overload is used for non-default-constructible user-defined-types template struct has_non_default_from_json : std::false_type {}; template struct has_non_default_from_json < BasicJsonType, T, enable_if_t < !is_basic_json::value >> { using serializer = typename BasicJsonType::template json_serializer; static constexpr bool value = is_detected_exact::value; }; // This trait checks if BasicJsonType::json_serializer::to_json exists // Do not evaluate the trait when T is a basic_json type, to avoid template instantiation infinite recursion. template struct has_to_json : std::false_type {}; template struct has_to_json < BasicJsonType, T, enable_if_t < !is_basic_json::value >> { using serializer = typename BasicJsonType::template json_serializer; static constexpr bool value = is_detected_exact::value; }; /////////////////// // is_ functions // /////////////////// template struct is_iterator_traits : std::false_type {}; template struct is_iterator_traits> { private: using traits = iterator_traits; public: static constexpr auto value = is_detected::value && is_detected::value && is_detected::value && is_detected::value && is_detected::value; }; // source: https://stackoverflow.com/a/37193089/4116453 template struct is_complete_type : std::false_type {}; template struct is_complete_type : std::true_type {}; template struct is_compatible_object_type_impl : std::false_type {}; template struct is_compatible_object_type_impl < BasicJsonType, CompatibleObjectType, enable_if_t < is_detected::value&& is_detected::value >> { using object_t = typename BasicJsonType::object_t; // macOS's is_constructible does not play well with nonesuch... static constexpr bool value = std::is_constructible::value && std::is_constructible::value; }; template struct is_compatible_object_type : is_compatible_object_type_impl {}; template struct is_constructible_object_type_impl : std::false_type {}; template struct is_constructible_object_type_impl < BasicJsonType, ConstructibleObjectType, enable_if_t < is_detected::value&& is_detected::value >> { using object_t = typename BasicJsonType::object_t; static constexpr bool value = (std::is_default_constructible::value && (std::is_move_assignable::value || std::is_copy_assignable::value) && (std::is_constructible::value && std::is_same < typename object_t::mapped_type, typename ConstructibleObjectType::mapped_type >::value)) || (has_from_json::value || has_non_default_from_json < BasicJsonType, typename ConstructibleObjectType::mapped_type >::value); }; template struct is_constructible_object_type : is_constructible_object_type_impl {}; template struct is_compatible_string_type_impl : std::false_type {}; template struct is_compatible_string_type_impl < BasicJsonType, CompatibleStringType, enable_if_t::value >> { static constexpr auto value = std::is_constructible::value; }; template struct is_compatible_string_type : is_compatible_string_type_impl {}; template struct is_constructible_string_type_impl : std::false_type {}; template struct is_constructible_string_type_impl < BasicJsonType, ConstructibleStringType, enable_if_t::value >> { static constexpr auto value = std::is_constructible::value; }; template struct is_constructible_string_type : is_constructible_string_type_impl {}; template struct is_compatible_array_type_impl : std::false_type {}; template struct is_compatible_array_type_impl < BasicJsonType, CompatibleArrayType, enable_if_t < is_detected::value&& is_detected::value&& // This is needed because json_reverse_iterator has a ::iterator type... // Therefore it is detected as a CompatibleArrayType. // The real fix would be to have an Iterable concept. !is_iterator_traits < iterator_traits>::value >> { static constexpr bool value = std::is_constructible::value; }; template struct is_compatible_array_type : is_compatible_array_type_impl {}; template struct is_constructible_array_type_impl : std::false_type {}; template struct is_constructible_array_type_impl < BasicJsonType, ConstructibleArrayType, enable_if_t::value >> : std::true_type {}; template struct is_constructible_array_type_impl < BasicJsonType, ConstructibleArrayType, enable_if_t < !std::is_same::value&& std::is_default_constructible::value&& (std::is_move_assignable::value || std::is_copy_assignable::value)&& is_detected::value&& is_detected::value&& is_complete_type < detected_t>::value >> { static constexpr bool value = // This is needed because json_reverse_iterator has a ::iterator type, // furthermore, std::back_insert_iterator (and other iterators) have a // base class `iterator`... Therefore it is detected as a // ConstructibleArrayType. The real fix would be to have an Iterable // concept. !is_iterator_traits>::value && (std::is_same::value || has_from_json::value || has_non_default_from_json < BasicJsonType, typename ConstructibleArrayType::value_type >::value); }; template struct is_constructible_array_type : is_constructible_array_type_impl {}; template struct is_compatible_integer_type_impl : std::false_type {}; template struct is_compatible_integer_type_impl < RealIntegerType, CompatibleNumberIntegerType, enable_if_t < std::is_integral::value&& std::is_integral::value&& !std::is_same::value >> { // is there an assert somewhere on overflows? using RealLimits = std::numeric_limits; using CompatibleLimits = std::numeric_limits; static constexpr auto value = std::is_constructible::value && CompatibleLimits::is_integer && RealLimits::is_signed == CompatibleLimits::is_signed; }; template struct is_compatible_integer_type : is_compatible_integer_type_impl {}; template struct is_compatible_type_impl: std::false_type {}; template struct is_compatible_type_impl < BasicJsonType, CompatibleType, enable_if_t::value >> { static constexpr bool value = has_to_json::value; }; template struct is_compatible_type : is_compatible_type_impl {}; // https://en.cppreference.com/w/cpp/types/conjunction template struct conjunction : std::true_type { }; template struct conjunction : B1 { }; template struct conjunction : std::conditional, B1>::type {}; template struct is_constructible_tuple : std::false_type {}; template struct is_constructible_tuple> : conjunction...> {}; } // namespace detail } // namespace nlohmann // #include #include // array #include // size_t #include // uint8_t #include // string namespace nlohmann { namespace detail { /////////////////////////// // JSON type enumeration // /////////////////////////// /*! @brief the JSON type enumeration This enumeration collects the different JSON types. It is internally used to distinguish the stored values, and the functions @ref basic_json::is_null(), @ref basic_json::is_object(), @ref basic_json::is_array(), @ref basic_json::is_string(), @ref basic_json::is_boolean(), @ref basic_json::is_number() (with @ref basic_json::is_number_integer(), @ref basic_json::is_number_unsigned(), and @ref basic_json::is_number_float()), @ref basic_json::is_discarded(), @ref basic_json::is_primitive(), and @ref basic_json::is_structured() rely on it. @note There are three enumeration entries (number_integer, number_unsigned, and number_float), because the library distinguishes these three types for numbers: @ref basic_json::number_unsigned_t is used for unsigned integers, @ref basic_json::number_integer_t is used for signed integers, and @ref basic_json::number_float_t is used for floating-point numbers or to approximate integers which do not fit in the limits of their respective type. @sa @ref basic_json::basic_json(const value_t value_type) -- create a JSON value with the default value for a given type @since version 1.0.0 */ enum class value_t : std::uint8_t { null, ///< null value object, ///< object (unordered set of name/value pairs) array, ///< array (ordered collection of values) string, ///< string value boolean, ///< boolean value number_integer, ///< number value (signed integer) number_unsigned, ///< number value (unsigned integer) number_float, ///< number value (floating-point) binary, ///< binary array (ordered collection of bytes) discarded ///< discarded by the parser callback function }; /*! @brief comparison operator for JSON types Returns an ordering that is similar to Python: - order: null < boolean < number < object < array < string < binary - furthermore, each type is not smaller than itself - discarded values are not comparable - binary is represented as a b"" string in python and directly comparable to a string; however, making a binary array directly comparable with a string would be surprising behavior in a JSON file. @since version 1.0.0 */ inline bool operator<(const value_t lhs, const value_t rhs) noexcept { static constexpr std::array order = {{ 0 /* null */, 3 /* object */, 4 /* array */, 5 /* string */, 1 /* boolean */, 2 /* integer */, 2 /* unsigned */, 2 /* float */, 6 /* binary */ } }; const auto l_index = static_cast(lhs); const auto r_index = static_cast(rhs); return l_index < order.size() && r_index < order.size() && order[l_index] < order[r_index]; } } // namespace detail } // namespace nlohmann namespace nlohmann { namespace detail { template void from_json(const BasicJsonType& j, typename std::nullptr_t& n) { if (JSON_HEDLEY_UNLIKELY(!j.is_null())) { JSON_THROW(type_error::create(302, "type must be null, but is " + std::string(j.type_name()))); } n = nullptr; } // overloads for basic_json template parameters template < typename BasicJsonType, typename ArithmeticType, enable_if_t < std::is_arithmetic::value&& !std::is_same::value, int > = 0 > void get_arithmetic_value(const BasicJsonType& j, ArithmeticType& val) { switch (static_cast(j)) { case value_t::number_unsigned: { val = static_cast(*j.template get_ptr()); break; } case value_t::number_integer: { val = static_cast(*j.template get_ptr()); break; } case value_t::number_float: { val = static_cast(*j.template get_ptr()); break; } default: JSON_THROW(type_error::create(302, "type must be number, but is " + std::string(j.type_name()))); } } template void from_json(const BasicJsonType& j, typename BasicJsonType::boolean_t& b) { if (JSON_HEDLEY_UNLIKELY(!j.is_boolean())) { JSON_THROW(type_error::create(302, "type must be boolean, but is " + std::string(j.type_name()))); } b = *j.template get_ptr(); } template void from_json(const BasicJsonType& j, typename BasicJsonType::string_t& s) { if (JSON_HEDLEY_UNLIKELY(!j.is_string())) { JSON_THROW(type_error::create(302, "type must be string, but is " + std::string(j.type_name()))); } s = *j.template get_ptr(); } template < typename BasicJsonType, typename ConstructibleStringType, enable_if_t < is_constructible_string_type::value&& !std::is_same::value, int > = 0 > void from_json(const BasicJsonType& j, ConstructibleStringType& s) { if (JSON_HEDLEY_UNLIKELY(!j.is_string())) { JSON_THROW(type_error::create(302, "type must be string, but is " + std::string(j.type_name()))); } s = *j.template get_ptr(); } template void from_json(const BasicJsonType& j, typename BasicJsonType::number_float_t& val) { get_arithmetic_value(j, val); } template void from_json(const BasicJsonType& j, typename BasicJsonType::number_unsigned_t& val) { get_arithmetic_value(j, val); } template void from_json(const BasicJsonType& j, typename BasicJsonType::number_integer_t& val) { get_arithmetic_value(j, val); } template::value, int> = 0> void from_json(const BasicJsonType& j, EnumType& e) { typename std::underlying_type::type val; get_arithmetic_value(j, val); e = static_cast(val); } // forward_list doesn't have an insert method template::value, int> = 0> void from_json(const BasicJsonType& j, std::forward_list& l) { if (JSON_HEDLEY_UNLIKELY(!j.is_array())) { JSON_THROW(type_error::create(302, "type must be array, but is " + std::string(j.type_name()))); } l.clear(); std::transform(j.rbegin(), j.rend(), std::front_inserter(l), [](const BasicJsonType & i) { return i.template get(); }); } // valarray doesn't have an insert method template::value, int> = 0> void from_json(const BasicJsonType& j, std::valarray& l) { if (JSON_HEDLEY_UNLIKELY(!j.is_array())) { JSON_THROW(type_error::create(302, "type must be array, but is " + std::string(j.type_name()))); } l.resize(j.size()); std::transform(j.begin(), j.end(), std::begin(l), [](const BasicJsonType & elem) { return elem.template get(); }); } template auto from_json(const BasicJsonType& j, T (&arr)[N]) -> decltype(j.template get(), void()) { for (std::size_t i = 0; i < N; ++i) { arr[i] = j.at(i).template get(); } } template void from_json_array_impl(const BasicJsonType& j, typename BasicJsonType::array_t& arr, priority_tag<3> /*unused*/) { arr = *j.template get_ptr(); } template auto from_json_array_impl(const BasicJsonType& j, std::array& arr, priority_tag<2> /*unused*/) -> decltype(j.template get(), void()) { for (std::size_t i = 0; i < N; ++i) { arr[i] = j.at(i).template get(); } } template auto from_json_array_impl(const BasicJsonType& j, ConstructibleArrayType& arr, priority_tag<1> /*unused*/) -> decltype( arr.reserve(std::declval()), j.template get(), void()) { using std::end; ConstructibleArrayType ret; ret.reserve(j.size()); std::transform(j.begin(), j.end(), std::inserter(ret, end(ret)), [](const BasicJsonType & i) { // get() returns *this, this won't call a from_json // method when value_type is BasicJsonType return i.template get(); }); arr = std::move(ret); } template void from_json_array_impl(const BasicJsonType& j, ConstructibleArrayType& arr, priority_tag<0> /*unused*/) { using std::end; ConstructibleArrayType ret; std::transform( j.begin(), j.end(), std::inserter(ret, end(ret)), [](const BasicJsonType & i) { // get() returns *this, this won't call a from_json // method when value_type is BasicJsonType return i.template get(); }); arr = std::move(ret); } template < typename BasicJsonType, typename ConstructibleArrayType, enable_if_t < is_constructible_array_type::value&& !is_constructible_object_type::value&& !is_constructible_string_type::value&& !std::is_same::value&& !is_basic_json::value, int > = 0 > auto from_json(const BasicJsonType& j, ConstructibleArrayType& arr) -> decltype(from_json_array_impl(j, arr, priority_tag<3> {}), j.template get(), void()) { if (JSON_HEDLEY_UNLIKELY(!j.is_array())) { JSON_THROW(type_error::create(302, "type must be array, but is " + std::string(j.type_name()))); } from_json_array_impl(j, arr, priority_tag<3> {}); } template void from_json(const BasicJsonType& j, typename BasicJsonType::binary_t& bin) { if (JSON_HEDLEY_UNLIKELY(!j.is_binary())) { JSON_THROW(type_error::create(302, "type must be binary, but is " + std::string(j.type_name()))); } bin = *j.template get_ptr(); } template::value, int> = 0> void from_json(const BasicJsonType& j, ConstructibleObjectType& obj) { if (JSON_HEDLEY_UNLIKELY(!j.is_object())) { JSON_THROW(type_error::create(302, "type must be object, but is " + std::string(j.type_name()))); } ConstructibleObjectType ret; auto inner_object = j.template get_ptr(); using value_type = typename ConstructibleObjectType::value_type; std::transform( inner_object->begin(), inner_object->end(), std::inserter(ret, ret.begin()), [](typename BasicJsonType::object_t::value_type const & p) { return value_type(p.first, p.second.template get()); }); obj = std::move(ret); } // overload for arithmetic types, not chosen for basic_json template arguments // (BooleanType, etc..); note: Is it really necessary to provide explicit // overloads for boolean_t etc. in case of a custom BooleanType which is not // an arithmetic type? template < typename BasicJsonType, typename ArithmeticType, enable_if_t < std::is_arithmetic::value&& !std::is_same::value&& !std::is_same::value&& !std::is_same::value&& !std::is_same::value, int > = 0 > void from_json(const BasicJsonType& j, ArithmeticType& val) { switch (static_cast(j)) { case value_t::number_unsigned: { val = static_cast(*j.template get_ptr()); break; } case value_t::number_integer: { val = static_cast(*j.template get_ptr()); break; } case value_t::number_float: { val = static_cast(*j.template get_ptr()); break; } case value_t::boolean: { val = static_cast(*j.template get_ptr()); break; } default: JSON_THROW(type_error::create(302, "type must be number, but is " + std::string(j.type_name()))); } } template void from_json(const BasicJsonType& j, std::pair& p) { p = {j.at(0).template get(), j.at(1).template get()}; } template void from_json_tuple_impl(const BasicJsonType& j, Tuple& t, index_sequence /*unused*/) { t = std::make_tuple(j.at(Idx).template get::type>()...); } template void from_json(const BasicJsonType& j, std::tuple& t) { from_json_tuple_impl(j, t, index_sequence_for {}); } template < typename BasicJsonType, typename Key, typename Value, typename Compare, typename Allocator, typename = enable_if_t < !std::is_constructible < typename BasicJsonType::string_t, Key >::value >> void from_json(const BasicJsonType& j, std::map& m) { if (JSON_HEDLEY_UNLIKELY(!j.is_array())) { JSON_THROW(type_error::create(302, "type must be array, but is " + std::string(j.type_name()))); } m.clear(); for (const auto& p : j) { if (JSON_HEDLEY_UNLIKELY(!p.is_array())) { JSON_THROW(type_error::create(302, "type must be array, but is " + std::string(p.type_name()))); } m.emplace(p.at(0).template get(), p.at(1).template get()); } } template < typename BasicJsonType, typename Key, typename Value, typename Hash, typename KeyEqual, typename Allocator, typename = enable_if_t < !std::is_constructible < typename BasicJsonType::string_t, Key >::value >> void from_json(const BasicJsonType& j, std::unordered_map& m) { if (JSON_HEDLEY_UNLIKELY(!j.is_array())) { JSON_THROW(type_error::create(302, "type must be array, but is " + std::string(j.type_name()))); } m.clear(); for (const auto& p : j) { if (JSON_HEDLEY_UNLIKELY(!p.is_array())) { JSON_THROW(type_error::create(302, "type must be array, but is " + std::string(p.type_name()))); } m.emplace(p.at(0).template get(), p.at(1).template get()); } } struct from_json_fn { template auto operator()(const BasicJsonType& j, T& val) const noexcept(noexcept(from_json(j, val))) -> decltype(from_json(j, val), void()) { return from_json(j, val); } }; } // namespace detail /// namespace to hold default `from_json` function /// to see why this is required: /// http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2015/n4381.html namespace { constexpr const auto& from_json = detail::static_const::value; } // namespace } // namespace nlohmann // #include #include // copy #include // begin, end #include // string #include // tuple, get #include // is_same, is_constructible, is_floating_point, is_enum, underlying_type #include // move, forward, declval, pair #include // valarray #include // vector // #include #include // size_t #include // input_iterator_tag #include // string, to_string #include // tuple_size, get, tuple_element // #include // #include namespace nlohmann { namespace detail { template void int_to_string( string_type& target, std::size_t value ) { // For ADL using std::to_string; target = to_string(value); } template class iteration_proxy_value { public: using difference_type = std::ptrdiff_t; using value_type = iteration_proxy_value; using pointer = value_type * ; using reference = value_type & ; using iterator_category = std::input_iterator_tag; using string_type = typename std::remove_cv< typename std::remove_reference().key() ) >::type >::type; private: /// the iterator IteratorType anchor; /// an index for arrays (used to create key names) std::size_t array_index = 0; /// last stringified array index mutable std::size_t array_index_last = 0; /// a string representation of the array index mutable string_type array_index_str = "0"; /// an empty string (to return a reference for primitive values) const string_type empty_str = ""; public: explicit iteration_proxy_value(IteratorType it) noexcept : anchor(it) {} /// dereference operator (needed for range-based for) iteration_proxy_value& operator*() { return *this; } /// increment operator (needed for range-based for) iteration_proxy_value& operator++() { ++anchor; ++array_index; return *this; } /// equality operator (needed for InputIterator) bool operator==(const iteration_proxy_value& o) const { return anchor == o.anchor; } /// inequality operator (needed for range-based for) bool operator!=(const iteration_proxy_value& o) const { return anchor != o.anchor; } /// return key of the iterator const string_type& key() const { JSON_ASSERT(anchor.m_object != nullptr); switch (anchor.m_object->type()) { // use integer array index as key case value_t::array: { if (array_index != array_index_last) { int_to_string( array_index_str, array_index ); array_index_last = array_index; } return array_index_str; } // use key from the object case value_t::object: return anchor.key(); // use an empty key for all primitive types default: return empty_str; } } /// return value of the iterator typename IteratorType::reference value() const { return anchor.value(); } }; /// proxy class for the items() function template class iteration_proxy { private: /// the container to iterate typename IteratorType::reference container; public: /// construct iteration proxy from a container explicit iteration_proxy(typename IteratorType::reference cont) noexcept : container(cont) {} /// return iterator begin (needed for range-based for) iteration_proxy_value begin() noexcept { return iteration_proxy_value(container.begin()); } /// return iterator end (needed for range-based for) iteration_proxy_value end() noexcept { return iteration_proxy_value(container.end()); } }; // Structured Bindings Support // For further reference see https://blog.tartanllama.xyz/structured-bindings/ // And see https://github.com/nlohmann/json/pull/1391 template = 0> auto get(const nlohmann::detail::iteration_proxy_value& i) -> decltype(i.key()) { return i.key(); } // Structured Bindings Support // For further reference see https://blog.tartanllama.xyz/structured-bindings/ // And see https://github.com/nlohmann/json/pull/1391 template = 0> auto get(const nlohmann::detail::iteration_proxy_value& i) -> decltype(i.value()) { return i.value(); } } // namespace detail } // namespace nlohmann // The Addition to the STD Namespace is required to add // Structured Bindings Support to the iteration_proxy_value class // For further reference see https://blog.tartanllama.xyz/structured-bindings/ // And see https://github.com/nlohmann/json/pull/1391 namespace std { #if defined(__clang__) // Fix: https://github.com/nlohmann/json/issues/1401 #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wmismatched-tags" #endif template class tuple_size<::nlohmann::detail::iteration_proxy_value> : public std::integral_constant {}; template class tuple_element> { public: using type = decltype( get(std::declval < ::nlohmann::detail::iteration_proxy_value> ())); }; #if defined(__clang__) #pragma clang diagnostic pop #endif } // namespace std // #include // #include // #include namespace nlohmann { namespace detail { ////////////////// // constructors // ////////////////// template struct external_constructor; template<> struct external_constructor { template static void construct(BasicJsonType& j, typename BasicJsonType::boolean_t b) noexcept { j.m_type = value_t::boolean; j.m_value = b; j.assert_invariant(); } }; template<> struct external_constructor { template static void construct(BasicJsonType& j, const typename BasicJsonType::string_t& s) { j.m_type = value_t::string; j.m_value = s; j.assert_invariant(); } template static void construct(BasicJsonType& j, typename BasicJsonType::string_t&& s) { j.m_type = value_t::string; j.m_value = std::move(s); j.assert_invariant(); } template < typename BasicJsonType, typename CompatibleStringType, enable_if_t < !std::is_same::value, int > = 0 > static void construct(BasicJsonType& j, const CompatibleStringType& str) { j.m_type = value_t::string; j.m_value.string = j.template create(str); j.assert_invariant(); } }; template<> struct external_constructor { template static void construct(BasicJsonType& j, const typename BasicJsonType::binary_t& b) { j.m_type = value_t::binary; typename BasicJsonType::binary_t value{b}; j.m_value = value; j.assert_invariant(); } template static void construct(BasicJsonType& j, typename BasicJsonType::binary_t&& b) { j.m_type = value_t::binary; typename BasicJsonType::binary_t value{std::move(b)}; j.m_value = value; j.assert_invariant(); } }; template<> struct external_constructor { template static void construct(BasicJsonType& j, typename BasicJsonType::number_float_t val) noexcept { j.m_type = value_t::number_float; j.m_value = val; j.assert_invariant(); } }; template<> struct external_constructor { template static void construct(BasicJsonType& j, typename BasicJsonType::number_unsigned_t val) noexcept { j.m_type = value_t::number_unsigned; j.m_value = val; j.assert_invariant(); } }; template<> struct external_constructor { template static void construct(BasicJsonType& j, typename BasicJsonType::number_integer_t val) noexcept { j.m_type = value_t::number_integer; j.m_value = val; j.assert_invariant(); } }; template<> struct external_constructor { template static void construct(BasicJsonType& j, const typename BasicJsonType::array_t& arr) { j.m_type = value_t::array; j.m_value = arr; j.assert_invariant(); } template static void construct(BasicJsonType& j, typename BasicJsonType::array_t&& arr) { j.m_type = value_t::array; j.m_value = std::move(arr); j.assert_invariant(); } template < typename BasicJsonType, typename CompatibleArrayType, enable_if_t < !std::is_same::value, int > = 0 > static void construct(BasicJsonType& j, const CompatibleArrayType& arr) { using std::begin; using std::end; j.m_type = value_t::array; j.m_value.array = j.template create(begin(arr), end(arr)); j.assert_invariant(); } template static void construct(BasicJsonType& j, const std::vector& arr) { j.m_type = value_t::array; j.m_value = value_t::array; j.m_value.array->reserve(arr.size()); for (const bool x : arr) { j.m_value.array->push_back(x); } j.assert_invariant(); } template::value, int> = 0> static void construct(BasicJsonType& j, const std::valarray& arr) { j.m_type = value_t::array; j.m_value = value_t::array; j.m_value.array->resize(arr.size()); if (arr.size() > 0) { std::copy(std::begin(arr), std::end(arr), j.m_value.array->begin()); } j.assert_invariant(); } }; template<> struct external_constructor { template static void construct(BasicJsonType& j, const typename BasicJsonType::object_t& obj) { j.m_type = value_t::object; j.m_value = obj; j.assert_invariant(); } template static void construct(BasicJsonType& j, typename BasicJsonType::object_t&& obj) { j.m_type = value_t::object; j.m_value = std::move(obj); j.assert_invariant(); } template < typename BasicJsonType, typename CompatibleObjectType, enable_if_t < !std::is_same::value, int > = 0 > static void construct(BasicJsonType& j, const CompatibleObjectType& obj) { using std::begin; using std::end; j.m_type = value_t::object; j.m_value.object = j.template create(begin(obj), end(obj)); j.assert_invariant(); } }; ///////////// // to_json // ///////////// template::value, int> = 0> void to_json(BasicJsonType& j, T b) noexcept { external_constructor::construct(j, b); } template::value, int> = 0> void to_json(BasicJsonType& j, const CompatibleString& s) { external_constructor::construct(j, s); } template void to_json(BasicJsonType& j, typename BasicJsonType::string_t&& s) { external_constructor::construct(j, std::move(s)); } template::value, int> = 0> void to_json(BasicJsonType& j, FloatType val) noexcept { external_constructor::construct(j, static_cast(val)); } template::value, int> = 0> void to_json(BasicJsonType& j, CompatibleNumberUnsignedType val) noexcept { external_constructor::construct(j, static_cast(val)); } template::value, int> = 0> void to_json(BasicJsonType& j, CompatibleNumberIntegerType val) noexcept { external_constructor::construct(j, static_cast(val)); } template::value, int> = 0> void to_json(BasicJsonType& j, EnumType e) noexcept { using underlying_type = typename std::underlying_type::type; external_constructor::construct(j, static_cast(e)); } template void to_json(BasicJsonType& j, const std::vector& e) { external_constructor::construct(j, e); } template < typename BasicJsonType, typename CompatibleArrayType, enable_if_t < is_compatible_array_type::value&& !is_compatible_object_type::value&& !is_compatible_string_type::value&& !std::is_same::value&& !is_basic_json::value, int > = 0 > void to_json(BasicJsonType& j, const CompatibleArrayType& arr) { external_constructor::construct(j, arr); } template void to_json(BasicJsonType& j, const typename BasicJsonType::binary_t& bin) { external_constructor::construct(j, bin); } template::value, int> = 0> void to_json(BasicJsonType& j, const std::valarray& arr) { external_constructor::construct(j, std::move(arr)); } template void to_json(BasicJsonType& j, typename BasicJsonType::array_t&& arr) { external_constructor::construct(j, std::move(arr)); } template < typename BasicJsonType, typename CompatibleObjectType, enable_if_t < is_compatible_object_type::value&& !is_basic_json::value, int > = 0 > void to_json(BasicJsonType& j, const CompatibleObjectType& obj) { external_constructor::construct(j, obj); } template void to_json(BasicJsonType& j, typename BasicJsonType::object_t&& obj) { external_constructor::construct(j, std::move(obj)); } template < typename BasicJsonType, typename T, std::size_t N, enable_if_t < !std::is_constructible::value, int > = 0 > void to_json(BasicJsonType& j, const T(&arr)[N]) { external_constructor::construct(j, arr); } template < typename BasicJsonType, typename T1, typename T2, enable_if_t < std::is_constructible::value&& std::is_constructible::value, int > = 0 > void to_json(BasicJsonType& j, const std::pair& p) { j = { p.first, p.second }; } // for https://github.com/nlohmann/json/pull/1134 template>::value, int> = 0> void to_json(BasicJsonType& j, const T& b) { j = { {b.key(), b.value()} }; } template void to_json_tuple_impl(BasicJsonType& j, const Tuple& t, index_sequence /*unused*/) { j = { std::get(t)... }; } template::value, int > = 0> void to_json(BasicJsonType& j, const T& t) { to_json_tuple_impl(j, t, make_index_sequence::value> {}); } struct to_json_fn { template auto operator()(BasicJsonType& j, T&& val) const noexcept(noexcept(to_json(j, std::forward(val)))) -> decltype(to_json(j, std::forward(val)), void()) { return to_json(j, std::forward(val)); } }; } // namespace detail /// namespace to hold default `to_json` function namespace { constexpr const auto& to_json = detail::static_const::value; } // namespace } // namespace nlohmann namespace nlohmann { template struct adl_serializer { /*! @brief convert a JSON value to any value type This function is usually called by the `get()` function of the @ref basic_json class (either explicit or via conversion operators). @param[in] j JSON value to read from @param[in,out] val value to write to */ template static auto from_json(BasicJsonType&& j, ValueType& val) noexcept( noexcept(::nlohmann::from_json(std::forward(j), val))) -> decltype(::nlohmann::from_json(std::forward(j), val), void()) { ::nlohmann::from_json(std::forward(j), val); } /*! @brief convert any value type to a JSON value This function is usually called by the constructors of the @ref basic_json class. @param[in,out] j JSON value to write to @param[in] val value to read from */ template static auto to_json(BasicJsonType& j, ValueType&& val) noexcept( noexcept(::nlohmann::to_json(j, std::forward(val)))) -> decltype(::nlohmann::to_json(j, std::forward(val)), void()) { ::nlohmann::to_json(j, std::forward(val)); } }; } // namespace nlohmann // #include #include // uint8_t #include // tie #include // move namespace nlohmann { /*! @brief an internal type for a backed binary type This type extends the template parameter @a BinaryType provided to `basic_json` with a subtype used by BSON and MessagePack. This type exists so that the user does not have to specify a type themselves with a specific naming scheme in order to override the binary type. @tparam BinaryType container to store bytes (`std::vector` by default) @since version 3.8.0 */ template class byte_container_with_subtype : public BinaryType { public: /// the type of the underlying container using container_type = BinaryType; byte_container_with_subtype() noexcept(noexcept(container_type())) : container_type() {} byte_container_with_subtype(const container_type& b) noexcept(noexcept(container_type(b))) : container_type(b) {} byte_container_with_subtype(container_type&& b) noexcept(noexcept(container_type(std::move(b)))) : container_type(std::move(b)) {} byte_container_with_subtype(const container_type& b, std::uint8_t subtype) noexcept(noexcept(container_type(b))) : container_type(b) , m_subtype(subtype) , m_has_subtype(true) {} byte_container_with_subtype(container_type&& b, std::uint8_t subtype) noexcept(noexcept(container_type(std::move(b)))) : container_type(std::move(b)) , m_subtype(subtype) , m_has_subtype(true) {} bool operator==(const byte_container_with_subtype& rhs) const { return std::tie(static_cast(*this), m_subtype, m_has_subtype) == std::tie(static_cast(rhs), rhs.m_subtype, rhs.m_has_subtype); } bool operator!=(const byte_container_with_subtype& rhs) const { return !(rhs == *this); } /*! @brief sets the binary subtype Sets the binary subtype of the value, also flags a binary JSON value as having a subtype, which has implications for serialization. @complexity Constant. @exceptionsafety No-throw guarantee: this member function never throws exceptions. @sa @ref subtype() -- return the binary subtype @sa @ref clear_subtype() -- clears the binary subtype @sa @ref has_subtype() -- returns whether or not the binary value has a subtype @since version 3.8.0 */ void set_subtype(std::uint8_t subtype) noexcept { m_subtype = subtype; m_has_subtype = true; } /*! @brief return the binary subtype Returns the numerical subtype of the value if it has a subtype. If it does not have a subtype, this function will return size_t(-1) as a sentinel value. @return the numerical subtype of the binary value @complexity Constant. @exceptionsafety No-throw guarantee: this member function never throws exceptions. @sa @ref set_subtype() -- sets the binary subtype @sa @ref clear_subtype() -- clears the binary subtype @sa @ref has_subtype() -- returns whether or not the binary value has a subtype @since version 3.8.0 */ constexpr std::uint8_t subtype() const noexcept { return m_subtype; } /*! @brief return whether the value has a subtype @return whether the value has a subtype @complexity Constant. @exceptionsafety No-throw guarantee: this member function never throws exceptions. @sa @ref subtype() -- return the binary subtype @sa @ref set_subtype() -- sets the binary subtype @sa @ref clear_subtype() -- clears the binary subtype @since version 3.8.0 */ constexpr bool has_subtype() const noexcept { return m_has_subtype; } /*! @brief clears the binary subtype Clears the binary subtype and flags the value as not having a subtype, which has implications for serialization; for instance MessagePack will prefer the bin family over the ext family. @complexity Constant. @exceptionsafety No-throw guarantee: this member function never throws exceptions. @sa @ref subtype() -- return the binary subtype @sa @ref set_subtype() -- sets the binary subtype @sa @ref has_subtype() -- returns whether or not the binary value has a subtype @since version 3.8.0 */ void clear_subtype() noexcept { m_subtype = 0; m_has_subtype = false; } private: std::uint8_t m_subtype = 0; bool m_has_subtype = false; }; } // namespace nlohmann // #include // #include // #include // #include #include // size_t, uint8_t #include // hash namespace nlohmann { namespace detail { // boost::hash_combine inline std::size_t combine(std::size_t seed, std::size_t h) noexcept { seed ^= h + 0x9e3779b9 + (seed << 6U) + (seed >> 2U); return seed; } /*! @brief hash a JSON value The hash function tries to rely on std::hash where possible. Furthermore, the type of the JSON value is taken into account to have different hash values for null, 0, 0U, and false, etc. @tparam BasicJsonType basic_json specialization @param j JSON value to hash @return hash value of j */ template std::size_t hash(const BasicJsonType& j) { using string_t = typename BasicJsonType::string_t; using number_integer_t = typename BasicJsonType::number_integer_t; using number_unsigned_t = typename BasicJsonType::number_unsigned_t; using number_float_t = typename BasicJsonType::number_float_t; const auto type = static_cast(j.type()); switch (j.type()) { case BasicJsonType::value_t::null: case BasicJsonType::value_t::discarded: { return combine(type, 0); } case BasicJsonType::value_t::object: { auto seed = combine(type, j.size()); for (const auto& element : j.items()) { const auto h = std::hash {}(element.key()); seed = combine(seed, h); seed = combine(seed, hash(element.value())); } return seed; } case BasicJsonType::value_t::array: { auto seed = combine(type, j.size()); for (const auto& element : j) { seed = combine(seed, hash(element)); } return seed; } case BasicJsonType::value_t::string: { const auto h = std::hash {}(j.template get_ref()); return combine(type, h); } case BasicJsonType::value_t::boolean: { const auto h = std::hash {}(j.template get()); return combine(type, h); } case BasicJsonType::value_t::number_integer: { const auto h = std::hash {}(j.template get()); return combine(type, h); } case nlohmann::detail::value_t::number_unsigned: { const auto h = std::hash {}(j.template get()); return combine(type, h); } case nlohmann::detail::value_t::number_float: { const auto h = std::hash {}(j.template get()); return combine(type, h); } case nlohmann::detail::value_t::binary: { auto seed = combine(type, j.get_binary().size()); const auto h = std::hash {}(j.get_binary().has_subtype()); seed = combine(seed, h); seed = combine(seed, j.get_binary().subtype()); for (const auto byte : j.get_binary()) { seed = combine(seed, std::hash {}(byte)); } return seed; } default: // LCOV_EXCL_LINE JSON_ASSERT(false); // LCOV_EXCL_LINE } } } // namespace detail } // namespace nlohmann // #include #include // generate_n #include // array #include // ldexp #include // size_t #include // uint8_t, uint16_t, uint32_t, uint64_t #include // snprintf #include // memcpy #include // back_inserter #include // numeric_limits #include // char_traits, string #include // make_pair, move // #include // #include #include // array #include // size_t #include //FILE * #include // strlen #include // istream #include // begin, end, iterator_traits, random_access_iterator_tag, distance, next #include // shared_ptr, make_shared, addressof #include // accumulate #include // string, char_traits #include // enable_if, is_base_of, is_pointer, is_integral, remove_pointer #include // pair, declval // #include // #include namespace nlohmann { namespace detail { /// the supported input formats enum class input_format_t { json, cbor, msgpack, ubjson, bson }; //////////////////// // input adapters // //////////////////// /*! Input adapter for stdio file access. This adapter read only 1 byte and do not use any buffer. This adapter is a very low level adapter. */ class file_input_adapter { public: using char_type = char; JSON_HEDLEY_NON_NULL(2) explicit file_input_adapter(std::FILE* f) noexcept : m_file(f) {} // make class move-only file_input_adapter(const file_input_adapter&) = delete; file_input_adapter(file_input_adapter&&) = default; file_input_adapter& operator=(const file_input_adapter&) = delete; file_input_adapter& operator=(file_input_adapter&&) = delete; std::char_traits::int_type get_character() noexcept { return std::fgetc(m_file); } private: /// the file pointer to read from std::FILE* m_file; }; /*! Input adapter for a (caching) istream. Ignores a UFT Byte Order Mark at beginning of input. Does not support changing the underlying std::streambuf in mid-input. Maintains underlying std::istream and std::streambuf to support subsequent use of standard std::istream operations to process any input characters following those used in parsing the JSON input. Clears the std::istream flags; any input errors (e.g., EOF) will be detected by the first subsequent call for input from the std::istream. */ class input_stream_adapter { public: using char_type = char; ~input_stream_adapter() { // clear stream flags; we use underlying streambuf I/O, do not // maintain ifstream flags, except eof if (is != nullptr) { is->clear(is->rdstate() & std::ios::eofbit); } } explicit input_stream_adapter(std::istream& i) : is(&i), sb(i.rdbuf()) {} // delete because of pointer members input_stream_adapter(const input_stream_adapter&) = delete; input_stream_adapter& operator=(input_stream_adapter&) = delete; input_stream_adapter& operator=(input_stream_adapter&& rhs) = delete; input_stream_adapter(input_stream_adapter&& rhs) noexcept : is(rhs.is), sb(rhs.sb) { rhs.is = nullptr; rhs.sb = nullptr; } // std::istream/std::streambuf use std::char_traits::to_int_type, to // ensure that std::char_traits::eof() and the character 0xFF do not // end up as the same value, eg. 0xFFFFFFFF. std::char_traits::int_type get_character() { auto res = sb->sbumpc(); // set eof manually, as we don't use the istream interface. if (JSON_HEDLEY_UNLIKELY(res == EOF)) { is->clear(is->rdstate() | std::ios::eofbit); } return res; } private: /// the associated input stream std::istream* is = nullptr; std::streambuf* sb = nullptr; }; // General-purpose iterator-based adapter. It might not be as fast as // theoretically possible for some containers, but it is extremely versatile. template class iterator_input_adapter { public: using char_type = typename std::iterator_traits::value_type; iterator_input_adapter(IteratorType first, IteratorType last) : current(std::move(first)), end(std::move(last)) {} typename std::char_traits::int_type get_character() { if (JSON_HEDLEY_LIKELY(current != end)) { auto result = std::char_traits::to_int_type(*current); std::advance(current, 1); return result; } else { return std::char_traits::eof(); } } private: IteratorType current; IteratorType end; template friend struct wide_string_input_helper; bool empty() const { return current == end; } }; template struct wide_string_input_helper; template struct wide_string_input_helper { // UTF-32 static void fill_buffer(BaseInputAdapter& input, std::array::int_type, 4>& utf8_bytes, size_t& utf8_bytes_index, size_t& utf8_bytes_filled) { utf8_bytes_index = 0; if (JSON_HEDLEY_UNLIKELY(input.empty())) { utf8_bytes[0] = std::char_traits::eof(); utf8_bytes_filled = 1; } else { // get the current character const auto wc = input.get_character(); // UTF-32 to UTF-8 encoding if (wc < 0x80) { utf8_bytes[0] = static_cast::int_type>(wc); utf8_bytes_filled = 1; } else if (wc <= 0x7FF) { utf8_bytes[0] = static_cast::int_type>(0xC0u | ((static_cast(wc) >> 6u) & 0x1Fu)); utf8_bytes[1] = static_cast::int_type>(0x80u | (static_cast(wc) & 0x3Fu)); utf8_bytes_filled = 2; } else if (wc <= 0xFFFF) { utf8_bytes[0] = static_cast::int_type>(0xE0u | ((static_cast(wc) >> 12u) & 0x0Fu)); utf8_bytes[1] = static_cast::int_type>(0x80u | ((static_cast(wc) >> 6u) & 0x3Fu)); utf8_bytes[2] = static_cast::int_type>(0x80u | (static_cast(wc) & 0x3Fu)); utf8_bytes_filled = 3; } else if (wc <= 0x10FFFF) { utf8_bytes[0] = static_cast::int_type>(0xF0u | ((static_cast(wc) >> 18u) & 0x07u)); utf8_bytes[1] = static_cast::int_type>(0x80u | ((static_cast(wc) >> 12u) & 0x3Fu)); utf8_bytes[2] = static_cast::int_type>(0x80u | ((static_cast(wc) >> 6u) & 0x3Fu)); utf8_bytes[3] = static_cast::int_type>(0x80u | (static_cast(wc) & 0x3Fu)); utf8_bytes_filled = 4; } else { // unknown character utf8_bytes[0] = static_cast::int_type>(wc); utf8_bytes_filled = 1; } } } }; template struct wide_string_input_helper { // UTF-16 static void fill_buffer(BaseInputAdapter& input, std::array::int_type, 4>& utf8_bytes, size_t& utf8_bytes_index, size_t& utf8_bytes_filled) { utf8_bytes_index = 0; if (JSON_HEDLEY_UNLIKELY(input.empty())) { utf8_bytes[0] = std::char_traits::eof(); utf8_bytes_filled = 1; } else { // get the current character const auto wc = input.get_character(); // UTF-16 to UTF-8 encoding if (wc < 0x80) { utf8_bytes[0] = static_cast::int_type>(wc); utf8_bytes_filled = 1; } else if (wc <= 0x7FF) { utf8_bytes[0] = static_cast::int_type>(0xC0u | ((static_cast(wc) >> 6u))); utf8_bytes[1] = static_cast::int_type>(0x80u | (static_cast(wc) & 0x3Fu)); utf8_bytes_filled = 2; } else if (0xD800 > wc || wc >= 0xE000) { utf8_bytes[0] = static_cast::int_type>(0xE0u | ((static_cast(wc) >> 12u))); utf8_bytes[1] = static_cast::int_type>(0x80u | ((static_cast(wc) >> 6u) & 0x3Fu)); utf8_bytes[2] = static_cast::int_type>(0x80u | (static_cast(wc) & 0x3Fu)); utf8_bytes_filled = 3; } else { if (JSON_HEDLEY_UNLIKELY(!input.empty())) { const auto wc2 = static_cast(input.get_character()); const auto charcode = 0x10000u + (((static_cast(wc) & 0x3FFu) << 10u) | (wc2 & 0x3FFu)); utf8_bytes[0] = static_cast::int_type>(0xF0u | (charcode >> 18u)); utf8_bytes[1] = static_cast::int_type>(0x80u | ((charcode >> 12u) & 0x3Fu)); utf8_bytes[2] = static_cast::int_type>(0x80u | ((charcode >> 6u) & 0x3Fu)); utf8_bytes[3] = static_cast::int_type>(0x80u | (charcode & 0x3Fu)); utf8_bytes_filled = 4; } else { utf8_bytes[0] = static_cast::int_type>(wc); utf8_bytes_filled = 1; } } } } }; // Wraps another input apdater to convert wide character types into individual bytes. template class wide_string_input_adapter { public: using char_type = char; wide_string_input_adapter(BaseInputAdapter base) : base_adapter(base) {} typename std::char_traits::int_type get_character() noexcept { // check if buffer needs to be filled if (utf8_bytes_index == utf8_bytes_filled) { fill_buffer(); JSON_ASSERT(utf8_bytes_filled > 0); JSON_ASSERT(utf8_bytes_index == 0); } // use buffer JSON_ASSERT(utf8_bytes_filled > 0); JSON_ASSERT(utf8_bytes_index < utf8_bytes_filled); return utf8_bytes[utf8_bytes_index++]; } private: BaseInputAdapter base_adapter; template void fill_buffer() { wide_string_input_helper::fill_buffer(base_adapter, utf8_bytes, utf8_bytes_index, utf8_bytes_filled); } /// a buffer for UTF-8 bytes std::array::int_type, 4> utf8_bytes = {{0, 0, 0, 0}}; /// index to the utf8_codes array for the next valid byte std::size_t utf8_bytes_index = 0; /// number of valid bytes in the utf8_codes array std::size_t utf8_bytes_filled = 0; }; template struct iterator_input_adapter_factory { using iterator_type = IteratorType; using char_type = typename std::iterator_traits::value_type; using adapter_type = iterator_input_adapter; static adapter_type create(IteratorType first, IteratorType last) { return adapter_type(std::move(first), std::move(last)); } }; template struct is_iterator_of_multibyte { using value_type = typename std::iterator_traits::value_type; enum { value = sizeof(value_type) > 1 }; }; template struct iterator_input_adapter_factory::value>> { using iterator_type = IteratorType; using char_type = typename std::iterator_traits::value_type; using base_adapter_type = iterator_input_adapter; using adapter_type = wide_string_input_adapter; static adapter_type create(IteratorType first, IteratorType last) { return adapter_type(base_adapter_type(std::move(first), std::move(last))); } }; // General purpose iterator-based input template typename iterator_input_adapter_factory::adapter_type input_adapter(IteratorType first, IteratorType last) { using factory_type = iterator_input_adapter_factory; return factory_type::create(first, last); } // Convenience shorthand from container to iterator template auto input_adapter(const ContainerType& container) -> decltype(input_adapter(begin(container), end(container))) { // Enable ADL using std::begin; using std::end; return input_adapter(begin(container), end(container)); } // Special cases with fast paths inline file_input_adapter input_adapter(std::FILE* file) { return file_input_adapter(file); } inline input_stream_adapter input_adapter(std::istream& stream) { return input_stream_adapter(stream); } inline input_stream_adapter input_adapter(std::istream&& stream) { return input_stream_adapter(stream); } using contiguous_bytes_input_adapter = decltype(input_adapter(std::declval(), std::declval())); // Null-delimited strings, and the like. template < typename CharT, typename std::enable_if < std::is_pointer::value&& !std::is_array::value&& std::is_integral::type>::value&& sizeof(typename std::remove_pointer::type) == 1, int >::type = 0 > contiguous_bytes_input_adapter input_adapter(CharT b) { auto length = std::strlen(reinterpret_cast(b)); const auto* ptr = reinterpret_cast(b); return input_adapter(ptr, ptr + length); } template auto input_adapter(T (&array)[N]) -> decltype(input_adapter(array, array + N)) { return input_adapter(array, array + N); } // This class only handles inputs of input_buffer_adapter type. // It's required so that expressions like {ptr, len} can be implicitely casted // to the correct adapter. class span_input_adapter { public: template < typename CharT, typename std::enable_if < std::is_pointer::value&& std::is_integral::type>::value&& sizeof(typename std::remove_pointer::type) == 1, int >::type = 0 > span_input_adapter(CharT b, std::size_t l) : ia(reinterpret_cast(b), reinterpret_cast(b) + l) {} template::iterator_category, std::random_access_iterator_tag>::value, int>::type = 0> span_input_adapter(IteratorType first, IteratorType last) : ia(input_adapter(first, last)) {} contiguous_bytes_input_adapter&& get() { return std::move(ia); } private: contiguous_bytes_input_adapter ia; }; } // namespace detail } // namespace nlohmann // #include #include #include // string #include // move #include // vector // #include // #include namespace nlohmann { /*! @brief SAX interface This class describes the SAX interface used by @ref nlohmann::json::sax_parse. Each function is called in different situations while the input is parsed. The boolean return value informs the parser whether to continue processing the input. */ template struct json_sax { using number_integer_t = typename BasicJsonType::number_integer_t; using number_unsigned_t = typename BasicJsonType::number_unsigned_t; using number_float_t = typename BasicJsonType::number_float_t; using string_t = typename BasicJsonType::string_t; using binary_t = typename BasicJsonType::binary_t; /*! @brief a null value was read @return whether parsing should proceed */ virtual bool null() = 0; /*! @brief a boolean value was read @param[in] val boolean value @return whether parsing should proceed */ virtual bool boolean(bool val) = 0; /*! @brief an integer number was read @param[in] val integer value @return whether parsing should proceed */ virtual bool number_integer(number_integer_t val) = 0; /*! @brief an unsigned integer number was read @param[in] val unsigned integer value @return whether parsing should proceed */ virtual bool number_unsigned(number_unsigned_t val) = 0; /*! @brief an floating-point number was read @param[in] val floating-point value @param[in] s raw token value @return whether parsing should proceed */ virtual bool number_float(number_float_t val, const string_t& s) = 0; /*! @brief a string was read @param[in] val string value @return whether parsing should proceed @note It is safe to move the passed string. */ virtual bool string(string_t& val) = 0; /*! @brief a binary string was read @param[in] val binary value @return whether parsing should proceed @note It is safe to move the passed binary. */ virtual bool binary(binary_t& val) = 0; /*! @brief the beginning of an object was read @param[in] elements number of object elements or -1 if unknown @return whether parsing should proceed @note binary formats may report the number of elements */ virtual bool start_object(std::size_t elements) = 0; /*! @brief an object key was read @param[in] val object key @return whether parsing should proceed @note It is safe to move the passed string. */ virtual bool key(string_t& val) = 0; /*! @brief the end of an object was read @return whether parsing should proceed */ virtual bool end_object() = 0; /*! @brief the beginning of an array was read @param[in] elements number of array elements or -1 if unknown @return whether parsing should proceed @note binary formats may report the number of elements */ virtual bool start_array(std::size_t elements) = 0; /*! @brief the end of an array was read @return whether parsing should proceed */ virtual bool end_array() = 0; /*! @brief a parse error occurred @param[in] position the position in the input where the error occurs @param[in] last_token the last read token @param[in] ex an exception object describing the error @return whether parsing should proceed (must return false) */ virtual bool parse_error(std::size_t position, const std::string& last_token, const detail::exception& ex) = 0; virtual ~json_sax() = default; }; namespace detail { /*! @brief SAX implementation to create a JSON value from SAX events This class implements the @ref json_sax interface and processes the SAX events to create a JSON value which makes it basically a DOM parser. The structure or hierarchy of the JSON value is managed by the stack `ref_stack` which contains a pointer to the respective array or object for each recursion depth. After successful parsing, the value that is passed by reference to the constructor contains the parsed value. @tparam BasicJsonType the JSON type */ template class json_sax_dom_parser { public: using number_integer_t = typename BasicJsonType::number_integer_t; using number_unsigned_t = typename BasicJsonType::number_unsigned_t; using number_float_t = typename BasicJsonType::number_float_t; using string_t = typename BasicJsonType::string_t; using binary_t = typename BasicJsonType::binary_t; /*! @param[in, out] r reference to a JSON value that is manipulated while parsing @param[in] allow_exceptions_ whether parse errors yield exceptions */ explicit json_sax_dom_parser(BasicJsonType& r, const bool allow_exceptions_ = true) : root(r), allow_exceptions(allow_exceptions_) {} // make class move-only json_sax_dom_parser(const json_sax_dom_parser&) = delete; json_sax_dom_parser(json_sax_dom_parser&&) = default; json_sax_dom_parser& operator=(const json_sax_dom_parser&) = delete; json_sax_dom_parser& operator=(json_sax_dom_parser&&) = default; ~json_sax_dom_parser() = default; bool null() { handle_value(nullptr); return true; } bool boolean(bool val) { handle_value(val); return true; } bool number_integer(number_integer_t val) { handle_value(val); return true; } bool number_unsigned(number_unsigned_t val) { handle_value(val); return true; } bool number_float(number_float_t val, const string_t& /*unused*/) { handle_value(val); return true; } bool string(string_t& val) { handle_value(val); return true; } bool binary(binary_t& val) { handle_value(std::move(val)); return true; } bool start_object(std::size_t len) { ref_stack.push_back(handle_value(BasicJsonType::value_t::object)); if (JSON_HEDLEY_UNLIKELY(len != std::size_t(-1) && len > ref_stack.back()->max_size())) { JSON_THROW(out_of_range::create(408, "excessive object size: " + std::to_string(len))); } return true; } bool key(string_t& val) { // add null at given key and store the reference for later object_element = &(ref_stack.back()->m_value.object->operator[](val)); return true; } bool end_object() { ref_stack.pop_back(); return true; } bool start_array(std::size_t len) { ref_stack.push_back(handle_value(BasicJsonType::value_t::array)); if (JSON_HEDLEY_UNLIKELY(len != std::size_t(-1) && len > ref_stack.back()->max_size())) { JSON_THROW(out_of_range::create(408, "excessive array size: " + std::to_string(len))); } return true; } bool end_array() { ref_stack.pop_back(); return true; } template bool parse_error(std::size_t /*unused*/, const std::string& /*unused*/, const Exception& ex) { errored = true; static_cast(ex); if (allow_exceptions) { JSON_THROW(ex); } return false; } constexpr bool is_errored() const { return errored; } private: /*! @invariant If the ref stack is empty, then the passed value will be the new root. @invariant If the ref stack contains a value, then it is an array or an object to which we can add elements */ template JSON_HEDLEY_RETURNS_NON_NULL BasicJsonType* handle_value(Value&& v) { if (ref_stack.empty()) { root = BasicJsonType(std::forward(v)); return &root; } JSON_ASSERT(ref_stack.back()->is_array() || ref_stack.back()->is_object()); if (ref_stack.back()->is_array()) { ref_stack.back()->m_value.array->emplace_back(std::forward(v)); return &(ref_stack.back()->m_value.array->back()); } JSON_ASSERT(ref_stack.back()->is_object()); JSON_ASSERT(object_element); *object_element = BasicJsonType(std::forward(v)); return object_element; } /// the parsed JSON value BasicJsonType& root; /// stack to model hierarchy of values std::vector ref_stack {}; /// helper to hold the reference for the next object element BasicJsonType* object_element = nullptr; /// whether a syntax error occurred bool errored = false; /// whether to throw exceptions in case of errors const bool allow_exceptions = true; }; template class json_sax_dom_callback_parser { public: using number_integer_t = typename BasicJsonType::number_integer_t; using number_unsigned_t = typename BasicJsonType::number_unsigned_t; using number_float_t = typename BasicJsonType::number_float_t; using string_t = typename BasicJsonType::string_t; using binary_t = typename BasicJsonType::binary_t; using parser_callback_t = typename BasicJsonType::parser_callback_t; using parse_event_t = typename BasicJsonType::parse_event_t; json_sax_dom_callback_parser(BasicJsonType& r, const parser_callback_t cb, const bool allow_exceptions_ = true) : root(r), callback(cb), allow_exceptions(allow_exceptions_) { keep_stack.push_back(true); } // make class move-only json_sax_dom_callback_parser(const json_sax_dom_callback_parser&) = delete; json_sax_dom_callback_parser(json_sax_dom_callback_parser&&) = default; json_sax_dom_callback_parser& operator=(const json_sax_dom_callback_parser&) = delete; json_sax_dom_callback_parser& operator=(json_sax_dom_callback_parser&&) = default; ~json_sax_dom_callback_parser() = default; bool null() { handle_value(nullptr); return true; } bool boolean(bool val) { handle_value(val); return true; } bool number_integer(number_integer_t val) { handle_value(val); return true; } bool number_unsigned(number_unsigned_t val) { handle_value(val); return true; } bool number_float(number_float_t val, const string_t& /*unused*/) { handle_value(val); return true; } bool string(string_t& val) { handle_value(val); return true; } bool binary(binary_t& val) { handle_value(std::move(val)); return true; } bool start_object(std::size_t len) { // check callback for object start const bool keep = callback(static_cast(ref_stack.size()), parse_event_t::object_start, discarded); keep_stack.push_back(keep); auto val = handle_value(BasicJsonType::value_t::object, true); ref_stack.push_back(val.second); // check object limit if (ref_stack.back() && JSON_HEDLEY_UNLIKELY(len != std::size_t(-1) && len > ref_stack.back()->max_size())) { JSON_THROW(out_of_range::create(408, "excessive object size: " + std::to_string(len))); } return true; } bool key(string_t& val) { BasicJsonType k = BasicJsonType(val); // check callback for key const bool keep = callback(static_cast(ref_stack.size()), parse_event_t::key, k); key_keep_stack.push_back(keep); // add discarded value at given key and store the reference for later if (keep && ref_stack.back()) { object_element = &(ref_stack.back()->m_value.object->operator[](val) = discarded); } return true; } bool end_object() { if (ref_stack.back() && !callback(static_cast(ref_stack.size()) - 1, parse_event_t::object_end, *ref_stack.back())) { // discard object *ref_stack.back() = discarded; } JSON_ASSERT(!ref_stack.empty()); JSON_ASSERT(!keep_stack.empty()); ref_stack.pop_back(); keep_stack.pop_back(); if (!ref_stack.empty() && ref_stack.back() && ref_stack.back()->is_structured()) { // remove discarded value for (auto it = ref_stack.back()->begin(); it != ref_stack.back()->end(); ++it) { if (it->is_discarded()) { ref_stack.back()->erase(it); break; } } } return true; } bool start_array(std::size_t len) { const bool keep = callback(static_cast(ref_stack.size()), parse_event_t::array_start, discarded); keep_stack.push_back(keep); auto val = handle_value(BasicJsonType::value_t::array, true); ref_stack.push_back(val.second); // check array limit if (ref_stack.back() && JSON_HEDLEY_UNLIKELY(len != std::size_t(-1) && len > ref_stack.back()->max_size())) { JSON_THROW(out_of_range::create(408, "excessive array size: " + std::to_string(len))); } return true; } bool end_array() { bool keep = true; if (ref_stack.back()) { keep = callback(static_cast(ref_stack.size()) - 1, parse_event_t::array_end, *ref_stack.back()); if (!keep) { // discard array *ref_stack.back() = discarded; } } JSON_ASSERT(!ref_stack.empty()); JSON_ASSERT(!keep_stack.empty()); ref_stack.pop_back(); keep_stack.pop_back(); // remove discarded value if (!keep && !ref_stack.empty() && ref_stack.back()->is_array()) { ref_stack.back()->m_value.array->pop_back(); } return true; } template bool parse_error(std::size_t /*unused*/, const std::string& /*unused*/, const Exception& ex) { errored = true; static_cast(ex); if (allow_exceptions) { JSON_THROW(ex); } return false; } constexpr bool is_errored() const { return errored; } private: /*! @param[in] v value to add to the JSON value we build during parsing @param[in] skip_callback whether we should skip calling the callback function; this is required after start_array() and start_object() SAX events, because otherwise we would call the callback function with an empty array or object, respectively. @invariant If the ref stack is empty, then the passed value will be the new root. @invariant If the ref stack contains a value, then it is an array or an object to which we can add elements @return pair of boolean (whether value should be kept) and pointer (to the passed value in the ref_stack hierarchy; nullptr if not kept) */ template std::pair handle_value(Value&& v, const bool skip_callback = false) { JSON_ASSERT(!keep_stack.empty()); // do not handle this value if we know it would be added to a discarded // container if (!keep_stack.back()) { return {false, nullptr}; } // create value auto value = BasicJsonType(std::forward(v)); // check callback const bool keep = skip_callback || callback(static_cast(ref_stack.size()), parse_event_t::value, value); // do not handle this value if we just learnt it shall be discarded if (!keep) { return {false, nullptr}; } if (ref_stack.empty()) { root = std::move(value); return {true, &root}; } // skip this value if we already decided to skip the parent // (https://github.com/nlohmann/json/issues/971#issuecomment-413678360) if (!ref_stack.back()) { return {false, nullptr}; } // we now only expect arrays and objects JSON_ASSERT(ref_stack.back()->is_array() || ref_stack.back()->is_object()); // array if (ref_stack.back()->is_array()) { ref_stack.back()->m_value.array->push_back(std::move(value)); return {true, &(ref_stack.back()->m_value.array->back())}; } // object JSON_ASSERT(ref_stack.back()->is_object()); // check if we should store an element for the current key JSON_ASSERT(!key_keep_stack.empty()); const bool store_element = key_keep_stack.back(); key_keep_stack.pop_back(); if (!store_element) { return {false, nullptr}; } JSON_ASSERT(object_element); *object_element = std::move(value); return {true, object_element}; } /// the parsed JSON value BasicJsonType& root; /// stack to model hierarchy of values std::vector ref_stack {}; /// stack to manage which values to keep std::vector keep_stack {}; /// stack to manage which object keys to keep std::vector key_keep_stack {}; /// helper to hold the reference for the next object element BasicJsonType* object_element = nullptr; /// whether a syntax error occurred bool errored = false; /// callback function const parser_callback_t callback = nullptr; /// whether to throw exceptions in case of errors const bool allow_exceptions = true; /// a discarded value for the callback BasicJsonType discarded = BasicJsonType::value_t::discarded; }; template class json_sax_acceptor { public: using number_integer_t = typename BasicJsonType::number_integer_t; using number_unsigned_t = typename BasicJsonType::number_unsigned_t; using number_float_t = typename BasicJsonType::number_float_t; using string_t = typename BasicJsonType::string_t; using binary_t = typename BasicJsonType::binary_t; bool null() { return true; } bool boolean(bool /*unused*/) { return true; } bool number_integer(number_integer_t /*unused*/) { return true; } bool number_unsigned(number_unsigned_t /*unused*/) { return true; } bool number_float(number_float_t /*unused*/, const string_t& /*unused*/) { return true; } bool string(string_t& /*unused*/) { return true; } bool binary(binary_t& /*unused*/) { return true; } bool start_object(std::size_t /*unused*/ = std::size_t(-1)) { return true; } bool key(string_t& /*unused*/) { return true; } bool end_object() { return true; } bool start_array(std::size_t /*unused*/ = std::size_t(-1)) { return true; } bool end_array() { return true; } bool parse_error(std::size_t /*unused*/, const std::string& /*unused*/, const detail::exception& /*unused*/) { return false; } }; } // namespace detail } // namespace nlohmann // #include #include // array #include // localeconv #include // size_t #include // snprintf #include // strtof, strtod, strtold, strtoll, strtoull #include // initializer_list #include // char_traits, string #include // move #include // vector // #include // #include // #include namespace nlohmann { namespace detail { /////////// // lexer // /////////// template class lexer_base { public: /// token types for the parser enum class token_type { uninitialized, ///< indicating the scanner is uninitialized literal_true, ///< the `true` literal literal_false, ///< the `false` literal literal_null, ///< the `null` literal value_string, ///< a string -- use get_string() for actual value value_unsigned, ///< an unsigned integer -- use get_number_unsigned() for actual value value_integer, ///< a signed integer -- use get_number_integer() for actual value value_float, ///< an floating point number -- use get_number_float() for actual value begin_array, ///< the character for array begin `[` begin_object, ///< the character for object begin `{` end_array, ///< the character for array end `]` end_object, ///< the character for object end `}` name_separator, ///< the name separator `:` value_separator, ///< the value separator `,` parse_error, ///< indicating a parse error end_of_input, ///< indicating the end of the input buffer literal_or_value ///< a literal or the begin of a value (only for diagnostics) }; /// return name of values of type token_type (only used for errors) JSON_HEDLEY_RETURNS_NON_NULL JSON_HEDLEY_CONST static const char* token_type_name(const token_type t) noexcept { switch (t) { case token_type::uninitialized: return ""; case token_type::literal_true: return "true literal"; case token_type::literal_false: return "false literal"; case token_type::literal_null: return "null literal"; case token_type::value_string: return "string literal"; case token_type::value_unsigned: case token_type::value_integer: case token_type::value_float: return "number literal"; case token_type::begin_array: return "'['"; case token_type::begin_object: return "'{'"; case token_type::end_array: return "']'"; case token_type::end_object: return "'}'"; case token_type::name_separator: return "':'"; case token_type::value_separator: return "','"; case token_type::parse_error: return ""; case token_type::end_of_input: return "end of input"; case token_type::literal_or_value: return "'[', '{', or a literal"; // LCOV_EXCL_START default: // catch non-enum values return "unknown token"; // LCOV_EXCL_STOP } } }; /*! @brief lexical analysis This class organizes the lexical analysis during JSON deserialization. */ template class lexer : public lexer_base { using number_integer_t = typename BasicJsonType::number_integer_t; using number_unsigned_t = typename BasicJsonType::number_unsigned_t; using number_float_t = typename BasicJsonType::number_float_t; using string_t = typename BasicJsonType::string_t; using char_type = typename InputAdapterType::char_type; using char_int_type = typename std::char_traits::int_type; public: using token_type = typename lexer_base::token_type; explicit lexer(InputAdapterType&& adapter, bool ignore_comments_ = false) : ia(std::move(adapter)) , ignore_comments(ignore_comments_) , decimal_point_char(static_cast(get_decimal_point())) {} // delete because of pointer members lexer(const lexer&) = delete; lexer(lexer&&) = default; lexer& operator=(lexer&) = delete; lexer& operator=(lexer&&) = default; ~lexer() = default; private: ///////////////////// // locales ///////////////////// /// return the locale-dependent decimal point JSON_HEDLEY_PURE static char get_decimal_point() noexcept { const auto* loc = localeconv(); JSON_ASSERT(loc != nullptr); return (loc->decimal_point == nullptr) ? '.' : *(loc->decimal_point); } ///////////////////// // scan functions ///////////////////// /*! @brief get codepoint from 4 hex characters following `\u` For input "\u c1 c2 c3 c4" the codepoint is: (c1 * 0x1000) + (c2 * 0x0100) + (c3 * 0x0010) + c4 = (c1 << 12) + (c2 << 8) + (c3 << 4) + (c4 << 0) Furthermore, the possible characters '0'..'9', 'A'..'F', and 'a'..'f' must be converted to the integers 0x0..0x9, 0xA..0xF, 0xA..0xF, resp. The conversion is done by subtracting the offset (0x30, 0x37, and 0x57) between the ASCII value of the character and the desired integer value. @return codepoint (0x0000..0xFFFF) or -1 in case of an error (e.g. EOF or non-hex character) */ int get_codepoint() { // this function only makes sense after reading `\u` JSON_ASSERT(current == 'u'); int codepoint = 0; const auto factors = { 12u, 8u, 4u, 0u }; for (const auto factor : factors) { get(); if (current >= '0' && current <= '9') { codepoint += static_cast((static_cast(current) - 0x30u) << factor); } else if (current >= 'A' && current <= 'F') { codepoint += static_cast((static_cast(current) - 0x37u) << factor); } else if (current >= 'a' && current <= 'f') { codepoint += static_cast((static_cast(current) - 0x57u) << factor); } else { return -1; } } JSON_ASSERT(0x0000 <= codepoint && codepoint <= 0xFFFF); return codepoint; } /*! @brief check if the next byte(s) are inside a given range Adds the current byte and, for each passed range, reads a new byte and checks if it is inside the range. If a violation was detected, set up an error message and return false. Otherwise, return true. @param[in] ranges list of integers; interpreted as list of pairs of inclusive lower and upper bound, respectively @pre The passed list @a ranges must have 2, 4, or 6 elements; that is, 1, 2, or 3 pairs. This precondition is enforced by an assertion. @return true if and only if no range violation was detected */ bool next_byte_in_range(std::initializer_list ranges) { JSON_ASSERT(ranges.size() == 2 || ranges.size() == 4 || ranges.size() == 6); add(current); for (auto range = ranges.begin(); range != ranges.end(); ++range) { get(); if (JSON_HEDLEY_LIKELY(*range <= current && current <= *(++range))) { add(current); } else { error_message = "invalid string: ill-formed UTF-8 byte"; return false; } } return true; } /*! @brief scan a string literal This function scans a string according to Sect. 7 of RFC 7159. While scanning, bytes are escaped and copied into buffer token_buffer. Then the function returns successfully, token_buffer is *not* null-terminated (as it may contain \0 bytes), and token_buffer.size() is the number of bytes in the string. @return token_type::value_string if string could be successfully scanned, token_type::parse_error otherwise @note In case of errors, variable error_message contains a textual description. */ token_type scan_string() { // reset token_buffer (ignore opening quote) reset(); // we entered the function by reading an open quote JSON_ASSERT(current == '\"'); while (true) { // get next character switch (get()) { // end of file while parsing string case std::char_traits::eof(): { error_message = "invalid string: missing closing quote"; return token_type::parse_error; } // closing quote case '\"': { return token_type::value_string; } // escapes case '\\': { switch (get()) { // quotation mark case '\"': add('\"'); break; // reverse solidus case '\\': add('\\'); break; // solidus case '/': add('/'); break; // backspace case 'b': add('\b'); break; // form feed case 'f': add('\f'); break; // line feed case 'n': add('\n'); break; // carriage return case 'r': add('\r'); break; // tab case 't': add('\t'); break; // unicode escapes case 'u': { const int codepoint1 = get_codepoint(); int codepoint = codepoint1; // start with codepoint1 if (JSON_HEDLEY_UNLIKELY(codepoint1 == -1)) { error_message = "invalid string: '\\u' must be followed by 4 hex digits"; return token_type::parse_error; } // check if code point is a high surrogate if (0xD800 <= codepoint1 && codepoint1 <= 0xDBFF) { // expect next \uxxxx entry if (JSON_HEDLEY_LIKELY(get() == '\\' && get() == 'u')) { const int codepoint2 = get_codepoint(); if (JSON_HEDLEY_UNLIKELY(codepoint2 == -1)) { error_message = "invalid string: '\\u' must be followed by 4 hex digits"; return token_type::parse_error; } // check if codepoint2 is a low surrogate if (JSON_HEDLEY_LIKELY(0xDC00 <= codepoint2 && codepoint2 <= 0xDFFF)) { // overwrite codepoint codepoint = static_cast( // high surrogate occupies the most significant 22 bits (static_cast(codepoint1) << 10u) // low surrogate occupies the least significant 15 bits + static_cast(codepoint2) // there is still the 0xD800, 0xDC00 and 0x10000 noise // in the result so we have to subtract with: // (0xD800 << 10) + DC00 - 0x10000 = 0x35FDC00 - 0x35FDC00u); } else { error_message = "invalid string: surrogate U+D800..U+DBFF must be followed by U+DC00..U+DFFF"; return token_type::parse_error; } } else { error_message = "invalid string: surrogate U+D800..U+DBFF must be followed by U+DC00..U+DFFF"; return token_type::parse_error; } } else { if (JSON_HEDLEY_UNLIKELY(0xDC00 <= codepoint1 && codepoint1 <= 0xDFFF)) { error_message = "invalid string: surrogate U+DC00..U+DFFF must follow U+D800..U+DBFF"; return token_type::parse_error; } } // result of the above calculation yields a proper codepoint JSON_ASSERT(0x00 <= codepoint && codepoint <= 0x10FFFF); // translate codepoint into bytes if (codepoint < 0x80) { // 1-byte characters: 0xxxxxxx (ASCII) add(static_cast(codepoint)); } else if (codepoint <= 0x7FF) { // 2-byte characters: 110xxxxx 10xxxxxx add(static_cast(0xC0u | (static_cast(codepoint) >> 6u))); add(static_cast(0x80u | (static_cast(codepoint) & 0x3Fu))); } else if (codepoint <= 0xFFFF) { // 3-byte characters: 1110xxxx 10xxxxxx 10xxxxxx add(static_cast(0xE0u | (static_cast(codepoint) >> 12u))); add(static_cast(0x80u | ((static_cast(codepoint) >> 6u) & 0x3Fu))); add(static_cast(0x80u | (static_cast(codepoint) & 0x3Fu))); } else { // 4-byte characters: 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx add(static_cast(0xF0u | (static_cast(codepoint) >> 18u))); add(static_cast(0x80u | ((static_cast(codepoint) >> 12u) & 0x3Fu))); add(static_cast(0x80u | ((static_cast(codepoint) >> 6u) & 0x3Fu))); add(static_cast(0x80u | (static_cast(codepoint) & 0x3Fu))); } break; } // other characters after escape default: error_message = "invalid string: forbidden character after backslash"; return token_type::parse_error; } break; } // invalid control characters case 0x00: { error_message = "invalid string: control character U+0000 (NUL) must be escaped to \\u0000"; return token_type::parse_error; } case 0x01: { error_message = "invalid string: control character U+0001 (SOH) must be escaped to \\u0001"; return token_type::parse_error; } case 0x02: { error_message = "invalid string: control character U+0002 (STX) must be escaped to \\u0002"; return token_type::parse_error; } case 0x03: { error_message = "invalid string: control character U+0003 (ETX) must be escaped to \\u0003"; return token_type::parse_error; } case 0x04: { error_message = "invalid string: control character U+0004 (EOT) must be escaped to \\u0004"; return token_type::parse_error; } case 0x05: { error_message = "invalid string: control character U+0005 (ENQ) must be escaped to \\u0005"; return token_type::parse_error; } case 0x06: { error_message = "invalid string: control character U+0006 (ACK) must be escaped to \\u0006"; return token_type::parse_error; } case 0x07: { error_message = "invalid string: control character U+0007 (BEL) must be escaped to \\u0007"; return token_type::parse_error; } case 0x08: { error_message = "invalid string: control character U+0008 (BS) must be escaped to \\u0008 or \\b"; return token_type::parse_error; } case 0x09: { error_message = "invalid string: control character U+0009 (HT) must be escaped to \\u0009 or \\t"; return token_type::parse_error; } case 0x0A: { error_message = "invalid string: control character U+000A (LF) must be escaped to \\u000A or \\n"; return token_type::parse_error; } case 0x0B: { error_message = "invalid string: control character U+000B (VT) must be escaped to \\u000B"; return token_type::parse_error; } case 0x0C: { error_message = "invalid string: control character U+000C (FF) must be escaped to \\u000C or \\f"; return token_type::parse_error; } case 0x0D: { error_message = "invalid string: control character U+000D (CR) must be escaped to \\u000D or \\r"; return token_type::parse_error; } case 0x0E: { error_message = "invalid string: control character U+000E (SO) must be escaped to \\u000E"; return token_type::parse_error; } case 0x0F: { error_message = "invalid string: control character U+000F (SI) must be escaped to \\u000F"; return token_type::parse_error; } case 0x10: { error_message = "invalid string: control character U+0010 (DLE) must be escaped to \\u0010"; return token_type::parse_error; } case 0x11: { error_message = "invalid string: control character U+0011 (DC1) must be escaped to \\u0011"; return token_type::parse_error; } case 0x12: { error_message = "invalid string: control character U+0012 (DC2) must be escaped to \\u0012"; return token_type::parse_error; } case 0x13: { error_message = "invalid string: control character U+0013 (DC3) must be escaped to \\u0013"; return token_type::parse_error; } case 0x14: { error_message = "invalid string: control character U+0014 (DC4) must be escaped to \\u0014"; return token_type::parse_error; } case 0x15: { error_message = "invalid string: control character U+0015 (NAK) must be escaped to \\u0015"; return token_type::parse_error; } case 0x16: { error_message = "invalid string: control character U+0016 (SYN) must be escaped to \\u0016"; return token_type::parse_error; } case 0x17: { error_message = "invalid string: control character U+0017 (ETB) must be escaped to \\u0017"; return token_type::parse_error; } case 0x18: { error_message = "invalid string: control character U+0018 (CAN) must be escaped to \\u0018"; return token_type::parse_error; } case 0x19: { error_message = "invalid string: control character U+0019 (EM) must be escaped to \\u0019"; return token_type::parse_error; } case 0x1A: { error_message = "invalid string: control character U+001A (SUB) must be escaped to \\u001A"; return token_type::parse_error; } case 0x1B: { error_message = "invalid string: control character U+001B (ESC) must be escaped to \\u001B"; return token_type::parse_error; } case 0x1C: { error_message = "invalid string: control character U+001C (FS) must be escaped to \\u001C"; return token_type::parse_error; } case 0x1D: { error_message = "invalid string: control character U+001D (GS) must be escaped to \\u001D"; return token_type::parse_error; } case 0x1E: { error_message = "invalid string: control character U+001E (RS) must be escaped to \\u001E"; return token_type::parse_error; } case 0x1F: { error_message = "invalid string: control character U+001F (US) must be escaped to \\u001F"; return token_type::parse_error; } // U+0020..U+007F (except U+0022 (quote) and U+005C (backspace)) case 0x20: case 0x21: case 0x23: case 0x24: case 0x25: case 0x26: case 0x27: case 0x28: case 0x29: case 0x2A: case 0x2B: case 0x2C: case 0x2D: case 0x2E: case 0x2F: case 0x30: case 0x31: case 0x32: case 0x33: case 0x34: case 0x35: case 0x36: case 0x37: case 0x38: case 0x39: case 0x3A: case 0x3B: case 0x3C: case 0x3D: case 0x3E: case 0x3F: case 0x40: case 0x41: case 0x42: case 0x43: case 0x44: case 0x45: case 0x46: case 0x47: case 0x48: case 0x49: case 0x4A: case 0x4B: case 0x4C: case 0x4D: case 0x4E: case 0x4F: case 0x50: case 0x51: case 0x52: case 0x53: case 0x54: case 0x55: case 0x56: case 0x57: case 0x58: case 0x59: case 0x5A: case 0x5B: case 0x5D: case 0x5E: case 0x5F: case 0x60: case 0x61: case 0x62: case 0x63: case 0x64: case 0x65: case 0x66: case 0x67: case 0x68: case 0x69: case 0x6A: case 0x6B: case 0x6C: case 0x6D: case 0x6E: case 0x6F: case 0x70: case 0x71: case 0x72: case 0x73: case 0x74: case 0x75: case 0x76: case 0x77: case 0x78: case 0x79: case 0x7A: case 0x7B: case 0x7C: case 0x7D: case 0x7E: case 0x7F: { add(current); break; } // U+0080..U+07FF: bytes C2..DF 80..BF case 0xC2: case 0xC3: case 0xC4: case 0xC5: case 0xC6: case 0xC7: case 0xC8: case 0xC9: case 0xCA: case 0xCB: case 0xCC: case 0xCD: case 0xCE: case 0xCF: case 0xD0: case 0xD1: case 0xD2: case 0xD3: case 0xD4: case 0xD5: case 0xD6: case 0xD7: case 0xD8: case 0xD9: case 0xDA: case 0xDB: case 0xDC: case 0xDD: case 0xDE: case 0xDF: { if (JSON_HEDLEY_UNLIKELY(!next_byte_in_range({0x80, 0xBF}))) { return token_type::parse_error; } break; } // U+0800..U+0FFF: bytes E0 A0..BF 80..BF case 0xE0: { if (JSON_HEDLEY_UNLIKELY(!(next_byte_in_range({0xA0, 0xBF, 0x80, 0xBF})))) { return token_type::parse_error; } break; } // U+1000..U+CFFF: bytes E1..EC 80..BF 80..BF // U+E000..U+FFFF: bytes EE..EF 80..BF 80..BF case 0xE1: case 0xE2: case 0xE3: case 0xE4: case 0xE5: case 0xE6: case 0xE7: case 0xE8: case 0xE9: case 0xEA: case 0xEB: case 0xEC: case 0xEE: case 0xEF: { if (JSON_HEDLEY_UNLIKELY(!(next_byte_in_range({0x80, 0xBF, 0x80, 0xBF})))) { return token_type::parse_error; } break; } // U+D000..U+D7FF: bytes ED 80..9F 80..BF case 0xED: { if (JSON_HEDLEY_UNLIKELY(!(next_byte_in_range({0x80, 0x9F, 0x80, 0xBF})))) { return token_type::parse_error; } break; } // U+10000..U+3FFFF F0 90..BF 80..BF 80..BF case 0xF0: { if (JSON_HEDLEY_UNLIKELY(!(next_byte_in_range({0x90, 0xBF, 0x80, 0xBF, 0x80, 0xBF})))) { return token_type::parse_error; } break; } // U+40000..U+FFFFF F1..F3 80..BF 80..BF 80..BF case 0xF1: case 0xF2: case 0xF3: { if (JSON_HEDLEY_UNLIKELY(!(next_byte_in_range({0x80, 0xBF, 0x80, 0xBF, 0x80, 0xBF})))) { return token_type::parse_error; } break; } // U+100000..U+10FFFF F4 80..8F 80..BF 80..BF case 0xF4: { if (JSON_HEDLEY_UNLIKELY(!(next_byte_in_range({0x80, 0x8F, 0x80, 0xBF, 0x80, 0xBF})))) { return token_type::parse_error; } break; } // remaining bytes (80..C1 and F5..FF) are ill-formed default: { error_message = "invalid string: ill-formed UTF-8 byte"; return token_type::parse_error; } } } } /*! * @brief scan a comment * @return whether comment could be scanned successfully */ bool scan_comment() { switch (get()) { // single-line comments skip input until a newline or EOF is read case '/': { while (true) { switch (get()) { case '\n': case '\r': case std::char_traits::eof(): case '\0': return true; default: break; } } } // multi-line comments skip input until */ is read case '*': { while (true) { switch (get()) { case std::char_traits::eof(): case '\0': { error_message = "invalid comment; missing closing '*/'"; return false; } case '*': { switch (get()) { case '/': return true; default: { unget(); continue; } } } default: continue; } } } // unexpected character after reading '/' default: { error_message = "invalid comment; expecting '/' or '*' after '/'"; return false; } } } JSON_HEDLEY_NON_NULL(2) static void strtof(float& f, const char* str, char** endptr) noexcept { f = std::strtof(str, endptr); } JSON_HEDLEY_NON_NULL(2) static void strtof(double& f, const char* str, char** endptr) noexcept { f = std::strtod(str, endptr); } JSON_HEDLEY_NON_NULL(2) static void strtof(long double& f, const char* str, char** endptr) noexcept { f = std::strtold(str, endptr); } /*! @brief scan a number literal This function scans a string according to Sect. 6 of RFC 7159. The function is realized with a deterministic finite state machine derived from the grammar described in RFC 7159. Starting in state "init", the input is read and used to determined the next state. Only state "done" accepts the number. State "error" is a trap state to model errors. In the table below, "anything" means any character but the ones listed before. state | 0 | 1-9 | e E | + | - | . | anything ---------|----------|----------|----------|---------|---------|----------|----------- init | zero | any1 | [error] | [error] | minus | [error] | [error] minus | zero | any1 | [error] | [error] | [error] | [error] | [error] zero | done | done | exponent | done | done | decimal1 | done any1 | any1 | any1 | exponent | done | done | decimal1 | done decimal1 | decimal2 | decimal2 | [error] | [error] | [error] | [error] | [error] decimal2 | decimal2 | decimal2 | exponent | done | done | done | done exponent | any2 | any2 | [error] | sign | sign | [error] | [error] sign | any2 | any2 | [error] | [error] | [error] | [error] | [error] any2 | any2 | any2 | done | done | done | done | done The state machine is realized with one label per state (prefixed with "scan_number_") and `goto` statements between them. The state machine contains cycles, but any cycle can be left when EOF is read. Therefore, the function is guaranteed to terminate. During scanning, the read bytes are stored in token_buffer. This string is then converted to a signed integer, an unsigned integer, or a floating-point number. @return token_type::value_unsigned, token_type::value_integer, or token_type::value_float if number could be successfully scanned, token_type::parse_error otherwise @note The scanner is independent of the current locale. Internally, the locale's decimal point is used instead of `.` to work with the locale-dependent converters. */ token_type scan_number() // lgtm [cpp/use-of-goto] { // reset token_buffer to store the number's bytes reset(); // the type of the parsed number; initially set to unsigned; will be // changed if minus sign, decimal point or exponent is read token_type number_type = token_type::value_unsigned; // state (init): we just found out we need to scan a number switch (current) { case '-': { add(current); goto scan_number_minus; } case '0': { add(current); goto scan_number_zero; } case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': { add(current); goto scan_number_any1; } // all other characters are rejected outside scan_number() default: // LCOV_EXCL_LINE JSON_ASSERT(false); // LCOV_EXCL_LINE } scan_number_minus: // state: we just parsed a leading minus sign number_type = token_type::value_integer; switch (get()) { case '0': { add(current); goto scan_number_zero; } case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': { add(current); goto scan_number_any1; } default: { error_message = "invalid number; expected digit after '-'"; return token_type::parse_error; } } scan_number_zero: // state: we just parse a zero (maybe with a leading minus sign) switch (get()) { case '.': { add(decimal_point_char); goto scan_number_decimal1; } case 'e': case 'E': { add(current); goto scan_number_exponent; } default: goto scan_number_done; } scan_number_any1: // state: we just parsed a number 0-9 (maybe with a leading minus sign) switch (get()) { case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': { add(current); goto scan_number_any1; } case '.': { add(decimal_point_char); goto scan_number_decimal1; } case 'e': case 'E': { add(current); goto scan_number_exponent; } default: goto scan_number_done; } scan_number_decimal1: // state: we just parsed a decimal point number_type = token_type::value_float; switch (get()) { case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': { add(current); goto scan_number_decimal2; } default: { error_message = "invalid number; expected digit after '.'"; return token_type::parse_error; } } scan_number_decimal2: // we just parsed at least one number after a decimal point switch (get()) { case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': { add(current); goto scan_number_decimal2; } case 'e': case 'E': { add(current); goto scan_number_exponent; } default: goto scan_number_done; } scan_number_exponent: // we just parsed an exponent number_type = token_type::value_float; switch (get()) { case '+': case '-': { add(current); goto scan_number_sign; } case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': { add(current); goto scan_number_any2; } default: { error_message = "invalid number; expected '+', '-', or digit after exponent"; return token_type::parse_error; } } scan_number_sign: // we just parsed an exponent sign switch (get()) { case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': { add(current); goto scan_number_any2; } default: { error_message = "invalid number; expected digit after exponent sign"; return token_type::parse_error; } } scan_number_any2: // we just parsed a number after the exponent or exponent sign switch (get()) { case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': { add(current); goto scan_number_any2; } default: goto scan_number_done; } scan_number_done: // unget the character after the number (we only read it to know that // we are done scanning a number) unget(); char* endptr = nullptr; errno = 0; // try to parse integers first and fall back to floats if (number_type == token_type::value_unsigned) { const auto x = std::strtoull(token_buffer.data(), &endptr, 10); // we checked the number format before JSON_ASSERT(endptr == token_buffer.data() + token_buffer.size()); if (errno == 0) { value_unsigned = static_cast(x); if (value_unsigned == x) { return token_type::value_unsigned; } } } else if (number_type == token_type::value_integer) { const auto x = std::strtoll(token_buffer.data(), &endptr, 10); // we checked the number format before JSON_ASSERT(endptr == token_buffer.data() + token_buffer.size()); if (errno == 0) { value_integer = static_cast(x); if (value_integer == x) { return token_type::value_integer; } } } // this code is reached if we parse a floating-point number or if an // integer conversion above failed strtof(value_float, token_buffer.data(), &endptr); // we checked the number format before JSON_ASSERT(endptr == token_buffer.data() + token_buffer.size()); return token_type::value_float; } /*! @param[in] literal_text the literal text to expect @param[in] length the length of the passed literal text @param[in] return_type the token type to return on success */ JSON_HEDLEY_NON_NULL(2) token_type scan_literal(const char_type* literal_text, const std::size_t length, token_type return_type) { JSON_ASSERT(std::char_traits::to_char_type(current) == literal_text[0]); for (std::size_t i = 1; i < length; ++i) { if (JSON_HEDLEY_UNLIKELY(std::char_traits::to_char_type(get()) != literal_text[i])) { error_message = "invalid literal"; return token_type::parse_error; } } return return_type; } ///////////////////// // input management ///////////////////// /// reset token_buffer; current character is beginning of token void reset() noexcept { token_buffer.clear(); token_string.clear(); token_string.push_back(std::char_traits::to_char_type(current)); } /* @brief get next character from the input This function provides the interface to the used input adapter. It does not throw in case the input reached EOF, but returns a `std::char_traits::eof()` in that case. Stores the scanned characters for use in error messages. @return character read from the input */ char_int_type get() { ++position.chars_read_total; ++position.chars_read_current_line; if (next_unget) { // just reset the next_unget variable and work with current next_unget = false; } else { current = ia.get_character(); } if (JSON_HEDLEY_LIKELY(current != std::char_traits::eof())) { token_string.push_back(std::char_traits::to_char_type(current)); } if (current == '\n') { ++position.lines_read; position.chars_read_current_line = 0; } return current; } /*! @brief unget current character (read it again on next get) We implement unget by setting variable next_unget to true. The input is not changed - we just simulate ungetting by modifying chars_read_total, chars_read_current_line, and token_string. The next call to get() will behave as if the unget character is read again. */ void unget() { next_unget = true; --position.chars_read_total; // in case we "unget" a newline, we have to also decrement the lines_read if (position.chars_read_current_line == 0) { if (position.lines_read > 0) { --position.lines_read; } } else { --position.chars_read_current_line; } if (JSON_HEDLEY_LIKELY(current != std::char_traits::eof())) { JSON_ASSERT(!token_string.empty()); token_string.pop_back(); } } /// add a character to token_buffer void add(char_int_type c) { token_buffer.push_back(static_cast(c)); } public: ///////////////////// // value getters ///////////////////// /// return integer value constexpr number_integer_t get_number_integer() const noexcept { return value_integer; } /// return unsigned integer value constexpr number_unsigned_t get_number_unsigned() const noexcept { return value_unsigned; } /// return floating-point value constexpr number_float_t get_number_float() const noexcept { return value_float; } /// return current string value (implicitly resets the token; useful only once) string_t& get_string() { return token_buffer; } ///////////////////// // diagnostics ///////////////////// /// return position of last read token constexpr position_t get_position() const noexcept { return position; } /// return the last read token (for errors only). Will never contain EOF /// (an arbitrary value that is not a valid char value, often -1), because /// 255 may legitimately occur. May contain NUL, which should be escaped. std::string get_token_string() const { // escape control characters std::string result; for (const auto c : token_string) { if (static_cast(c) <= '\x1F') { // escape control characters std::array cs{{}}; (std::snprintf)(cs.data(), cs.size(), "", static_cast(c)); result += cs.data(); } else { // add character as is result.push_back(static_cast(c)); } } return result; } /// return syntax error message JSON_HEDLEY_RETURNS_NON_NULL constexpr const char* get_error_message() const noexcept { return error_message; } ///////////////////// // actual scanner ///////////////////// /*! @brief skip the UTF-8 byte order mark @return true iff there is no BOM or the correct BOM has been skipped */ bool skip_bom() { if (get() == 0xEF) { // check if we completely parse the BOM return get() == 0xBB && get() == 0xBF; } // the first character is not the beginning of the BOM; unget it to // process is later unget(); return true; } void skip_whitespace() { do { get(); } while (current == ' ' || current == '\t' || current == '\n' || current == '\r'); } token_type scan() { // initially, skip the BOM if (position.chars_read_total == 0 && !skip_bom()) { error_message = "invalid BOM; must be 0xEF 0xBB 0xBF if given"; return token_type::parse_error; } // read next character and ignore whitespace skip_whitespace(); // ignore comments while (ignore_comments && current == '/') { if (!scan_comment()) { return token_type::parse_error; } // skip following whitespace skip_whitespace(); } switch (current) { // structural characters case '[': return token_type::begin_array; case ']': return token_type::end_array; case '{': return token_type::begin_object; case '}': return token_type::end_object; case ':': return token_type::name_separator; case ',': return token_type::value_separator; // literals case 't': { std::array true_literal = {{'t', 'r', 'u', 'e'}}; return scan_literal(true_literal.data(), true_literal.size(), token_type::literal_true); } case 'f': { std::array false_literal = {{'f', 'a', 'l', 's', 'e'}}; return scan_literal(false_literal.data(), false_literal.size(), token_type::literal_false); } case 'n': { std::array null_literal = {{'n', 'u', 'l', 'l'}}; return scan_literal(null_literal.data(), null_literal.size(), token_type::literal_null); } // string case '\"': return scan_string(); // number case '-': case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': return scan_number(); // end of input (the null byte is needed when parsing from // string literals) case '\0': case std::char_traits::eof(): return token_type::end_of_input; // error default: error_message = "invalid literal"; return token_type::parse_error; } } private: /// input adapter InputAdapterType ia; /// whether comments should be ignored (true) or signaled as errors (false) const bool ignore_comments = false; /// the current character char_int_type current = std::char_traits::eof(); /// whether the next get() call should just return current bool next_unget = false; /// the start position of the current token position_t position {}; /// raw input token string (for error messages) std::vector token_string {}; /// buffer for variable-length tokens (numbers, strings) string_t token_buffer {}; /// a description of occurred lexer errors const char* error_message = ""; // number values number_integer_t value_integer = 0; number_unsigned_t value_unsigned = 0; number_float_t value_float = 0; /// the decimal point const char_int_type decimal_point_char = '.'; }; } // namespace detail } // namespace nlohmann // #include // #include #include // size_t #include // declval #include // string // #include // #include namespace nlohmann { namespace detail { template using null_function_t = decltype(std::declval().null()); template using boolean_function_t = decltype(std::declval().boolean(std::declval())); template using number_integer_function_t = decltype(std::declval().number_integer(std::declval())); template using number_unsigned_function_t = decltype(std::declval().number_unsigned(std::declval())); template using number_float_function_t = decltype(std::declval().number_float( std::declval(), std::declval())); template using string_function_t = decltype(std::declval().string(std::declval())); template using binary_function_t = decltype(std::declval().binary(std::declval())); template using start_object_function_t = decltype(std::declval().start_object(std::declval())); template using key_function_t = decltype(std::declval().key(std::declval())); template using end_object_function_t = decltype(std::declval().end_object()); template using start_array_function_t = decltype(std::declval().start_array(std::declval())); template using end_array_function_t = decltype(std::declval().end_array()); template using parse_error_function_t = decltype(std::declval().parse_error( std::declval(), std::declval(), std::declval())); template struct is_sax { private: static_assert(is_basic_json::value, "BasicJsonType must be of type basic_json<...>"); using number_integer_t = typename BasicJsonType::number_integer_t; using number_unsigned_t = typename BasicJsonType::number_unsigned_t; using number_float_t = typename BasicJsonType::number_float_t; using string_t = typename BasicJsonType::string_t; using binary_t = typename BasicJsonType::binary_t; using exception_t = typename BasicJsonType::exception; public: static constexpr bool value = is_detected_exact::value && is_detected_exact::value && is_detected_exact::value && is_detected_exact::value && is_detected_exact::value && is_detected_exact::value && is_detected_exact::value && is_detected_exact::value && is_detected_exact::value && is_detected_exact::value && is_detected_exact::value && is_detected_exact::value && is_detected_exact::value; }; template struct is_sax_static_asserts { private: static_assert(is_basic_json::value, "BasicJsonType must be of type basic_json<...>"); using number_integer_t = typename BasicJsonType::number_integer_t; using number_unsigned_t = typename BasicJsonType::number_unsigned_t; using number_float_t = typename BasicJsonType::number_float_t; using string_t = typename BasicJsonType::string_t; using binary_t = typename BasicJsonType::binary_t; using exception_t = typename BasicJsonType::exception; public: static_assert(is_detected_exact::value, "Missing/invalid function: bool null()"); static_assert(is_detected_exact::value, "Missing/invalid function: bool boolean(bool)"); static_assert(is_detected_exact::value, "Missing/invalid function: bool boolean(bool)"); static_assert( is_detected_exact::value, "Missing/invalid function: bool number_integer(number_integer_t)"); static_assert( is_detected_exact::value, "Missing/invalid function: bool number_unsigned(number_unsigned_t)"); static_assert(is_detected_exact::value, "Missing/invalid function: bool number_float(number_float_t, const string_t&)"); static_assert( is_detected_exact::value, "Missing/invalid function: bool string(string_t&)"); static_assert( is_detected_exact::value, "Missing/invalid function: bool binary(binary_t&)"); static_assert(is_detected_exact::value, "Missing/invalid function: bool start_object(std::size_t)"); static_assert(is_detected_exact::value, "Missing/invalid function: bool key(string_t&)"); static_assert(is_detected_exact::value, "Missing/invalid function: bool end_object()"); static_assert(is_detected_exact::value, "Missing/invalid function: bool start_array(std::size_t)"); static_assert(is_detected_exact::value, "Missing/invalid function: bool end_array()"); static_assert( is_detected_exact::value, "Missing/invalid function: bool parse_error(std::size_t, const " "std::string&, const exception&)"); }; } // namespace detail } // namespace nlohmann // #include namespace nlohmann { namespace detail { /// how to treat CBOR tags enum class cbor_tag_handler_t { error, ///< throw a parse_error exception in case of a tag ignore ///< ignore tags }; /*! @brief determine system byte order @return true if and only if system's byte order is little endian @note from https://stackoverflow.com/a/1001328/266378 */ static inline bool little_endianess(int num = 1) noexcept { return *reinterpret_cast(&num) == 1; } /////////////////// // binary reader // /////////////////// /*! @brief deserialization of CBOR, MessagePack, and UBJSON values */ template> class binary_reader { using number_integer_t = typename BasicJsonType::number_integer_t; using number_unsigned_t = typename BasicJsonType::number_unsigned_t; using number_float_t = typename BasicJsonType::number_float_t; using string_t = typename BasicJsonType::string_t; using binary_t = typename BasicJsonType::binary_t; using json_sax_t = SAX; using char_type = typename InputAdapterType::char_type; using char_int_type = typename std::char_traits::int_type; public: /*! @brief create a binary reader @param[in] adapter input adapter to read from */ explicit binary_reader(InputAdapterType&& adapter) : ia(std::move(adapter)) { (void)detail::is_sax_static_asserts {}; } // make class move-only binary_reader(const binary_reader&) = delete; binary_reader(binary_reader&&) = default; binary_reader& operator=(const binary_reader&) = delete; binary_reader& operator=(binary_reader&&) = default; ~binary_reader() = default; /*! @param[in] format the binary format to parse @param[in] sax_ a SAX event processor @param[in] strict whether to expect the input to be consumed completed @param[in] tag_handler how to treat CBOR tags @return */ JSON_HEDLEY_NON_NULL(3) bool sax_parse(const input_format_t format, json_sax_t* sax_, const bool strict = true, const cbor_tag_handler_t tag_handler = cbor_tag_handler_t::error) { sax = sax_; bool result = false; switch (format) { case input_format_t::bson: result = parse_bson_internal(); break; case input_format_t::cbor: result = parse_cbor_internal(true, tag_handler); break; case input_format_t::msgpack: result = parse_msgpack_internal(); break; case input_format_t::ubjson: result = parse_ubjson_internal(); break; default: // LCOV_EXCL_LINE JSON_ASSERT(false); // LCOV_EXCL_LINE } // strict mode: next byte must be EOF if (result && strict) { if (format == input_format_t::ubjson) { get_ignore_noop(); } else { get(); } if (JSON_HEDLEY_UNLIKELY(current != std::char_traits::eof())) { return sax->parse_error(chars_read, get_token_string(), parse_error::create(110, chars_read, exception_message(format, "expected end of input; last byte: 0x" + get_token_string(), "value"))); } } return result; } private: ////////// // BSON // ////////// /*! @brief Reads in a BSON-object and passes it to the SAX-parser. @return whether a valid BSON-value was passed to the SAX parser */ bool parse_bson_internal() { std::int32_t document_size{}; get_number(input_format_t::bson, document_size); if (JSON_HEDLEY_UNLIKELY(!sax->start_object(std::size_t(-1)))) { return false; } if (JSON_HEDLEY_UNLIKELY(!parse_bson_element_list(/*is_array*/false))) { return false; } return sax->end_object(); } /*! @brief Parses a C-style string from the BSON input. @param[in, out] result A reference to the string variable where the read string is to be stored. @return `true` if the \x00-byte indicating the end of the string was encountered before the EOF; false` indicates an unexpected EOF. */ bool get_bson_cstr(string_t& result) { auto out = std::back_inserter(result); while (true) { get(); if (JSON_HEDLEY_UNLIKELY(!unexpect_eof(input_format_t::bson, "cstring"))) { return false; } if (current == 0x00) { return true; } *out++ = static_cast(current); } } /*! @brief Parses a zero-terminated string of length @a len from the BSON input. @param[in] len The length (including the zero-byte at the end) of the string to be read. @param[in, out] result A reference to the string variable where the read string is to be stored. @tparam NumberType The type of the length @a len @pre len >= 1 @return `true` if the string was successfully parsed */ template bool get_bson_string(const NumberType len, string_t& result) { if (JSON_HEDLEY_UNLIKELY(len < 1)) { auto last_token = get_token_string(); return sax->parse_error(chars_read, last_token, parse_error::create(112, chars_read, exception_message(input_format_t::bson, "string length must be at least 1, is " + std::to_string(len), "string"))); } return get_string(input_format_t::bson, len - static_cast(1), result) && get() != std::char_traits::eof(); } /*! @brief Parses a byte array input of length @a len from the BSON input. @param[in] len The length of the byte array to be read. @param[in, out] result A reference to the binary variable where the read array is to be stored. @tparam NumberType The type of the length @a len @pre len >= 0 @return `true` if the byte array was successfully parsed */ template bool get_bson_binary(const NumberType len, binary_t& result) { if (JSON_HEDLEY_UNLIKELY(len < 0)) { auto last_token = get_token_string(); return sax->parse_error(chars_read, last_token, parse_error::create(112, chars_read, exception_message(input_format_t::bson, "byte array length cannot be negative, is " + std::to_string(len), "binary"))); } // All BSON binary values have a subtype std::uint8_t subtype{}; get_number(input_format_t::bson, subtype); result.set_subtype(subtype); return get_binary(input_format_t::bson, len, result); } /*! @brief Read a BSON document element of the given @a element_type. @param[in] element_type The BSON element type, c.f. http://bsonspec.org/spec.html @param[in] element_type_parse_position The position in the input stream, where the `element_type` was read. @warning Not all BSON element types are supported yet. An unsupported @a element_type will give rise to a parse_error.114: Unsupported BSON record type 0x... @return whether a valid BSON-object/array was passed to the SAX parser */ bool parse_bson_element_internal(const char_int_type element_type, const std::size_t element_type_parse_position) { switch (element_type) { case 0x01: // double { double number{}; return get_number(input_format_t::bson, number) && sax->number_float(static_cast(number), ""); } case 0x02: // string { std::int32_t len{}; string_t value; return get_number(input_format_t::bson, len) && get_bson_string(len, value) && sax->string(value); } case 0x03: // object { return parse_bson_internal(); } case 0x04: // array { return parse_bson_array(); } case 0x05: // binary { std::int32_t len{}; binary_t value; return get_number(input_format_t::bson, len) && get_bson_binary(len, value) && sax->binary(value); } case 0x08: // boolean { return sax->boolean(get() != 0); } case 0x0A: // null { return sax->null(); } case 0x10: // int32 { std::int32_t value{}; return get_number(input_format_t::bson, value) && sax->number_integer(value); } case 0x12: // int64 { std::int64_t value{}; return get_number(input_format_t::bson, value) && sax->number_integer(value); } default: // anything else not supported (yet) { std::array cr{{}}; (std::snprintf)(cr.data(), cr.size(), "%.2hhX", static_cast(element_type)); return sax->parse_error(element_type_parse_position, std::string(cr.data()), parse_error::create(114, element_type_parse_position, "Unsupported BSON record type 0x" + std::string(cr.data()))); } } } /*! @brief Read a BSON element list (as specified in the BSON-spec) The same binary layout is used for objects and arrays, hence it must be indicated with the argument @a is_array which one is expected (true --> array, false --> object). @param[in] is_array Determines if the element list being read is to be treated as an object (@a is_array == false), or as an array (@a is_array == true). @return whether a valid BSON-object/array was passed to the SAX parser */ bool parse_bson_element_list(const bool is_array) { string_t key; while (auto element_type = get()) { if (JSON_HEDLEY_UNLIKELY(!unexpect_eof(input_format_t::bson, "element list"))) { return false; } const std::size_t element_type_parse_position = chars_read; if (JSON_HEDLEY_UNLIKELY(!get_bson_cstr(key))) { return false; } if (!is_array && !sax->key(key)) { return false; } if (JSON_HEDLEY_UNLIKELY(!parse_bson_element_internal(element_type, element_type_parse_position))) { return false; } // get_bson_cstr only appends key.clear(); } return true; } /*! @brief Reads an array from the BSON input and passes it to the SAX-parser. @return whether a valid BSON-array was passed to the SAX parser */ bool parse_bson_array() { std::int32_t document_size{}; get_number(input_format_t::bson, document_size); if (JSON_HEDLEY_UNLIKELY(!sax->start_array(std::size_t(-1)))) { return false; } if (JSON_HEDLEY_UNLIKELY(!parse_bson_element_list(/*is_array*/true))) { return false; } return sax->end_array(); } ////////// // CBOR // ////////// /*! @param[in] get_char whether a new character should be retrieved from the input (true) or whether the last read character should be considered instead (false) @param[in] tag_handler how CBOR tags should be treated @return whether a valid CBOR value was passed to the SAX parser */ bool parse_cbor_internal(const bool get_char, const cbor_tag_handler_t tag_handler) { switch (get_char ? get() : current) { // EOF case std::char_traits::eof(): return unexpect_eof(input_format_t::cbor, "value"); // Integer 0x00..0x17 (0..23) case 0x00: case 0x01: case 0x02: case 0x03: case 0x04: case 0x05: case 0x06: case 0x07: case 0x08: case 0x09: case 0x0A: case 0x0B: case 0x0C: case 0x0D: case 0x0E: case 0x0F: case 0x10: case 0x11: case 0x12: case 0x13: case 0x14: case 0x15: case 0x16: case 0x17: return sax->number_unsigned(static_cast(current)); case 0x18: // Unsigned integer (one-byte uint8_t follows) { std::uint8_t number{}; return get_number(input_format_t::cbor, number) && sax->number_unsigned(number); } case 0x19: // Unsigned integer (two-byte uint16_t follows) { std::uint16_t number{}; return get_number(input_format_t::cbor, number) && sax->number_unsigned(number); } case 0x1A: // Unsigned integer (four-byte uint32_t follows) { std::uint32_t number{}; return get_number(input_format_t::cbor, number) && sax->number_unsigned(number); } case 0x1B: // Unsigned integer (eight-byte uint64_t follows) { std::uint64_t number{}; return get_number(input_format_t::cbor, number) && sax->number_unsigned(number); } // Negative integer -1-0x00..-1-0x17 (-1..-24) case 0x20: case 0x21: case 0x22: case 0x23: case 0x24: case 0x25: case 0x26: case 0x27: case 0x28: case 0x29: case 0x2A: case 0x2B: case 0x2C: case 0x2D: case 0x2E: case 0x2F: case 0x30: case 0x31: case 0x32: case 0x33: case 0x34: case 0x35: case 0x36: case 0x37: return sax->number_integer(static_cast(0x20 - 1 - current)); case 0x38: // Negative integer (one-byte uint8_t follows) { std::uint8_t number{}; return get_number(input_format_t::cbor, number) && sax->number_integer(static_cast(-1) - number); } case 0x39: // Negative integer -1-n (two-byte uint16_t follows) { std::uint16_t number{}; return get_number(input_format_t::cbor, number) && sax->number_integer(static_cast(-1) - number); } case 0x3A: // Negative integer -1-n (four-byte uint32_t follows) { std::uint32_t number{}; return get_number(input_format_t::cbor, number) && sax->number_integer(static_cast(-1) - number); } case 0x3B: // Negative integer -1-n (eight-byte uint64_t follows) { std::uint64_t number{}; return get_number(input_format_t::cbor, number) && sax->number_integer(static_cast(-1) - static_cast(number)); } // Binary data (0x00..0x17 bytes follow) case 0x40: case 0x41: case 0x42: case 0x43: case 0x44: case 0x45: case 0x46: case 0x47: case 0x48: case 0x49: case 0x4A: case 0x4B: case 0x4C: case 0x4D: case 0x4E: case 0x4F: case 0x50: case 0x51: case 0x52: case 0x53: case 0x54: case 0x55: case 0x56: case 0x57: case 0x58: // Binary data (one-byte uint8_t for n follows) case 0x59: // Binary data (two-byte uint16_t for n follow) case 0x5A: // Binary data (four-byte uint32_t for n follow) case 0x5B: // Binary data (eight-byte uint64_t for n follow) case 0x5F: // Binary data (indefinite length) { binary_t b; return get_cbor_binary(b) && sax->binary(b); } // UTF-8 string (0x00..0x17 bytes follow) case 0x60: case 0x61: case 0x62: case 0x63: case 0x64: case 0x65: case 0x66: case 0x67: case 0x68: case 0x69: case 0x6A: case 0x6B: case 0x6C: case 0x6D: case 0x6E: case 0x6F: case 0x70: case 0x71: case 0x72: case 0x73: case 0x74: case 0x75: case 0x76: case 0x77: case 0x78: // UTF-8 string (one-byte uint8_t for n follows) case 0x79: // UTF-8 string (two-byte uint16_t for n follow) case 0x7A: // UTF-8 string (four-byte uint32_t for n follow) case 0x7B: // UTF-8 string (eight-byte uint64_t for n follow) case 0x7F: // UTF-8 string (indefinite length) { string_t s; return get_cbor_string(s) && sax->string(s); } // array (0x00..0x17 data items follow) case 0x80: case 0x81: case 0x82: case 0x83: case 0x84: case 0x85: case 0x86: case 0x87: case 0x88: case 0x89: case 0x8A: case 0x8B: case 0x8C: case 0x8D: case 0x8E: case 0x8F: case 0x90: case 0x91: case 0x92: case 0x93: case 0x94: case 0x95: case 0x96: case 0x97: return get_cbor_array(static_cast(static_cast(current) & 0x1Fu), tag_handler); case 0x98: // array (one-byte uint8_t for n follows) { std::uint8_t len{}; return get_number(input_format_t::cbor, len) && get_cbor_array(static_cast(len), tag_handler); } case 0x99: // array (two-byte uint16_t for n follow) { std::uint16_t len{}; return get_number(input_format_t::cbor, len) && get_cbor_array(static_cast(len), tag_handler); } case 0x9A: // array (four-byte uint32_t for n follow) { std::uint32_t len{}; return get_number(input_format_t::cbor, len) && get_cbor_array(static_cast(len), tag_handler); } case 0x9B: // array (eight-byte uint64_t for n follow) { std::uint64_t len{}; return get_number(input_format_t::cbor, len) && get_cbor_array(static_cast(len), tag_handler); } case 0x9F: // array (indefinite length) return get_cbor_array(std::size_t(-1), tag_handler); // map (0x00..0x17 pairs of data items follow) case 0xA0: case 0xA1: case 0xA2: case 0xA3: case 0xA4: case 0xA5: case 0xA6: case 0xA7: case 0xA8: case 0xA9: case 0xAA: case 0xAB: case 0xAC: case 0xAD: case 0xAE: case 0xAF: case 0xB0: case 0xB1: case 0xB2: case 0xB3: case 0xB4: case 0xB5: case 0xB6: case 0xB7: return get_cbor_object(static_cast(static_cast(current) & 0x1Fu), tag_handler); case 0xB8: // map (one-byte uint8_t for n follows) { std::uint8_t len{}; return get_number(input_format_t::cbor, len) && get_cbor_object(static_cast(len), tag_handler); } case 0xB9: // map (two-byte uint16_t for n follow) { std::uint16_t len{}; return get_number(input_format_t::cbor, len) && get_cbor_object(static_cast(len), tag_handler); } case 0xBA: // map (four-byte uint32_t for n follow) { std::uint32_t len{}; return get_number(input_format_t::cbor, len) && get_cbor_object(static_cast(len), tag_handler); } case 0xBB: // map (eight-byte uint64_t for n follow) { std::uint64_t len{}; return get_number(input_format_t::cbor, len) && get_cbor_object(static_cast(len), tag_handler); } case 0xBF: // map (indefinite length) return get_cbor_object(std::size_t(-1), tag_handler); case 0xC6: // tagged item case 0xC7: case 0xC8: case 0xC9: case 0xCA: case 0xCB: case 0xCC: case 0xCD: case 0xCE: case 0xCF: case 0xD0: case 0xD1: case 0xD2: case 0xD3: case 0xD4: case 0xD8: // tagged item (1 bytes follow) case 0xD9: // tagged item (2 bytes follow) case 0xDA: // tagged item (4 bytes follow) case 0xDB: // tagged item (8 bytes follow) { switch (tag_handler) { case cbor_tag_handler_t::error: { auto last_token = get_token_string(); return sax->parse_error(chars_read, last_token, parse_error::create(112, chars_read, exception_message(input_format_t::cbor, "invalid byte: 0x" + last_token, "value"))); } case cbor_tag_handler_t::ignore: { switch (current) { case 0xD8: { std::uint8_t len{}; get_number(input_format_t::cbor, len); break; } case 0xD9: { std::uint16_t len{}; get_number(input_format_t::cbor, len); break; } case 0xDA: { std::uint32_t len{}; get_number(input_format_t::cbor, len); break; } case 0xDB: { std::uint64_t len{}; get_number(input_format_t::cbor, len); break; } default: break; } return parse_cbor_internal(true, tag_handler); } default: // LCOV_EXCL_LINE JSON_ASSERT(false); // LCOV_EXCL_LINE } } case 0xF4: // false return sax->boolean(false); case 0xF5: // true return sax->boolean(true); case 0xF6: // null return sax->null(); case 0xF9: // Half-Precision Float (two-byte IEEE 754) { const auto byte1_raw = get(); if (JSON_HEDLEY_UNLIKELY(!unexpect_eof(input_format_t::cbor, "number"))) { return false; } const auto byte2_raw = get(); if (JSON_HEDLEY_UNLIKELY(!unexpect_eof(input_format_t::cbor, "number"))) { return false; } const auto byte1 = static_cast(byte1_raw); const auto byte2 = static_cast(byte2_raw); // code from RFC 7049, Appendix D, Figure 3: // As half-precision floating-point numbers were only added // to IEEE 754 in 2008, today's programming platforms often // still only have limited support for them. It is very // easy to include at least decoding support for them even // without such support. An example of a small decoder for // half-precision floating-point numbers in the C language // is shown in Fig. 3. const auto half = static_cast((byte1 << 8u) + byte2); const double val = [&half] { const int exp = (half >> 10u) & 0x1Fu; const unsigned int mant = half & 0x3FFu; JSON_ASSERT(0 <= exp&& exp <= 32); JSON_ASSERT(mant <= 1024); switch (exp) { case 0: return std::ldexp(mant, -24); case 31: return (mant == 0) ? std::numeric_limits::infinity() : std::numeric_limits::quiet_NaN(); default: return std::ldexp(mant + 1024, exp - 25); } }(); return sax->number_float((half & 0x8000u) != 0 ? static_cast(-val) : static_cast(val), ""); } case 0xFA: // Single-Precision Float (four-byte IEEE 754) { float number{}; return get_number(input_format_t::cbor, number) && sax->number_float(static_cast(number), ""); } case 0xFB: // Double-Precision Float (eight-byte IEEE 754) { double number{}; return get_number(input_format_t::cbor, number) && sax->number_float(static_cast(number), ""); } default: // anything else (0xFF is handled inside the other types) { auto last_token = get_token_string(); return sax->parse_error(chars_read, last_token, parse_error::create(112, chars_read, exception_message(input_format_t::cbor, "invalid byte: 0x" + last_token, "value"))); } } } /*! @brief reads a CBOR string This function first reads starting bytes to determine the expected string length and then copies this number of bytes into a string. Additionally, CBOR's strings with indefinite lengths are supported. @param[out] result created string @return whether string creation completed */ bool get_cbor_string(string_t& result) { if (JSON_HEDLEY_UNLIKELY(!unexpect_eof(input_format_t::cbor, "string"))) { return false; } switch (current) { // UTF-8 string (0x00..0x17 bytes follow) case 0x60: case 0x61: case 0x62: case 0x63: case 0x64: case 0x65: case 0x66: case 0x67: case 0x68: case 0x69: case 0x6A: case 0x6B: case 0x6C: case 0x6D: case 0x6E: case 0x6F: case 0x70: case 0x71: case 0x72: case 0x73: case 0x74: case 0x75: case 0x76: case 0x77: { return get_string(input_format_t::cbor, static_cast(current) & 0x1Fu, result); } case 0x78: // UTF-8 string (one-byte uint8_t for n follows) { std::uint8_t len{}; return get_number(input_format_t::cbor, len) && get_string(input_format_t::cbor, len, result); } case 0x79: // UTF-8 string (two-byte uint16_t for n follow) { std::uint16_t len{}; return get_number(input_format_t::cbor, len) && get_string(input_format_t::cbor, len, result); } case 0x7A: // UTF-8 string (four-byte uint32_t for n follow) { std::uint32_t len{}; return get_number(input_format_t::cbor, len) && get_string(input_format_t::cbor, len, result); } case 0x7B: // UTF-8 string (eight-byte uint64_t for n follow) { std::uint64_t len{}; return get_number(input_format_t::cbor, len) && get_string(input_format_t::cbor, len, result); } case 0x7F: // UTF-8 string (indefinite length) { while (get() != 0xFF) { string_t chunk; if (!get_cbor_string(chunk)) { return false; } result.append(chunk); } return true; } default: { auto last_token = get_token_string(); return sax->parse_error(chars_read, last_token, parse_error::create(113, chars_read, exception_message(input_format_t::cbor, "expected length specification (0x60-0x7B) or indefinite string type (0x7F); last byte: 0x" + last_token, "string"))); } } } /*! @brief reads a CBOR byte array This function first reads starting bytes to determine the expected byte array length and then copies this number of bytes into the byte array. Additionally, CBOR's byte arrays with indefinite lengths are supported. @param[out] result created byte array @return whether byte array creation completed */ bool get_cbor_binary(binary_t& result) { if (JSON_HEDLEY_UNLIKELY(!unexpect_eof(input_format_t::cbor, "binary"))) { return false; } switch (current) { // Binary data (0x00..0x17 bytes follow) case 0x40: case 0x41: case 0x42: case 0x43: case 0x44: case 0x45: case 0x46: case 0x47: case 0x48: case 0x49: case 0x4A: case 0x4B: case 0x4C: case 0x4D: case 0x4E: case 0x4F: case 0x50: case 0x51: case 0x52: case 0x53: case 0x54: case 0x55: case 0x56: case 0x57: { return get_binary(input_format_t::cbor, static_cast(current) & 0x1Fu, result); } case 0x58: // Binary data (one-byte uint8_t for n follows) { std::uint8_t len{}; return get_number(input_format_t::cbor, len) && get_binary(input_format_t::cbor, len, result); } case 0x59: // Binary data (two-byte uint16_t for n follow) { std::uint16_t len{}; return get_number(input_format_t::cbor, len) && get_binary(input_format_t::cbor, len, result); } case 0x5A: // Binary data (four-byte uint32_t for n follow) { std::uint32_t len{}; return get_number(input_format_t::cbor, len) && get_binary(input_format_t::cbor, len, result); } case 0x5B: // Binary data (eight-byte uint64_t for n follow) { std::uint64_t len{}; return get_number(input_format_t::cbor, len) && get_binary(input_format_t::cbor, len, result); } case 0x5F: // Binary data (indefinite length) { while (get() != 0xFF) { binary_t chunk; if (!get_cbor_binary(chunk)) { return false; } result.insert(result.end(), chunk.begin(), chunk.end()); } return true; } default: { auto last_token = get_token_string(); return sax->parse_error(chars_read, last_token, parse_error::create(113, chars_read, exception_message(input_format_t::cbor, "expected length specification (0x40-0x5B) or indefinite binary array type (0x5F); last byte: 0x" + last_token, "binary"))); } } } /*! @param[in] len the length of the array or std::size_t(-1) for an array of indefinite size @param[in] tag_handler how CBOR tags should be treated @return whether array creation completed */ bool get_cbor_array(const std::size_t len, const cbor_tag_handler_t tag_handler) { if (JSON_HEDLEY_UNLIKELY(!sax->start_array(len))) { return false; } if (len != std::size_t(-1)) { for (std::size_t i = 0; i < len; ++i) { if (JSON_HEDLEY_UNLIKELY(!parse_cbor_internal(true, tag_handler))) { return false; } } } else { while (get() != 0xFF) { if (JSON_HEDLEY_UNLIKELY(!parse_cbor_internal(false, tag_handler))) { return false; } } } return sax->end_array(); } /*! @param[in] len the length of the object or std::size_t(-1) for an object of indefinite size @param[in] tag_handler how CBOR tags should be treated @return whether object creation completed */ bool get_cbor_object(const std::size_t len, const cbor_tag_handler_t tag_handler) { if (JSON_HEDLEY_UNLIKELY(!sax->start_object(len))) { return false; } string_t key; if (len != std::size_t(-1)) { for (std::size_t i = 0; i < len; ++i) { get(); if (JSON_HEDLEY_UNLIKELY(!get_cbor_string(key) || !sax->key(key))) { return false; } if (JSON_HEDLEY_UNLIKELY(!parse_cbor_internal(true, tag_handler))) { return false; } key.clear(); } } else { while (get() != 0xFF) { if (JSON_HEDLEY_UNLIKELY(!get_cbor_string(key) || !sax->key(key))) { return false; } if (JSON_HEDLEY_UNLIKELY(!parse_cbor_internal(true, tag_handler))) { return false; } key.clear(); } } return sax->end_object(); } ///////////// // MsgPack // ///////////// /*! @return whether a valid MessagePack value was passed to the SAX parser */ bool parse_msgpack_internal() { switch (get()) { // EOF case std::char_traits::eof(): return unexpect_eof(input_format_t::msgpack, "value"); // positive fixint case 0x00: case 0x01: case 0x02: case 0x03: case 0x04: case 0x05: case 0x06: case 0x07: case 0x08: case 0x09: case 0x0A: case 0x0B: case 0x0C: case 0x0D: case 0x0E: case 0x0F: case 0x10: case 0x11: case 0x12: case 0x13: case 0x14: case 0x15: case 0x16: case 0x17: case 0x18: case 0x19: case 0x1A: case 0x1B: case 0x1C: case 0x1D: case 0x1E: case 0x1F: case 0x20: case 0x21: case 0x22: case 0x23: case 0x24: case 0x25: case 0x26: case 0x27: case 0x28: case 0x29: case 0x2A: case 0x2B: case 0x2C: case 0x2D: case 0x2E: case 0x2F: case 0x30: case 0x31: case 0x32: case 0x33: case 0x34: case 0x35: case 0x36: case 0x37: case 0x38: case 0x39: case 0x3A: case 0x3B: case 0x3C: case 0x3D: case 0x3E: case 0x3F: case 0x40: case 0x41: case 0x42: case 0x43: case 0x44: case 0x45: case 0x46: case 0x47: case 0x48: case 0x49: case 0x4A: case 0x4B: case 0x4C: case 0x4D: case 0x4E: case 0x4F: case 0x50: case 0x51: case 0x52: case 0x53: case 0x54: case 0x55: case 0x56: case 0x57: case 0x58: case 0x59: case 0x5A: case 0x5B: case 0x5C: case 0x5D: case 0x5E: case 0x5F: case 0x60: case 0x61: case 0x62: case 0x63: case 0x64: case 0x65: case 0x66: case 0x67: case 0x68: case 0x69: case 0x6A: case 0x6B: case 0x6C: case 0x6D: case 0x6E: case 0x6F: case 0x70: case 0x71: case 0x72: case 0x73: case 0x74: case 0x75: case 0x76: case 0x77: case 0x78: case 0x79: case 0x7A: case 0x7B: case 0x7C: case 0x7D: case 0x7E: case 0x7F: return sax->number_unsigned(static_cast(current)); // fixmap case 0x80: case 0x81: case 0x82: case 0x83: case 0x84: case 0x85: case 0x86: case 0x87: case 0x88: case 0x89: case 0x8A: case 0x8B: case 0x8C: case 0x8D: case 0x8E: case 0x8F: return get_msgpack_object(static_cast(static_cast(current) & 0x0Fu)); // fixarray case 0x90: case 0x91: case 0x92: case 0x93: case 0x94: case 0x95: case 0x96: case 0x97: case 0x98: case 0x99: case 0x9A: case 0x9B: case 0x9C: case 0x9D: case 0x9E: case 0x9F: return get_msgpack_array(static_cast(static_cast(current) & 0x0Fu)); // fixstr case 0xA0: case 0xA1: case 0xA2: case 0xA3: case 0xA4: case 0xA5: case 0xA6: case 0xA7: case 0xA8: case 0xA9: case 0xAA: case 0xAB: case 0xAC: case 0xAD: case 0xAE: case 0xAF: case 0xB0: case 0xB1: case 0xB2: case 0xB3: case 0xB4: case 0xB5: case 0xB6: case 0xB7: case 0xB8: case 0xB9: case 0xBA: case 0xBB: case 0xBC: case 0xBD: case 0xBE: case 0xBF: case 0xD9: // str 8 case 0xDA: // str 16 case 0xDB: // str 32 { string_t s; return get_msgpack_string(s) && sax->string(s); } case 0xC0: // nil return sax->null(); case 0xC2: // false return sax->boolean(false); case 0xC3: // true return sax->boolean(true); case 0xC4: // bin 8 case 0xC5: // bin 16 case 0xC6: // bin 32 case 0xC7: // ext 8 case 0xC8: // ext 16 case 0xC9: // ext 32 case 0xD4: // fixext 1 case 0xD5: // fixext 2 case 0xD6: // fixext 4 case 0xD7: // fixext 8 case 0xD8: // fixext 16 { binary_t b; return get_msgpack_binary(b) && sax->binary(b); } case 0xCA: // float 32 { float number{}; return get_number(input_format_t::msgpack, number) && sax->number_float(static_cast(number), ""); } case 0xCB: // float 64 { double number{}; return get_number(input_format_t::msgpack, number) && sax->number_float(static_cast(number), ""); } case 0xCC: // uint 8 { std::uint8_t number{}; return get_number(input_format_t::msgpack, number) && sax->number_unsigned(number); } case 0xCD: // uint 16 { std::uint16_t number{}; return get_number(input_format_t::msgpack, number) && sax->number_unsigned(number); } case 0xCE: // uint 32 { std::uint32_t number{}; return get_number(input_format_t::msgpack, number) && sax->number_unsigned(number); } case 0xCF: // uint 64 { std::uint64_t number{}; return get_number(input_format_t::msgpack, number) && sax->number_unsigned(number); } case 0xD0: // int 8 { std::int8_t number{}; return get_number(input_format_t::msgpack, number) && sax->number_integer(number); } case 0xD1: // int 16 { std::int16_t number{}; return get_number(input_format_t::msgpack, number) && sax->number_integer(number); } case 0xD2: // int 32 { std::int32_t number{}; return get_number(input_format_t::msgpack, number) && sax->number_integer(number); } case 0xD3: // int 64 { std::int64_t number{}; return get_number(input_format_t::msgpack, number) && sax->number_integer(number); } case 0xDC: // array 16 { std::uint16_t len{}; return get_number(input_format_t::msgpack, len) && get_msgpack_array(static_cast(len)); } case 0xDD: // array 32 { std::uint32_t len{}; return get_number(input_format_t::msgpack, len) && get_msgpack_array(static_cast(len)); } case 0xDE: // map 16 { std::uint16_t len{}; return get_number(input_format_t::msgpack, len) && get_msgpack_object(static_cast(len)); } case 0xDF: // map 32 { std::uint32_t len{}; return get_number(input_format_t::msgpack, len) && get_msgpack_object(static_cast(len)); } // negative fixint case 0xE0: case 0xE1: case 0xE2: case 0xE3: case 0xE4: case 0xE5: case 0xE6: case 0xE7: case 0xE8: case 0xE9: case 0xEA: case 0xEB: case 0xEC: case 0xED: case 0xEE: case 0xEF: case 0xF0: case 0xF1: case 0xF2: case 0xF3: case 0xF4: case 0xF5: case 0xF6: case 0xF7: case 0xF8: case 0xF9: case 0xFA: case 0xFB: case 0xFC: case 0xFD: case 0xFE: case 0xFF: return sax->number_integer(static_cast(current)); default: // anything else { auto last_token = get_token_string(); return sax->parse_error(chars_read, last_token, parse_error::create(112, chars_read, exception_message(input_format_t::msgpack, "invalid byte: 0x" + last_token, "value"))); } } } /*! @brief reads a MessagePack string This function first reads starting bytes to determine the expected string length and then copies this number of bytes into a string. @param[out] result created string @return whether string creation completed */ bool get_msgpack_string(string_t& result) { if (JSON_HEDLEY_UNLIKELY(!unexpect_eof(input_format_t::msgpack, "string"))) { return false; } switch (current) { // fixstr case 0xA0: case 0xA1: case 0xA2: case 0xA3: case 0xA4: case 0xA5: case 0xA6: case 0xA7: case 0xA8: case 0xA9: case 0xAA: case 0xAB: case 0xAC: case 0xAD: case 0xAE: case 0xAF: case 0xB0: case 0xB1: case 0xB2: case 0xB3: case 0xB4: case 0xB5: case 0xB6: case 0xB7: case 0xB8: case 0xB9: case 0xBA: case 0xBB: case 0xBC: case 0xBD: case 0xBE: case 0xBF: { return get_string(input_format_t::msgpack, static_cast(current) & 0x1Fu, result); } case 0xD9: // str 8 { std::uint8_t len{}; return get_number(input_format_t::msgpack, len) && get_string(input_format_t::msgpack, len, result); } case 0xDA: // str 16 { std::uint16_t len{}; return get_number(input_format_t::msgpack, len) && get_string(input_format_t::msgpack, len, result); } case 0xDB: // str 32 { std::uint32_t len{}; return get_number(input_format_t::msgpack, len) && get_string(input_format_t::msgpack, len, result); } default: { auto last_token = get_token_string(); return sax->parse_error(chars_read, last_token, parse_error::create(113, chars_read, exception_message(input_format_t::msgpack, "expected length specification (0xA0-0xBF, 0xD9-0xDB); last byte: 0x" + last_token, "string"))); } } } /*! @brief reads a MessagePack byte array This function first reads starting bytes to determine the expected byte array length and then copies this number of bytes into a byte array. @param[out] result created byte array @return whether byte array creation completed */ bool get_msgpack_binary(binary_t& result) { // helper function to set the subtype auto assign_and_return_true = [&result](std::int8_t subtype) { result.set_subtype(static_cast(subtype)); return true; }; switch (current) { case 0xC4: // bin 8 { std::uint8_t len{}; return get_number(input_format_t::msgpack, len) && get_binary(input_format_t::msgpack, len, result); } case 0xC5: // bin 16 { std::uint16_t len{}; return get_number(input_format_t::msgpack, len) && get_binary(input_format_t::msgpack, len, result); } case 0xC6: // bin 32 { std::uint32_t len{}; return get_number(input_format_t::msgpack, len) && get_binary(input_format_t::msgpack, len, result); } case 0xC7: // ext 8 { std::uint8_t len{}; std::int8_t subtype{}; return get_number(input_format_t::msgpack, len) && get_number(input_format_t::msgpack, subtype) && get_binary(input_format_t::msgpack, len, result) && assign_and_return_true(subtype); } case 0xC8: // ext 16 { std::uint16_t len{}; std::int8_t subtype{}; return get_number(input_format_t::msgpack, len) && get_number(input_format_t::msgpack, subtype) && get_binary(input_format_t::msgpack, len, result) && assign_and_return_true(subtype); } case 0xC9: // ext 32 { std::uint32_t len{}; std::int8_t subtype{}; return get_number(input_format_t::msgpack, len) && get_number(input_format_t::msgpack, subtype) && get_binary(input_format_t::msgpack, len, result) && assign_and_return_true(subtype); } case 0xD4: // fixext 1 { std::int8_t subtype{}; return get_number(input_format_t::msgpack, subtype) && get_binary(input_format_t::msgpack, 1, result) && assign_and_return_true(subtype); } case 0xD5: // fixext 2 { std::int8_t subtype{}; return get_number(input_format_t::msgpack, subtype) && get_binary(input_format_t::msgpack, 2, result) && assign_and_return_true(subtype); } case 0xD6: // fixext 4 { std::int8_t subtype{}; return get_number(input_format_t::msgpack, subtype) && get_binary(input_format_t::msgpack, 4, result) && assign_and_return_true(subtype); } case 0xD7: // fixext 8 { std::int8_t subtype{}; return get_number(input_format_t::msgpack, subtype) && get_binary(input_format_t::msgpack, 8, result) && assign_and_return_true(subtype); } case 0xD8: // fixext 16 { std::int8_t subtype{}; return get_number(input_format_t::msgpack, subtype) && get_binary(input_format_t::msgpack, 16, result) && assign_and_return_true(subtype); } default: // LCOV_EXCL_LINE return false; // LCOV_EXCL_LINE } } /*! @param[in] len the length of the array @return whether array creation completed */ bool get_msgpack_array(const std::size_t len) { if (JSON_HEDLEY_UNLIKELY(!sax->start_array(len))) { return false; } for (std::size_t i = 0; i < len; ++i) { if (JSON_HEDLEY_UNLIKELY(!parse_msgpack_internal())) { return false; } } return sax->end_array(); } /*! @param[in] len the length of the object @return whether object creation completed */ bool get_msgpack_object(const std::size_t len) { if (JSON_HEDLEY_UNLIKELY(!sax->start_object(len))) { return false; } string_t key; for (std::size_t i = 0; i < len; ++i) { get(); if (JSON_HEDLEY_UNLIKELY(!get_msgpack_string(key) || !sax->key(key))) { return false; } if (JSON_HEDLEY_UNLIKELY(!parse_msgpack_internal())) { return false; } key.clear(); } return sax->end_object(); } //////////// // UBJSON // //////////// /*! @param[in] get_char whether a new character should be retrieved from the input (true, default) or whether the last read character should be considered instead @return whether a valid UBJSON value was passed to the SAX parser */ bool parse_ubjson_internal(const bool get_char = true) { return get_ubjson_value(get_char ? get_ignore_noop() : current); } /*! @brief reads a UBJSON string This function is either called after reading the 'S' byte explicitly indicating a string, or in case of an object key where the 'S' byte can be left out. @param[out] result created string @param[in] get_char whether a new character should be retrieved from the input (true, default) or whether the last read character should be considered instead @return whether string creation completed */ bool get_ubjson_string(string_t& result, const bool get_char = true) { if (get_char) { get(); // TODO(niels): may we ignore N here? } if (JSON_HEDLEY_UNLIKELY(!unexpect_eof(input_format_t::ubjson, "value"))) { return false; } switch (current) { case 'U': { std::uint8_t len{}; return get_number(input_format_t::ubjson, len) && get_string(input_format_t::ubjson, len, result); } case 'i': { std::int8_t len{}; return get_number(input_format_t::ubjson, len) && get_string(input_format_t::ubjson, len, result); } case 'I': { std::int16_t len{}; return get_number(input_format_t::ubjson, len) && get_string(input_format_t::ubjson, len, result); } case 'l': { std::int32_t len{}; return get_number(input_format_t::ubjson, len) && get_string(input_format_t::ubjson, len, result); } case 'L': { std::int64_t len{}; return get_number(input_format_t::ubjson, len) && get_string(input_format_t::ubjson, len, result); } default: auto last_token = get_token_string(); return sax->parse_error(chars_read, last_token, parse_error::create(113, chars_read, exception_message(input_format_t::ubjson, "expected length type specification (U, i, I, l, L); last byte: 0x" + last_token, "string"))); } } /*! @param[out] result determined size @return whether size determination completed */ bool get_ubjson_size_value(std::size_t& result) { switch (get_ignore_noop()) { case 'U': { std::uint8_t number{}; if (JSON_HEDLEY_UNLIKELY(!get_number(input_format_t::ubjson, number))) { return false; } result = static_cast(number); return true; } case 'i': { std::int8_t number{}; if (JSON_HEDLEY_UNLIKELY(!get_number(input_format_t::ubjson, number))) { return false; } result = static_cast(number); return true; } case 'I': { std::int16_t number{}; if (JSON_HEDLEY_UNLIKELY(!get_number(input_format_t::ubjson, number))) { return false; } result = static_cast(number); return true; } case 'l': { std::int32_t number{}; if (JSON_HEDLEY_UNLIKELY(!get_number(input_format_t::ubjson, number))) { return false; } result = static_cast(number); return true; } case 'L': { std::int64_t number{}; if (JSON_HEDLEY_UNLIKELY(!get_number(input_format_t::ubjson, number))) { return false; } result = static_cast(number); return true; } default: { auto last_token = get_token_string(); return sax->parse_error(chars_read, last_token, parse_error::create(113, chars_read, exception_message(input_format_t::ubjson, "expected length type specification (U, i, I, l, L) after '#'; last byte: 0x" + last_token, "size"))); } } } /*! @brief determine the type and size for a container In the optimized UBJSON format, a type and a size can be provided to allow for a more compact representation. @param[out] result pair of the size and the type @return whether pair creation completed */ bool get_ubjson_size_type(std::pair& result) { result.first = string_t::npos; // size result.second = 0; // type get_ignore_noop(); if (current == '$') { result.second = get(); // must not ignore 'N', because 'N' maybe the type if (JSON_HEDLEY_UNLIKELY(!unexpect_eof(input_format_t::ubjson, "type"))) { return false; } get_ignore_noop(); if (JSON_HEDLEY_UNLIKELY(current != '#')) { if (JSON_HEDLEY_UNLIKELY(!unexpect_eof(input_format_t::ubjson, "value"))) { return false; } auto last_token = get_token_string(); return sax->parse_error(chars_read, last_token, parse_error::create(112, chars_read, exception_message(input_format_t::ubjson, "expected '#' after type information; last byte: 0x" + last_token, "size"))); } return get_ubjson_size_value(result.first); } if (current == '#') { return get_ubjson_size_value(result.first); } return true; } /*! @param prefix the previously read or set type prefix @return whether value creation completed */ bool get_ubjson_value(const char_int_type prefix) { switch (prefix) { case std::char_traits::eof(): // EOF return unexpect_eof(input_format_t::ubjson, "value"); case 'T': // true return sax->boolean(true); case 'F': // false return sax->boolean(false); case 'Z': // null return sax->null(); case 'U': { std::uint8_t number{}; return get_number(input_format_t::ubjson, number) && sax->number_unsigned(number); } case 'i': { std::int8_t number{}; return get_number(input_format_t::ubjson, number) && sax->number_integer(number); } case 'I': { std::int16_t number{}; return get_number(input_format_t::ubjson, number) && sax->number_integer(number); } case 'l': { std::int32_t number{}; return get_number(input_format_t::ubjson, number) && sax->number_integer(number); } case 'L': { std::int64_t number{}; return get_number(input_format_t::ubjson, number) && sax->number_integer(number); } case 'd': { float number{}; return get_number(input_format_t::ubjson, number) && sax->number_float(static_cast(number), ""); } case 'D': { double number{}; return get_number(input_format_t::ubjson, number) && sax->number_float(static_cast(number), ""); } case 'H': { return get_ubjson_high_precision_number(); } case 'C': // char { get(); if (JSON_HEDLEY_UNLIKELY(!unexpect_eof(input_format_t::ubjson, "char"))) { return false; } if (JSON_HEDLEY_UNLIKELY(current > 127)) { auto last_token = get_token_string(); return sax->parse_error(chars_read, last_token, parse_error::create(113, chars_read, exception_message(input_format_t::ubjson, "byte after 'C' must be in range 0x00..0x7F; last byte: 0x" + last_token, "char"))); } string_t s(1, static_cast(current)); return sax->string(s); } case 'S': // string { string_t s; return get_ubjson_string(s) && sax->string(s); } case '[': // array return get_ubjson_array(); case '{': // object return get_ubjson_object(); default: // anything else { auto last_token = get_token_string(); return sax->parse_error(chars_read, last_token, parse_error::create(112, chars_read, exception_message(input_format_t::ubjson, "invalid byte: 0x" + last_token, "value"))); } } } /*! @return whether array creation completed */ bool get_ubjson_array() { std::pair size_and_type; if (JSON_HEDLEY_UNLIKELY(!get_ubjson_size_type(size_and_type))) { return false; } if (size_and_type.first != string_t::npos) { if (JSON_HEDLEY_UNLIKELY(!sax->start_array(size_and_type.first))) { return false; } if (size_and_type.second != 0) { if (size_and_type.second != 'N') { for (std::size_t i = 0; i < size_and_type.first; ++i) { if (JSON_HEDLEY_UNLIKELY(!get_ubjson_value(size_and_type.second))) { return false; } } } } else { for (std::size_t i = 0; i < size_and_type.first; ++i) { if (JSON_HEDLEY_UNLIKELY(!parse_ubjson_internal())) { return false; } } } } else { if (JSON_HEDLEY_UNLIKELY(!sax->start_array(std::size_t(-1)))) { return false; } while (current != ']') { if (JSON_HEDLEY_UNLIKELY(!parse_ubjson_internal(false))) { return false; } get_ignore_noop(); } } return sax->end_array(); } /*! @return whether object creation completed */ bool get_ubjson_object() { std::pair size_and_type; if (JSON_HEDLEY_UNLIKELY(!get_ubjson_size_type(size_and_type))) { return false; } string_t key; if (size_and_type.first != string_t::npos) { if (JSON_HEDLEY_UNLIKELY(!sax->start_object(size_and_type.first))) { return false; } if (size_and_type.second != 0) { for (std::size_t i = 0; i < size_and_type.first; ++i) { if (JSON_HEDLEY_UNLIKELY(!get_ubjson_string(key) || !sax->key(key))) { return false; } if (JSON_HEDLEY_UNLIKELY(!get_ubjson_value(size_and_type.second))) { return false; } key.clear(); } } else { for (std::size_t i = 0; i < size_and_type.first; ++i) { if (JSON_HEDLEY_UNLIKELY(!get_ubjson_string(key) || !sax->key(key))) { return false; } if (JSON_HEDLEY_UNLIKELY(!parse_ubjson_internal())) { return false; } key.clear(); } } } else { if (JSON_HEDLEY_UNLIKELY(!sax->start_object(std::size_t(-1)))) { return false; } while (current != '}') { if (JSON_HEDLEY_UNLIKELY(!get_ubjson_string(key, false) || !sax->key(key))) { return false; } if (JSON_HEDLEY_UNLIKELY(!parse_ubjson_internal())) { return false; } get_ignore_noop(); key.clear(); } } return sax->end_object(); } // Note, no reader for UBJSON binary types is implemented because they do // not exist bool get_ubjson_high_precision_number() { // get size of following number string std::size_t size{}; auto res = get_ubjson_size_value(size); if (JSON_HEDLEY_UNLIKELY(!res)) { return res; } // get number string std::vector number_vector; for (std::size_t i = 0; i < size; ++i) { get(); if (JSON_HEDLEY_UNLIKELY(!unexpect_eof(input_format_t::ubjson, "number"))) { return false; } number_vector.push_back(static_cast(current)); } // parse number string auto number_ia = detail::input_adapter(std::forward(number_vector)); auto number_lexer = detail::lexer(std::move(number_ia), false); const auto result_number = number_lexer.scan(); const auto number_string = number_lexer.get_token_string(); const auto result_remainder = number_lexer.scan(); using token_type = typename detail::lexer_base::token_type; if (JSON_HEDLEY_UNLIKELY(result_remainder != token_type::end_of_input)) { return sax->parse_error(chars_read, number_string, parse_error::create(115, chars_read, exception_message(input_format_t::ubjson, "invalid number text: " + number_lexer.get_token_string(), "high-precision number"))); } switch (result_number) { case token_type::value_integer: return sax->number_integer(number_lexer.get_number_integer()); case token_type::value_unsigned: return sax->number_unsigned(number_lexer.get_number_unsigned()); case token_type::value_float: return sax->number_float(number_lexer.get_number_float(), std::move(number_string)); default: return sax->parse_error(chars_read, number_string, parse_error::create(115, chars_read, exception_message(input_format_t::ubjson, "invalid number text: " + number_lexer.get_token_string(), "high-precision number"))); } } /////////////////////// // Utility functions // /////////////////////// /*! @brief get next character from the input This function provides the interface to the used input adapter. It does not throw in case the input reached EOF, but returns a -'ve valued `std::char_traits::eof()` in that case. @return character read from the input */ char_int_type get() { ++chars_read; return current = ia.get_character(); } /*! @return character read from the input after ignoring all 'N' entries */ char_int_type get_ignore_noop() { do { get(); } while (current == 'N'); return current; } /* @brief read a number from the input @tparam NumberType the type of the number @param[in] format the current format (for diagnostics) @param[out] result number of type @a NumberType @return whether conversion completed @note This function needs to respect the system's endianess, because bytes in CBOR, MessagePack, and UBJSON are stored in network order (big endian) and therefore need reordering on little endian systems. */ template bool get_number(const input_format_t format, NumberType& result) { // step 1: read input into array with system's byte order std::array vec; for (std::size_t i = 0; i < sizeof(NumberType); ++i) { get(); if (JSON_HEDLEY_UNLIKELY(!unexpect_eof(format, "number"))) { return false; } // reverse byte order prior to conversion if necessary if (is_little_endian != InputIsLittleEndian) { vec[sizeof(NumberType) - i - 1] = static_cast(current); } else { vec[i] = static_cast(current); // LCOV_EXCL_LINE } } // step 2: convert array into number of type T and return std::memcpy(&result, vec.data(), sizeof(NumberType)); return true; } /*! @brief create a string by reading characters from the input @tparam NumberType the type of the number @param[in] format the current format (for diagnostics) @param[in] len number of characters to read @param[out] result string created by reading @a len bytes @return whether string creation completed @note We can not reserve @a len bytes for the result, because @a len may be too large. Usually, @ref unexpect_eof() detects the end of the input before we run out of string memory. */ template bool get_string(const input_format_t format, const NumberType len, string_t& result) { bool success = true; for (NumberType i = 0; i < len; i++) { get(); if (JSON_HEDLEY_UNLIKELY(!unexpect_eof(format, "string"))) { success = false; break; } result.push_back(static_cast(current)); }; return success; } /*! @brief create a byte array by reading bytes from the input @tparam NumberType the type of the number @param[in] format the current format (for diagnostics) @param[in] len number of bytes to read @param[out] result byte array created by reading @a len bytes @return whether byte array creation completed @note We can not reserve @a len bytes for the result, because @a len may be too large. Usually, @ref unexpect_eof() detects the end of the input before we run out of memory. */ template bool get_binary(const input_format_t format, const NumberType len, binary_t& result) { bool success = true; for (NumberType i = 0; i < len; i++) { get(); if (JSON_HEDLEY_UNLIKELY(!unexpect_eof(format, "binary"))) { success = false; break; } result.push_back(static_cast(current)); } return success; } /*! @param[in] format the current format (for diagnostics) @param[in] context further context information (for diagnostics) @return whether the last read character is not EOF */ JSON_HEDLEY_NON_NULL(3) bool unexpect_eof(const input_format_t format, const char* context) const { if (JSON_HEDLEY_UNLIKELY(current == std::char_traits::eof())) { return sax->parse_error(chars_read, "", parse_error::create(110, chars_read, exception_message(format, "unexpected end of input", context))); } return true; } /*! @return a string representation of the last read byte */ std::string get_token_string() const { std::array cr{{}}; (std::snprintf)(cr.data(), cr.size(), "%.2hhX", static_cast(current)); return std::string{cr.data()}; } /*! @param[in] format the current format @param[in] detail a detailed error message @param[in] context further context information @return a message string to use in the parse_error exceptions */ std::string exception_message(const input_format_t format, const std::string& detail, const std::string& context) const { std::string error_msg = "syntax error while parsing "; switch (format) { case input_format_t::cbor: error_msg += "CBOR"; break; case input_format_t::msgpack: error_msg += "MessagePack"; break; case input_format_t::ubjson: error_msg += "UBJSON"; break; case input_format_t::bson: error_msg += "BSON"; break; default: // LCOV_EXCL_LINE JSON_ASSERT(false); // LCOV_EXCL_LINE } return error_msg + " " + context + ": " + detail; } private: /// input adapter InputAdapterType ia; /// the current character char_int_type current = std::char_traits::eof(); /// the number of characters read std::size_t chars_read = 0; /// whether we can assume little endianess const bool is_little_endian = little_endianess(); /// the SAX parser json_sax_t* sax = nullptr; }; } // namespace detail } // namespace nlohmann // #include // #include // #include #include // isfinite #include // uint8_t #include // function #include // string #include // move #include // vector // #include // #include // #include // #include // #include // #include // #include namespace nlohmann { namespace detail { //////////// // parser // //////////// enum class parse_event_t : uint8_t { /// the parser read `{` and started to process a JSON object object_start, /// the parser read `}` and finished processing a JSON object object_end, /// the parser read `[` and started to process a JSON array array_start, /// the parser read `]` and finished processing a JSON array array_end, /// the parser read a key of a value in an object key, /// the parser finished reading a JSON value value }; template using parser_callback_t = std::function; /*! @brief syntax analysis This class implements a recursive descent parser. */ template class parser { using number_integer_t = typename BasicJsonType::number_integer_t; using number_unsigned_t = typename BasicJsonType::number_unsigned_t; using number_float_t = typename BasicJsonType::number_float_t; using string_t = typename BasicJsonType::string_t; using lexer_t = lexer; using token_type = typename lexer_t::token_type; public: /// a parser reading from an input adapter explicit parser(InputAdapterType&& adapter, const parser_callback_t cb = nullptr, const bool allow_exceptions_ = true, const bool skip_comments = false) : callback(cb) , m_lexer(std::move(adapter), skip_comments) , allow_exceptions(allow_exceptions_) { // read first token get_token(); } /*! @brief public parser interface @param[in] strict whether to expect the last token to be EOF @param[in,out] result parsed JSON value @throw parse_error.101 in case of an unexpected token @throw parse_error.102 if to_unicode fails or surrogate error @throw parse_error.103 if to_unicode fails */ void parse(const bool strict, BasicJsonType& result) { if (callback) { json_sax_dom_callback_parser sdp(result, callback, allow_exceptions); sax_parse_internal(&sdp); result.assert_invariant(); // in strict mode, input must be completely read if (strict && (get_token() != token_type::end_of_input)) { sdp.parse_error(m_lexer.get_position(), m_lexer.get_token_string(), parse_error::create(101, m_lexer.get_position(), exception_message(token_type::end_of_input, "value"))); } // in case of an error, return discarded value if (sdp.is_errored()) { result = value_t::discarded; return; } // set top-level value to null if it was discarded by the callback // function if (result.is_discarded()) { result = nullptr; } } else { json_sax_dom_parser sdp(result, allow_exceptions); sax_parse_internal(&sdp); result.assert_invariant(); // in strict mode, input must be completely read if (strict && (get_token() != token_type::end_of_input)) { sdp.parse_error(m_lexer.get_position(), m_lexer.get_token_string(), parse_error::create(101, m_lexer.get_position(), exception_message(token_type::end_of_input, "value"))); } // in case of an error, return discarded value if (sdp.is_errored()) { result = value_t::discarded; return; } } } /*! @brief public accept interface @param[in] strict whether to expect the last token to be EOF @return whether the input is a proper JSON text */ bool accept(const bool strict = true) { json_sax_acceptor sax_acceptor; return sax_parse(&sax_acceptor, strict); } template JSON_HEDLEY_NON_NULL(2) bool sax_parse(SAX* sax, const bool strict = true) { (void)detail::is_sax_static_asserts {}; const bool result = sax_parse_internal(sax); // strict mode: next byte must be EOF if (result && strict && (get_token() != token_type::end_of_input)) { return sax->parse_error(m_lexer.get_position(), m_lexer.get_token_string(), parse_error::create(101, m_lexer.get_position(), exception_message(token_type::end_of_input, "value"))); } return result; } private: template JSON_HEDLEY_NON_NULL(2) bool sax_parse_internal(SAX* sax) { // stack to remember the hierarchy of structured values we are parsing // true = array; false = object std::vector states; // value to avoid a goto (see comment where set to true) bool skip_to_state_evaluation = false; while (true) { if (!skip_to_state_evaluation) { // invariant: get_token() was called before each iteration switch (last_token) { case token_type::begin_object: { if (JSON_HEDLEY_UNLIKELY(!sax->start_object(std::size_t(-1)))) { return false; } // closing } -> we are done if (get_token() == token_type::end_object) { if (JSON_HEDLEY_UNLIKELY(!sax->end_object())) { return false; } break; } // parse key if (JSON_HEDLEY_UNLIKELY(last_token != token_type::value_string)) { return sax->parse_error(m_lexer.get_position(), m_lexer.get_token_string(), parse_error::create(101, m_lexer.get_position(), exception_message(token_type::value_string, "object key"))); } if (JSON_HEDLEY_UNLIKELY(!sax->key(m_lexer.get_string()))) { return false; } // parse separator (:) if (JSON_HEDLEY_UNLIKELY(get_token() != token_type::name_separator)) { return sax->parse_error(m_lexer.get_position(), m_lexer.get_token_string(), parse_error::create(101, m_lexer.get_position(), exception_message(token_type::name_separator, "object separator"))); } // remember we are now inside an object states.push_back(false); // parse values get_token(); continue; } case token_type::begin_array: { if (JSON_HEDLEY_UNLIKELY(!sax->start_array(std::size_t(-1)))) { return false; } // closing ] -> we are done if (get_token() == token_type::end_array) { if (JSON_HEDLEY_UNLIKELY(!sax->end_array())) { return false; } break; } // remember we are now inside an array states.push_back(true); // parse values (no need to call get_token) continue; } case token_type::value_float: { const auto res = m_lexer.get_number_float(); if (JSON_HEDLEY_UNLIKELY(!std::isfinite(res))) { return sax->parse_error(m_lexer.get_position(), m_lexer.get_token_string(), out_of_range::create(406, "number overflow parsing '" + m_lexer.get_token_string() + "'")); } if (JSON_HEDLEY_UNLIKELY(!sax->number_float(res, m_lexer.get_string()))) { return false; } break; } case token_type::literal_false: { if (JSON_HEDLEY_UNLIKELY(!sax->boolean(false))) { return false; } break; } case token_type::literal_null: { if (JSON_HEDLEY_UNLIKELY(!sax->null())) { return false; } break; } case token_type::literal_true: { if (JSON_HEDLEY_UNLIKELY(!sax->boolean(true))) { return false; } break; } case token_type::value_integer: { if (JSON_HEDLEY_UNLIKELY(!sax->number_integer(m_lexer.get_number_integer()))) { return false; } break; } case token_type::value_string: { if (JSON_HEDLEY_UNLIKELY(!sax->string(m_lexer.get_string()))) { return false; } break; } case token_type::value_unsigned: { if (JSON_HEDLEY_UNLIKELY(!sax->number_unsigned(m_lexer.get_number_unsigned()))) { return false; } break; } case token_type::parse_error: { // using "uninitialized" to avoid "expected" message return sax->parse_error(m_lexer.get_position(), m_lexer.get_token_string(), parse_error::create(101, m_lexer.get_position(), exception_message(token_type::uninitialized, "value"))); } default: // the last token was unexpected { return sax->parse_error(m_lexer.get_position(), m_lexer.get_token_string(), parse_error::create(101, m_lexer.get_position(), exception_message(token_type::literal_or_value, "value"))); } } } else { skip_to_state_evaluation = false; } // we reached this line after we successfully parsed a value if (states.empty()) { // empty stack: we reached the end of the hierarchy: done return true; } if (states.back()) // array { // comma -> next value if (get_token() == token_type::value_separator) { // parse a new value get_token(); continue; } // closing ] if (JSON_HEDLEY_LIKELY(last_token == token_type::end_array)) { if (JSON_HEDLEY_UNLIKELY(!sax->end_array())) { return false; } // We are done with this array. Before we can parse a // new value, we need to evaluate the new state first. // By setting skip_to_state_evaluation to false, we // are effectively jumping to the beginning of this if. JSON_ASSERT(!states.empty()); states.pop_back(); skip_to_state_evaluation = true; continue; } return sax->parse_error(m_lexer.get_position(), m_lexer.get_token_string(), parse_error::create(101, m_lexer.get_position(), exception_message(token_type::end_array, "array"))); } else // object { // comma -> next value if (get_token() == token_type::value_separator) { // parse key if (JSON_HEDLEY_UNLIKELY(get_token() != token_type::value_string)) { return sax->parse_error(m_lexer.get_position(), m_lexer.get_token_string(), parse_error::create(101, m_lexer.get_position(), exception_message(token_type::value_string, "object key"))); } if (JSON_HEDLEY_UNLIKELY(!sax->key(m_lexer.get_string()))) { return false; } // parse separator (:) if (JSON_HEDLEY_UNLIKELY(get_token() != token_type::name_separator)) { return sax->parse_error(m_lexer.get_position(), m_lexer.get_token_string(), parse_error::create(101, m_lexer.get_position(), exception_message(token_type::name_separator, "object separator"))); } // parse values get_token(); continue; } // closing } if (JSON_HEDLEY_LIKELY(last_token == token_type::end_object)) { if (JSON_HEDLEY_UNLIKELY(!sax->end_object())) { return false; } // We are done with this object. Before we can parse a // new value, we need to evaluate the new state first. // By setting skip_to_state_evaluation to false, we // are effectively jumping to the beginning of this if. JSON_ASSERT(!states.empty()); states.pop_back(); skip_to_state_evaluation = true; continue; } return sax->parse_error(m_lexer.get_position(), m_lexer.get_token_string(), parse_error::create(101, m_lexer.get_position(), exception_message(token_type::end_object, "object"))); } } } /// get next token from lexer token_type get_token() { return last_token = m_lexer.scan(); } std::string exception_message(const token_type expected, const std::string& context) { std::string error_msg = "syntax error "; if (!context.empty()) { error_msg += "while parsing " + context + " "; } error_msg += "- "; if (last_token == token_type::parse_error) { error_msg += std::string(m_lexer.get_error_message()) + "; last read: '" + m_lexer.get_token_string() + "'"; } else { error_msg += "unexpected " + std::string(lexer_t::token_type_name(last_token)); } if (expected != token_type::uninitialized) { error_msg += "; expected " + std::string(lexer_t::token_type_name(expected)); } return error_msg; } private: /// callback function const parser_callback_t callback = nullptr; /// the type of the last read token token_type last_token = token_type::uninitialized; /// the lexer lexer_t m_lexer; /// whether to throw exceptions in case of errors const bool allow_exceptions = true; }; } // namespace detail } // namespace nlohmann // #include // #include #include // ptrdiff_t #include // numeric_limits namespace nlohmann { namespace detail { /* @brief an iterator for primitive JSON types This class models an iterator for primitive JSON types (boolean, number, string). It's only purpose is to allow the iterator/const_iterator classes to "iterate" over primitive values. Internally, the iterator is modeled by a `difference_type` variable. Value begin_value (`0`) models the begin, end_value (`1`) models past the end. */ class primitive_iterator_t { private: using difference_type = std::ptrdiff_t; static constexpr difference_type begin_value = 0; static constexpr difference_type end_value = begin_value + 1; /// iterator as signed integer type difference_type m_it = (std::numeric_limits::min)(); public: constexpr difference_type get_value() const noexcept { return m_it; } /// set iterator to a defined beginning void set_begin() noexcept { m_it = begin_value; } /// set iterator to a defined past the end void set_end() noexcept { m_it = end_value; } /// return whether the iterator can be dereferenced constexpr bool is_begin() const noexcept { return m_it == begin_value; } /// return whether the iterator is at end constexpr bool is_end() const noexcept { return m_it == end_value; } friend constexpr bool operator==(primitive_iterator_t lhs, primitive_iterator_t rhs) noexcept { return lhs.m_it == rhs.m_it; } friend constexpr bool operator<(primitive_iterator_t lhs, primitive_iterator_t rhs) noexcept { return lhs.m_it < rhs.m_it; } primitive_iterator_t operator+(difference_type n) noexcept { auto result = *this; result += n; return result; } friend constexpr difference_type operator-(primitive_iterator_t lhs, primitive_iterator_t rhs) noexcept { return lhs.m_it - rhs.m_it; } primitive_iterator_t& operator++() noexcept { ++m_it; return *this; } primitive_iterator_t const operator++(int) noexcept { auto result = *this; ++m_it; return result; } primitive_iterator_t& operator--() noexcept { --m_it; return *this; } primitive_iterator_t const operator--(int) noexcept { auto result = *this; --m_it; return result; } primitive_iterator_t& operator+=(difference_type n) noexcept { m_it += n; return *this; } primitive_iterator_t& operator-=(difference_type n) noexcept { m_it -= n; return *this; } }; } // namespace detail } // namespace nlohmann namespace nlohmann { namespace detail { /*! @brief an iterator value @note This structure could easily be a union, but MSVC currently does not allow unions members with complex constructors, see https://github.com/nlohmann/json/pull/105. */ template struct internal_iterator { /// iterator for JSON objects typename BasicJsonType::object_t::iterator object_iterator {}; /// iterator for JSON arrays typename BasicJsonType::array_t::iterator array_iterator {}; /// generic iterator for all other types primitive_iterator_t primitive_iterator {}; }; } // namespace detail } // namespace nlohmann // #include #include // iterator, random_access_iterator_tag, bidirectional_iterator_tag, advance, next #include // conditional, is_const, remove_const // #include // #include // #include // #include // #include // #include // #include namespace nlohmann { namespace detail { // forward declare, to be able to friend it later on template class iteration_proxy; template class iteration_proxy_value; /*! @brief a template for a bidirectional iterator for the @ref basic_json class This class implements a both iterators (iterator and const_iterator) for the @ref basic_json class. @note An iterator is called *initialized* when a pointer to a JSON value has been set (e.g., by a constructor or a copy assignment). If the iterator is default-constructed, it is *uninitialized* and most methods are undefined. **The library uses assertions to detect calls on uninitialized iterators.** @requirement The class satisfies the following concept requirements: - [BidirectionalIterator](https://en.cppreference.com/w/cpp/named_req/BidirectionalIterator): The iterator that can be moved can be moved in both directions (i.e. incremented and decremented). @since version 1.0.0, simplified in version 2.0.9, change to bidirectional iterators in version 3.0.0 (see https://github.com/nlohmann/json/issues/593) */ template class iter_impl { /// allow basic_json to access private members friend iter_impl::value, typename std::remove_const::type, const BasicJsonType>::type>; friend BasicJsonType; friend iteration_proxy; friend iteration_proxy_value; using object_t = typename BasicJsonType::object_t; using array_t = typename BasicJsonType::array_t; // make sure BasicJsonType is basic_json or const basic_json static_assert(is_basic_json::type>::value, "iter_impl only accepts (const) basic_json"); public: /// The std::iterator class template (used as a base class to provide typedefs) is deprecated in C++17. /// The C++ Standard has never required user-defined iterators to derive from std::iterator. /// A user-defined iterator should provide publicly accessible typedefs named /// iterator_category, value_type, difference_type, pointer, and reference. /// Note that value_type is required to be non-const, even for constant iterators. using iterator_category = std::bidirectional_iterator_tag; /// the type of the values when the iterator is dereferenced using value_type = typename BasicJsonType::value_type; /// a type to represent differences between iterators using difference_type = typename BasicJsonType::difference_type; /// defines a pointer to the type iterated over (value_type) using pointer = typename std::conditional::value, typename BasicJsonType::const_pointer, typename BasicJsonType::pointer>::type; /// defines a reference to the type iterated over (value_type) using reference = typename std::conditional::value, typename BasicJsonType::const_reference, typename BasicJsonType::reference>::type; /// default constructor iter_impl() = default; /*! @brief constructor for a given JSON instance @param[in] object pointer to a JSON object for this iterator @pre object != nullptr @post The iterator is initialized; i.e. `m_object != nullptr`. */ explicit iter_impl(pointer object) noexcept : m_object(object) { JSON_ASSERT(m_object != nullptr); switch (m_object->m_type) { case value_t::object: { m_it.object_iterator = typename object_t::iterator(); break; } case value_t::array: { m_it.array_iterator = typename array_t::iterator(); break; } default: { m_it.primitive_iterator = primitive_iterator_t(); break; } } } /*! @note The conventional copy constructor and copy assignment are implicitly defined. Combined with the following converting constructor and assignment, they support: (1) copy from iterator to iterator, (2) copy from const iterator to const iterator, and (3) conversion from iterator to const iterator. However conversion from const iterator to iterator is not defined. */ /*! @brief const copy constructor @param[in] other const iterator to copy from @note This copy constructor had to be defined explicitly to circumvent a bug occurring on msvc v19.0 compiler (VS 2015) debug build. For more information refer to: https://github.com/nlohmann/json/issues/1608 */ iter_impl(const iter_impl& other) noexcept : m_object(other.m_object), m_it(other.m_it) {} /*! @brief converting assignment @param[in] other const iterator to copy from @return const/non-const iterator @note It is not checked whether @a other is initialized. */ iter_impl& operator=(const iter_impl& other) noexcept { m_object = other.m_object; m_it = other.m_it; return *this; } /*! @brief converting constructor @param[in] other non-const iterator to copy from @note It is not checked whether @a other is initialized. */ iter_impl(const iter_impl::type>& other) noexcept : m_object(other.m_object), m_it(other.m_it) {} /*! @brief converting assignment @param[in] other non-const iterator to copy from @return const/non-const iterator @note It is not checked whether @a other is initialized. */ iter_impl& operator=(const iter_impl::type>& other) noexcept { m_object = other.m_object; m_it = other.m_it; return *this; } private: /*! @brief set the iterator to the first value @pre The iterator is initialized; i.e. `m_object != nullptr`. */ void set_begin() noexcept { JSON_ASSERT(m_object != nullptr); switch (m_object->m_type) { case value_t::object: { m_it.object_iterator = m_object->m_value.object->begin(); break; } case value_t::array: { m_it.array_iterator = m_object->m_value.array->begin(); break; } case value_t::null: { // set to end so begin()==end() is true: null is empty m_it.primitive_iterator.set_end(); break; } default: { m_it.primitive_iterator.set_begin(); break; } } } /*! @brief set the iterator past the last value @pre The iterator is initialized; i.e. `m_object != nullptr`. */ void set_end() noexcept { JSON_ASSERT(m_object != nullptr); switch (m_object->m_type) { case value_t::object: { m_it.object_iterator = m_object->m_value.object->end(); break; } case value_t::array: { m_it.array_iterator = m_object->m_value.array->end(); break; } default: { m_it.primitive_iterator.set_end(); break; } } } public: /*! @brief return a reference to the value pointed to by the iterator @pre The iterator is initialized; i.e. `m_object != nullptr`. */ reference operator*() const { JSON_ASSERT(m_object != nullptr); switch (m_object->m_type) { case value_t::object: { JSON_ASSERT(m_it.object_iterator != m_object->m_value.object->end()); return m_it.object_iterator->second; } case value_t::array: { JSON_ASSERT(m_it.array_iterator != m_object->m_value.array->end()); return *m_it.array_iterator; } case value_t::null: JSON_THROW(invalid_iterator::create(214, "cannot get value")); default: { if (JSON_HEDLEY_LIKELY(m_it.primitive_iterator.is_begin())) { return *m_object; } JSON_THROW(invalid_iterator::create(214, "cannot get value")); } } } /*! @brief dereference the iterator @pre The iterator is initialized; i.e. `m_object != nullptr`. */ pointer operator->() const { JSON_ASSERT(m_object != nullptr); switch (m_object->m_type) { case value_t::object: { JSON_ASSERT(m_it.object_iterator != m_object->m_value.object->end()); return &(m_it.object_iterator->second); } case value_t::array: { JSON_ASSERT(m_it.array_iterator != m_object->m_value.array->end()); return &*m_it.array_iterator; } default: { if (JSON_HEDLEY_LIKELY(m_it.primitive_iterator.is_begin())) { return m_object; } JSON_THROW(invalid_iterator::create(214, "cannot get value")); } } } /*! @brief post-increment (it++) @pre The iterator is initialized; i.e. `m_object != nullptr`. */ iter_impl const operator++(int) { auto result = *this; ++(*this); return result; } /*! @brief pre-increment (++it) @pre The iterator is initialized; i.e. `m_object != nullptr`. */ iter_impl& operator++() { JSON_ASSERT(m_object != nullptr); switch (m_object->m_type) { case value_t::object: { std::advance(m_it.object_iterator, 1); break; } case value_t::array: { std::advance(m_it.array_iterator, 1); break; } default: { ++m_it.primitive_iterator; break; } } return *this; } /*! @brief post-decrement (it--) @pre The iterator is initialized; i.e. `m_object != nullptr`. */ iter_impl const operator--(int) { auto result = *this; --(*this); return result; } /*! @brief pre-decrement (--it) @pre The iterator is initialized; i.e. `m_object != nullptr`. */ iter_impl& operator--() { JSON_ASSERT(m_object != nullptr); switch (m_object->m_type) { case value_t::object: { std::advance(m_it.object_iterator, -1); break; } case value_t::array: { std::advance(m_it.array_iterator, -1); break; } default: { --m_it.primitive_iterator; break; } } return *this; } /*! @brief comparison: equal @pre The iterator is initialized; i.e. `m_object != nullptr`. */ bool operator==(const iter_impl& other) const { // if objects are not the same, the comparison is undefined if (JSON_HEDLEY_UNLIKELY(m_object != other.m_object)) { JSON_THROW(invalid_iterator::create(212, "cannot compare iterators of different containers")); } JSON_ASSERT(m_object != nullptr); switch (m_object->m_type) { case value_t::object: return (m_it.object_iterator == other.m_it.object_iterator); case value_t::array: return (m_it.array_iterator == other.m_it.array_iterator); default: return (m_it.primitive_iterator == other.m_it.primitive_iterator); } } /*! @brief comparison: not equal @pre The iterator is initialized; i.e. `m_object != nullptr`. */ bool operator!=(const iter_impl& other) const { return !operator==(other); } /*! @brief comparison: smaller @pre The iterator is initialized; i.e. `m_object != nullptr`. */ bool operator<(const iter_impl& other) const { // if objects are not the same, the comparison is undefined if (JSON_HEDLEY_UNLIKELY(m_object != other.m_object)) { JSON_THROW(invalid_iterator::create(212, "cannot compare iterators of different containers")); } JSON_ASSERT(m_object != nullptr); switch (m_object->m_type) { case value_t::object: JSON_THROW(invalid_iterator::create(213, "cannot compare order of object iterators")); case value_t::array: return (m_it.array_iterator < other.m_it.array_iterator); default: return (m_it.primitive_iterator < other.m_it.primitive_iterator); } } /*! @brief comparison: less than or equal @pre The iterator is initialized; i.e. `m_object != nullptr`. */ bool operator<=(const iter_impl& other) const { return !other.operator < (*this); } /*! @brief comparison: greater than @pre The iterator is initialized; i.e. `m_object != nullptr`. */ bool operator>(const iter_impl& other) const { return !operator<=(other); } /*! @brief comparison: greater than or equal @pre The iterator is initialized; i.e. `m_object != nullptr`. */ bool operator>=(const iter_impl& other) const { return !operator<(other); } /*! @brief add to iterator @pre The iterator is initialized; i.e. `m_object != nullptr`. */ iter_impl& operator+=(difference_type i) { JSON_ASSERT(m_object != nullptr); switch (m_object->m_type) { case value_t::object: JSON_THROW(invalid_iterator::create(209, "cannot use offsets with object iterators")); case value_t::array: { std::advance(m_it.array_iterator, i); break; } default: { m_it.primitive_iterator += i; break; } } return *this; } /*! @brief subtract from iterator @pre The iterator is initialized; i.e. `m_object != nullptr`. */ iter_impl& operator-=(difference_type i) { return operator+=(-i); } /*! @brief add to iterator @pre The iterator is initialized; i.e. `m_object != nullptr`. */ iter_impl operator+(difference_type i) const { auto result = *this; result += i; return result; } /*! @brief addition of distance and iterator @pre The iterator is initialized; i.e. `m_object != nullptr`. */ friend iter_impl operator+(difference_type i, const iter_impl& it) { auto result = it; result += i; return result; } /*! @brief subtract from iterator @pre The iterator is initialized; i.e. `m_object != nullptr`. */ iter_impl operator-(difference_type i) const { auto result = *this; result -= i; return result; } /*! @brief return difference @pre The iterator is initialized; i.e. `m_object != nullptr`. */ difference_type operator-(const iter_impl& other) const { JSON_ASSERT(m_object != nullptr); switch (m_object->m_type) { case value_t::object: JSON_THROW(invalid_iterator::create(209, "cannot use offsets with object iterators")); case value_t::array: return m_it.array_iterator - other.m_it.array_iterator; default: return m_it.primitive_iterator - other.m_it.primitive_iterator; } } /*! @brief access to successor @pre The iterator is initialized; i.e. `m_object != nullptr`. */ reference operator[](difference_type n) const { JSON_ASSERT(m_object != nullptr); switch (m_object->m_type) { case value_t::object: JSON_THROW(invalid_iterator::create(208, "cannot use operator[] for object iterators")); case value_t::array: return *std::next(m_it.array_iterator, n); case value_t::null: JSON_THROW(invalid_iterator::create(214, "cannot get value")); default: { if (JSON_HEDLEY_LIKELY(m_it.primitive_iterator.get_value() == -n)) { return *m_object; } JSON_THROW(invalid_iterator::create(214, "cannot get value")); } } } /*! @brief return the key of an object iterator @pre The iterator is initialized; i.e. `m_object != nullptr`. */ const typename object_t::key_type& key() const { JSON_ASSERT(m_object != nullptr); if (JSON_HEDLEY_LIKELY(m_object->is_object())) { return m_it.object_iterator->first; } JSON_THROW(invalid_iterator::create(207, "cannot use key() for non-object iterators")); } /*! @brief return the value of an iterator @pre The iterator is initialized; i.e. `m_object != nullptr`. */ reference value() const { return operator*(); } private: /// associated JSON instance pointer m_object = nullptr; /// the actual iterator of the associated instance internal_iterator::type> m_it {}; }; } // namespace detail } // namespace nlohmann // #include // #include #include // ptrdiff_t #include // reverse_iterator #include // declval namespace nlohmann { namespace detail { ////////////////////// // reverse_iterator // ////////////////////// /*! @brief a template for a reverse iterator class @tparam Base the base iterator type to reverse. Valid types are @ref iterator (to create @ref reverse_iterator) and @ref const_iterator (to create @ref const_reverse_iterator). @requirement The class satisfies the following concept requirements: - [BidirectionalIterator](https://en.cppreference.com/w/cpp/named_req/BidirectionalIterator): The iterator that can be moved can be moved in both directions (i.e. incremented and decremented). - [OutputIterator](https://en.cppreference.com/w/cpp/named_req/OutputIterator): It is possible to write to the pointed-to element (only if @a Base is @ref iterator). @since version 1.0.0 */ template class json_reverse_iterator : public std::reverse_iterator { public: using difference_type = std::ptrdiff_t; /// shortcut to the reverse iterator adapter using base_iterator = std::reverse_iterator; /// the reference type for the pointed-to element using reference = typename Base::reference; /// create reverse iterator from iterator explicit json_reverse_iterator(const typename base_iterator::iterator_type& it) noexcept : base_iterator(it) {} /// create reverse iterator from base class explicit json_reverse_iterator(const base_iterator& it) noexcept : base_iterator(it) {} /// post-increment (it++) json_reverse_iterator const operator++(int) { return static_cast(base_iterator::operator++(1)); } /// pre-increment (++it) json_reverse_iterator& operator++() { return static_cast(base_iterator::operator++()); } /// post-decrement (it--) json_reverse_iterator const operator--(int) { return static_cast(base_iterator::operator--(1)); } /// pre-decrement (--it) json_reverse_iterator& operator--() { return static_cast(base_iterator::operator--()); } /// add to iterator json_reverse_iterator& operator+=(difference_type i) { return static_cast(base_iterator::operator+=(i)); } /// add to iterator json_reverse_iterator operator+(difference_type i) const { return static_cast(base_iterator::operator+(i)); } /// subtract from iterator json_reverse_iterator operator-(difference_type i) const { return static_cast(base_iterator::operator-(i)); } /// return difference difference_type operator-(const json_reverse_iterator& other) const { return base_iterator(*this) - base_iterator(other); } /// access to successor reference operator[](difference_type n) const { return *(this->operator+(n)); } /// return the key of an object iterator auto key() const -> decltype(std::declval().key()) { auto it = --this->base(); return it.key(); } /// return the value of an iterator reference value() const { auto it = --this->base(); return it.operator * (); } }; } // namespace detail } // namespace nlohmann // #include // #include #include // all_of #include // isdigit #include // max #include // accumulate #include // string #include // move #include // vector // #include // #include // #include namespace nlohmann { template class json_pointer { // allow basic_json to access private members NLOHMANN_BASIC_JSON_TPL_DECLARATION friend class basic_json; public: /*! @brief create JSON pointer Create a JSON pointer according to the syntax described in [Section 3 of RFC6901](https://tools.ietf.org/html/rfc6901#section-3). @param[in] s string representing the JSON pointer; if omitted, the empty string is assumed which references the whole JSON value @throw parse_error.107 if the given JSON pointer @a s is nonempty and does not begin with a slash (`/`); see example below @throw parse_error.108 if a tilde (`~`) in the given JSON pointer @a s is not followed by `0` (representing `~`) or `1` (representing `/`); see example below @liveexample{The example shows the construction several valid JSON pointers as well as the exceptional behavior.,json_pointer} @since version 2.0.0 */ explicit json_pointer(const std::string& s = "") : reference_tokens(split(s)) {} /*! @brief return a string representation of the JSON pointer @invariant For each JSON pointer `ptr`, it holds: @code {.cpp} ptr == json_pointer(ptr.to_string()); @endcode @return a string representation of the JSON pointer @liveexample{The example shows the result of `to_string`.,json_pointer__to_string} @since version 2.0.0 */ std::string to_string() const { return std::accumulate(reference_tokens.begin(), reference_tokens.end(), std::string{}, [](const std::string & a, const std::string & b) { return a + "/" + escape(b); }); } /// @copydoc to_string() operator std::string() const { return to_string(); } /*! @brief append another JSON pointer at the end of this JSON pointer @param[in] ptr JSON pointer to append @return JSON pointer with @a ptr appended @liveexample{The example shows the usage of `operator/=`.,json_pointer__operator_add} @complexity Linear in the length of @a ptr. @sa @ref operator/=(std::string) to append a reference token @sa @ref operator/=(std::size_t) to append an array index @sa @ref operator/(const json_pointer&, const json_pointer&) for a binary operator @since version 3.6.0 */ json_pointer& operator/=(const json_pointer& ptr) { reference_tokens.insert(reference_tokens.end(), ptr.reference_tokens.begin(), ptr.reference_tokens.end()); return *this; } /*! @brief append an unescaped reference token at the end of this JSON pointer @param[in] token reference token to append @return JSON pointer with @a token appended without escaping @a token @liveexample{The example shows the usage of `operator/=`.,json_pointer__operator_add} @complexity Amortized constant. @sa @ref operator/=(const json_pointer&) to append a JSON pointer @sa @ref operator/=(std::size_t) to append an array index @sa @ref operator/(const json_pointer&, std::size_t) for a binary operator @since version 3.6.0 */ json_pointer& operator/=(std::string token) { push_back(std::move(token)); return *this; } /*! @brief append an array index at the end of this JSON pointer @param[in] array_idx array index to append @return JSON pointer with @a array_idx appended @liveexample{The example shows the usage of `operator/=`.,json_pointer__operator_add} @complexity Amortized constant. @sa @ref operator/=(const json_pointer&) to append a JSON pointer @sa @ref operator/=(std::string) to append a reference token @sa @ref operator/(const json_pointer&, std::string) for a binary operator @since version 3.6.0 */ json_pointer& operator/=(std::size_t array_idx) { return *this /= std::to_string(array_idx); } /*! @brief create a new JSON pointer by appending the right JSON pointer at the end of the left JSON pointer @param[in] lhs JSON pointer @param[in] rhs JSON pointer @return a new JSON pointer with @a rhs appended to @a lhs @liveexample{The example shows the usage of `operator/`.,json_pointer__operator_add_binary} @complexity Linear in the length of @a lhs and @a rhs. @sa @ref operator/=(const json_pointer&) to append a JSON pointer @since version 3.6.0 */ friend json_pointer operator/(const json_pointer& lhs, const json_pointer& rhs) { return json_pointer(lhs) /= rhs; } /*! @brief create a new JSON pointer by appending the unescaped token at the end of the JSON pointer @param[in] ptr JSON pointer @param[in] token reference token @return a new JSON pointer with unescaped @a token appended to @a ptr @liveexample{The example shows the usage of `operator/`.,json_pointer__operator_add_binary} @complexity Linear in the length of @a ptr. @sa @ref operator/=(std::string) to append a reference token @since version 3.6.0 */ friend json_pointer operator/(const json_pointer& ptr, std::string token) { return json_pointer(ptr) /= std::move(token); } /*! @brief create a new JSON pointer by appending the array-index-token at the end of the JSON pointer @param[in] ptr JSON pointer @param[in] array_idx array index @return a new JSON pointer with @a array_idx appended to @a ptr @liveexample{The example shows the usage of `operator/`.,json_pointer__operator_add_binary} @complexity Linear in the length of @a ptr. @sa @ref operator/=(std::size_t) to append an array index @since version 3.6.0 */ friend json_pointer operator/(const json_pointer& ptr, std::size_t array_idx) { return json_pointer(ptr) /= array_idx; } /*! @brief returns the parent of this JSON pointer @return parent of this JSON pointer; in case this JSON pointer is the root, the root itself is returned @complexity Linear in the length of the JSON pointer. @liveexample{The example shows the result of `parent_pointer` for different JSON Pointers.,json_pointer__parent_pointer} @since version 3.6.0 */ json_pointer parent_pointer() const { if (empty()) { return *this; } json_pointer res = *this; res.pop_back(); return res; } /*! @brief remove last reference token @pre not `empty()` @liveexample{The example shows the usage of `pop_back`.,json_pointer__pop_back} @complexity Constant. @throw out_of_range.405 if JSON pointer has no parent @since version 3.6.0 */ void pop_back() { if (JSON_HEDLEY_UNLIKELY(empty())) { JSON_THROW(detail::out_of_range::create(405, "JSON pointer has no parent")); } reference_tokens.pop_back(); } /*! @brief return last reference token @pre not `empty()` @return last reference token @liveexample{The example shows the usage of `back`.,json_pointer__back} @complexity Constant. @throw out_of_range.405 if JSON pointer has no parent @since version 3.6.0 */ const std::string& back() const { if (JSON_HEDLEY_UNLIKELY(empty())) { JSON_THROW(detail::out_of_range::create(405, "JSON pointer has no parent")); } return reference_tokens.back(); } /*! @brief append an unescaped token at the end of the reference pointer @param[in] token token to add @complexity Amortized constant. @liveexample{The example shows the result of `push_back` for different JSON Pointers.,json_pointer__push_back} @since version 3.6.0 */ void push_back(const std::string& token) { reference_tokens.push_back(token); } /// @copydoc push_back(const std::string&) void push_back(std::string&& token) { reference_tokens.push_back(std::move(token)); } /*! @brief return whether pointer points to the root document @return true iff the JSON pointer points to the root document @complexity Constant. @exceptionsafety No-throw guarantee: this function never throws exceptions. @liveexample{The example shows the result of `empty` for different JSON Pointers.,json_pointer__empty} @since version 3.6.0 */ bool empty() const noexcept { return reference_tokens.empty(); } private: /*! @param[in] s reference token to be converted into an array index @return integer representation of @a s @throw parse_error.106 if an array index begins with '0' @throw parse_error.109 if an array index begins not with a digit @throw out_of_range.404 if string @a s could not be converted to an integer @throw out_of_range.410 if an array index exceeds size_type */ static typename BasicJsonType::size_type array_index(const std::string& s) { using size_type = typename BasicJsonType::size_type; // error condition (cf. RFC 6901, Sect. 4) if (JSON_HEDLEY_UNLIKELY(s.size() > 1 && s[0] == '0')) { JSON_THROW(detail::parse_error::create(106, 0, "array index '" + s + "' must not begin with '0'")); } // error condition (cf. RFC 6901, Sect. 4) if (JSON_HEDLEY_UNLIKELY(s.size() > 1 && !(s[0] >= '1' && s[0] <= '9'))) { JSON_THROW(detail::parse_error::create(109, 0, "array index '" + s + "' is not a number")); } std::size_t processed_chars = 0; unsigned long long res = 0; JSON_TRY { res = std::stoull(s, &processed_chars); } JSON_CATCH(std::out_of_range&) { JSON_THROW(detail::out_of_range::create(404, "unresolved reference token '" + s + "'")); } // check if the string was completely read if (JSON_HEDLEY_UNLIKELY(processed_chars != s.size())) { JSON_THROW(detail::out_of_range::create(404, "unresolved reference token '" + s + "'")); } // only triggered on special platforms (like 32bit), see also // https://github.com/nlohmann/json/pull/2203 if (res >= static_cast((std::numeric_limits::max)())) { JSON_THROW(detail::out_of_range::create(410, "array index " + s + " exceeds size_type")); // LCOV_EXCL_LINE } return static_cast(res); } json_pointer top() const { if (JSON_HEDLEY_UNLIKELY(empty())) { JSON_THROW(detail::out_of_range::create(405, "JSON pointer has no parent")); } json_pointer result = *this; result.reference_tokens = {reference_tokens[0]}; return result; } /*! @brief create and return a reference to the pointed to value @complexity Linear in the number of reference tokens. @throw parse_error.109 if array index is not a number @throw type_error.313 if value cannot be unflattened */ BasicJsonType& get_and_create(BasicJsonType& j) const { auto result = &j; // in case no reference tokens exist, return a reference to the JSON value // j which will be overwritten by a primitive value for (const auto& reference_token : reference_tokens) { switch (result->type()) { case detail::value_t::null: { if (reference_token == "0") { // start a new array if reference token is 0 result = &result->operator[](0); } else { // start a new object otherwise result = &result->operator[](reference_token); } break; } case detail::value_t::object: { // create an entry in the object result = &result->operator[](reference_token); break; } case detail::value_t::array: { // create an entry in the array result = &result->operator[](array_index(reference_token)); break; } /* The following code is only reached if there exists a reference token _and_ the current value is primitive. In this case, we have an error situation, because primitive values may only occur as single value; that is, with an empty list of reference tokens. */ default: JSON_THROW(detail::type_error::create(313, "invalid value to unflatten")); } } return *result; } /*! @brief return a reference to the pointed to value @note This version does not throw if a value is not present, but tries to create nested values instead. For instance, calling this function with pointer `"/this/that"` on a null value is equivalent to calling `operator[]("this").operator[]("that")` on that value, effectively changing the null value to an object. @param[in] ptr a JSON value @return reference to the JSON value pointed to by the JSON pointer @complexity Linear in the length of the JSON pointer. @throw parse_error.106 if an array index begins with '0' @throw parse_error.109 if an array index was not a number @throw out_of_range.404 if the JSON pointer can not be resolved */ BasicJsonType& get_unchecked(BasicJsonType* ptr) const { for (const auto& reference_token : reference_tokens) { // convert null values to arrays or objects before continuing if (ptr->is_null()) { // check if reference token is a number const bool nums = std::all_of(reference_token.begin(), reference_token.end(), [](const unsigned char x) { return std::isdigit(x); }); // change value to array for numbers or "-" or to object otherwise *ptr = (nums || reference_token == "-") ? detail::value_t::array : detail::value_t::object; } switch (ptr->type()) { case detail::value_t::object: { // use unchecked object access ptr = &ptr->operator[](reference_token); break; } case detail::value_t::array: { if (reference_token == "-") { // explicitly treat "-" as index beyond the end ptr = &ptr->operator[](ptr->m_value.array->size()); } else { // convert array index to number; unchecked access ptr = &ptr->operator[](array_index(reference_token)); } break; } default: JSON_THROW(detail::out_of_range::create(404, "unresolved reference token '" + reference_token + "'")); } } return *ptr; } /*! @throw parse_error.106 if an array index begins with '0' @throw parse_error.109 if an array index was not a number @throw out_of_range.402 if the array index '-' is used @throw out_of_range.404 if the JSON pointer can not be resolved */ BasicJsonType& get_checked(BasicJsonType* ptr) const { for (const auto& reference_token : reference_tokens) { switch (ptr->type()) { case detail::value_t::object: { // note: at performs range check ptr = &ptr->at(reference_token); break; } case detail::value_t::array: { if (JSON_HEDLEY_UNLIKELY(reference_token == "-")) { // "-" always fails the range check JSON_THROW(detail::out_of_range::create(402, "array index '-' (" + std::to_string(ptr->m_value.array->size()) + ") is out of range")); } // note: at performs range check ptr = &ptr->at(array_index(reference_token)); break; } default: JSON_THROW(detail::out_of_range::create(404, "unresolved reference token '" + reference_token + "'")); } } return *ptr; } /*! @brief return a const reference to the pointed to value @param[in] ptr a JSON value @return const reference to the JSON value pointed to by the JSON pointer @throw parse_error.106 if an array index begins with '0' @throw parse_error.109 if an array index was not a number @throw out_of_range.402 if the array index '-' is used @throw out_of_range.404 if the JSON pointer can not be resolved */ const BasicJsonType& get_unchecked(const BasicJsonType* ptr) const { for (const auto& reference_token : reference_tokens) { switch (ptr->type()) { case detail::value_t::object: { // use unchecked object access ptr = &ptr->operator[](reference_token); break; } case detail::value_t::array: { if (JSON_HEDLEY_UNLIKELY(reference_token == "-")) { // "-" cannot be used for const access JSON_THROW(detail::out_of_range::create(402, "array index '-' (" + std::to_string(ptr->m_value.array->size()) + ") is out of range")); } // use unchecked array access ptr = &ptr->operator[](array_index(reference_token)); break; } default: JSON_THROW(detail::out_of_range::create(404, "unresolved reference token '" + reference_token + "'")); } } return *ptr; } /*! @throw parse_error.106 if an array index begins with '0' @throw parse_error.109 if an array index was not a number @throw out_of_range.402 if the array index '-' is used @throw out_of_range.404 if the JSON pointer can not be resolved */ const BasicJsonType& get_checked(const BasicJsonType* ptr) const { for (const auto& reference_token : reference_tokens) { switch (ptr->type()) { case detail::value_t::object: { // note: at performs range check ptr = &ptr->at(reference_token); break; } case detail::value_t::array: { if (JSON_HEDLEY_UNLIKELY(reference_token == "-")) { // "-" always fails the range check JSON_THROW(detail::out_of_range::create(402, "array index '-' (" + std::to_string(ptr->m_value.array->size()) + ") is out of range")); } // note: at performs range check ptr = &ptr->at(array_index(reference_token)); break; } default: JSON_THROW(detail::out_of_range::create(404, "unresolved reference token '" + reference_token + "'")); } } return *ptr; } /*! @throw parse_error.106 if an array index begins with '0' @throw parse_error.109 if an array index was not a number */ bool contains(const BasicJsonType* ptr) const { for (const auto& reference_token : reference_tokens) { switch (ptr->type()) { case detail::value_t::object: { if (!ptr->contains(reference_token)) { // we did not find the key in the object return false; } ptr = &ptr->operator[](reference_token); break; } case detail::value_t::array: { if (JSON_HEDLEY_UNLIKELY(reference_token == "-")) { // "-" always fails the range check return false; } if (JSON_HEDLEY_UNLIKELY(reference_token.size() == 1 && !("0" <= reference_token && reference_token <= "9"))) { // invalid char return false; } if (JSON_HEDLEY_UNLIKELY(reference_token.size() > 1)) { if (JSON_HEDLEY_UNLIKELY(!('1' <= reference_token[0] && reference_token[0] <= '9'))) { // first char should be between '1' and '9' return false; } for (std::size_t i = 1; i < reference_token.size(); i++) { if (JSON_HEDLEY_UNLIKELY(!('0' <= reference_token[i] && reference_token[i] <= '9'))) { // other char should be between '0' and '9' return false; } } } const auto idx = array_index(reference_token); if (idx >= ptr->size()) { // index out of range return false; } ptr = &ptr->operator[](idx); break; } default: { // we do not expect primitive values if there is still a // reference token to process return false; } } } // no reference token left means we found a primitive value return true; } /*! @brief split the string input to reference tokens @note This function is only called by the json_pointer constructor. All exceptions below are documented there. @throw parse_error.107 if the pointer is not empty or begins with '/' @throw parse_error.108 if character '~' is not followed by '0' or '1' */ static std::vector split(const std::string& reference_string) { std::vector result; // special case: empty reference string -> no reference tokens if (reference_string.empty()) { return result; } // check if nonempty reference string begins with slash if (JSON_HEDLEY_UNLIKELY(reference_string[0] != '/')) { JSON_THROW(detail::parse_error::create(107, 1, "JSON pointer must be empty or begin with '/' - was: '" + reference_string + "'")); } // extract the reference tokens: // - slash: position of the last read slash (or end of string) // - start: position after the previous slash for ( // search for the first slash after the first character std::size_t slash = reference_string.find_first_of('/', 1), // set the beginning of the first reference token start = 1; // we can stop if start == 0 (if slash == std::string::npos) start != 0; // set the beginning of the next reference token // (will eventually be 0 if slash == std::string::npos) start = (slash == std::string::npos) ? 0 : slash + 1, // find next slash slash = reference_string.find_first_of('/', start)) { // use the text between the beginning of the reference token // (start) and the last slash (slash). auto reference_token = reference_string.substr(start, slash - start); // check reference tokens are properly escaped for (std::size_t pos = reference_token.find_first_of('~'); pos != std::string::npos; pos = reference_token.find_first_of('~', pos + 1)) { JSON_ASSERT(reference_token[pos] == '~'); // ~ must be followed by 0 or 1 if (JSON_HEDLEY_UNLIKELY(pos == reference_token.size() - 1 || (reference_token[pos + 1] != '0' && reference_token[pos + 1] != '1'))) { JSON_THROW(detail::parse_error::create(108, 0, "escape character '~' must be followed with '0' or '1'")); } } // finally, store the reference token unescape(reference_token); result.push_back(reference_token); } return result; } /*! @brief replace all occurrences of a substring by another string @param[in,out] s the string to manipulate; changed so that all occurrences of @a f are replaced with @a t @param[in] f the substring to replace with @a t @param[in] t the string to replace @a f @pre The search string @a f must not be empty. **This precondition is enforced with an assertion.** @since version 2.0.0 */ static void replace_substring(std::string& s, const std::string& f, const std::string& t) { JSON_ASSERT(!f.empty()); for (auto pos = s.find(f); // find first occurrence of f pos != std::string::npos; // make sure f was found s.replace(pos, f.size(), t), // replace with t, and pos = s.find(f, pos + t.size())) // find next occurrence of f {} } /// escape "~" to "~0" and "/" to "~1" static std::string escape(std::string s) { replace_substring(s, "~", "~0"); replace_substring(s, "/", "~1"); return s; } /// unescape "~1" to tilde and "~0" to slash (order is important!) static void unescape(std::string& s) { replace_substring(s, "~1", "/"); replace_substring(s, "~0", "~"); } /*! @param[in] reference_string the reference string to the current value @param[in] value the value to consider @param[in,out] result the result object to insert values to @note Empty objects or arrays are flattened to `null`. */ static void flatten(const std::string& reference_string, const BasicJsonType& value, BasicJsonType& result) { switch (value.type()) { case detail::value_t::array: { if (value.m_value.array->empty()) { // flatten empty array as null result[reference_string] = nullptr; } else { // iterate array and use index as reference string for (std::size_t i = 0; i < value.m_value.array->size(); ++i) { flatten(reference_string + "/" + std::to_string(i), value.m_value.array->operator[](i), result); } } break; } case detail::value_t::object: { if (value.m_value.object->empty()) { // flatten empty object as null result[reference_string] = nullptr; } else { // iterate object and use keys as reference string for (const auto& element : *value.m_value.object) { flatten(reference_string + "/" + escape(element.first), element.second, result); } } break; } default: { // add primitive value with its reference string result[reference_string] = value; break; } } } /*! @param[in] value flattened JSON @return unflattened JSON @throw parse_error.109 if array index is not a number @throw type_error.314 if value is not an object @throw type_error.315 if object values are not primitive @throw type_error.313 if value cannot be unflattened */ static BasicJsonType unflatten(const BasicJsonType& value) { if (JSON_HEDLEY_UNLIKELY(!value.is_object())) { JSON_THROW(detail::type_error::create(314, "only objects can be unflattened")); } BasicJsonType result; // iterate the JSON object values for (const auto& element : *value.m_value.object) { if (JSON_HEDLEY_UNLIKELY(!element.second.is_primitive())) { JSON_THROW(detail::type_error::create(315, "values in object must be primitive")); } // assign value to reference pointed to by JSON pointer; Note that if // the JSON pointer is "" (i.e., points to the whole value), function // get_and_create returns a reference to result itself. An assignment // will then create a primitive value. json_pointer(element.first).get_and_create(result) = element.second; } return result; } /*! @brief compares two JSON pointers for equality @param[in] lhs JSON pointer to compare @param[in] rhs JSON pointer to compare @return whether @a lhs is equal to @a rhs @complexity Linear in the length of the JSON pointer @exceptionsafety No-throw guarantee: this function never throws exceptions. */ friend bool operator==(json_pointer const& lhs, json_pointer const& rhs) noexcept { return lhs.reference_tokens == rhs.reference_tokens; } /*! @brief compares two JSON pointers for inequality @param[in] lhs JSON pointer to compare @param[in] rhs JSON pointer to compare @return whether @a lhs is not equal @a rhs @complexity Linear in the length of the JSON pointer @exceptionsafety No-throw guarantee: this function never throws exceptions. */ friend bool operator!=(json_pointer const& lhs, json_pointer const& rhs) noexcept { return !(lhs == rhs); } /// the reference tokens std::vector reference_tokens; }; } // namespace nlohmann // #include #include #include // #include namespace nlohmann { namespace detail { template class json_ref { public: using value_type = BasicJsonType; json_ref(value_type&& value) : owned_value(std::move(value)) , value_ref(&owned_value) , is_rvalue(true) {} json_ref(const value_type& value) : value_ref(const_cast(&value)) , is_rvalue(false) {} json_ref(std::initializer_list init) : owned_value(init) , value_ref(&owned_value) , is_rvalue(true) {} template < class... Args, enable_if_t::value, int> = 0 > json_ref(Args && ... args) : owned_value(std::forward(args)...) , value_ref(&owned_value) , is_rvalue(true) {} // class should be movable only json_ref(json_ref&&) = default; json_ref(const json_ref&) = delete; json_ref& operator=(const json_ref&) = delete; json_ref& operator=(json_ref&&) = delete; ~json_ref() = default; value_type moved_or_copied() const { if (is_rvalue) { return std::move(*value_ref); } return *value_ref; } value_type const& operator*() const { return *static_cast(value_ref); } value_type const* operator->() const { return static_cast(value_ref); } private: mutable value_type owned_value = nullptr; value_type* value_ref = nullptr; const bool is_rvalue = true; }; } // namespace detail } // namespace nlohmann // #include // #include // #include // #include #include // reverse #include // array #include // uint8_t, uint16_t, uint32_t, uint64_t #include // memcpy #include // numeric_limits #include // string #include // isnan, isinf // #include // #include // #include #include // copy #include // size_t #include // streamsize #include // back_inserter #include // shared_ptr, make_shared #include // basic_ostream #include // basic_string #include // vector // #include namespace nlohmann { namespace detail { /// abstract output adapter interface template struct output_adapter_protocol { virtual void write_character(CharType c) = 0; virtual void write_characters(const CharType* s, std::size_t length) = 0; virtual ~output_adapter_protocol() = default; }; /// a type to simplify interfaces template using output_adapter_t = std::shared_ptr>; /// output adapter for byte vectors template class output_vector_adapter : public output_adapter_protocol { public: explicit output_vector_adapter(std::vector& vec) noexcept : v(vec) {} void write_character(CharType c) override { v.push_back(c); } JSON_HEDLEY_NON_NULL(2) void write_characters(const CharType* s, std::size_t length) override { std::copy(s, s + length, std::back_inserter(v)); } private: std::vector& v; }; /// output adapter for output streams template class output_stream_adapter : public output_adapter_protocol { public: explicit output_stream_adapter(std::basic_ostream& s) noexcept : stream(s) {} void write_character(CharType c) override { stream.put(c); } JSON_HEDLEY_NON_NULL(2) void write_characters(const CharType* s, std::size_t length) override { stream.write(s, static_cast(length)); } private: std::basic_ostream& stream; }; /// output adapter for basic_string template> class output_string_adapter : public output_adapter_protocol { public: explicit output_string_adapter(StringType& s) noexcept : str(s) {} void write_character(CharType c) override { str.push_back(c); } JSON_HEDLEY_NON_NULL(2) void write_characters(const CharType* s, std::size_t length) override { str.append(s, length); } private: StringType& str; }; template> class output_adapter { public: output_adapter(std::vector& vec) : oa(std::make_shared>(vec)) {} output_adapter(std::basic_ostream& s) : oa(std::make_shared>(s)) {} output_adapter(StringType& s) : oa(std::make_shared>(s)) {} operator output_adapter_t() { return oa; } private: output_adapter_t oa = nullptr; }; } // namespace detail } // namespace nlohmann namespace nlohmann { namespace detail { /////////////////// // binary writer // /////////////////// /*! @brief serialization to CBOR and MessagePack values */ template class binary_writer { using string_t = typename BasicJsonType::string_t; using binary_t = typename BasicJsonType::binary_t; using number_float_t = typename BasicJsonType::number_float_t; public: /*! @brief create a binary writer @param[in] adapter output adapter to write to */ explicit binary_writer(output_adapter_t adapter) : oa(adapter) { JSON_ASSERT(oa); } /*! @param[in] j JSON value to serialize @pre j.type() == value_t::object */ void write_bson(const BasicJsonType& j) { switch (j.type()) { case value_t::object: { write_bson_object(*j.m_value.object); break; } default: { JSON_THROW(type_error::create(317, "to serialize to BSON, top-level type must be object, but is " + std::string(j.type_name()))); } } } /*! @param[in] j JSON value to serialize */ void write_cbor(const BasicJsonType& j) { switch (j.type()) { case value_t::null: { oa->write_character(to_char_type(0xF6)); break; } case value_t::boolean: { oa->write_character(j.m_value.boolean ? to_char_type(0xF5) : to_char_type(0xF4)); break; } case value_t::number_integer: { if (j.m_value.number_integer >= 0) { // CBOR does not differentiate between positive signed // integers and unsigned integers. Therefore, we used the // code from the value_t::number_unsigned case here. if (j.m_value.number_integer <= 0x17) { write_number(static_cast(j.m_value.number_integer)); } else if (j.m_value.number_integer <= (std::numeric_limits::max)()) { oa->write_character(to_char_type(0x18)); write_number(static_cast(j.m_value.number_integer)); } else if (j.m_value.number_integer <= (std::numeric_limits::max)()) { oa->write_character(to_char_type(0x19)); write_number(static_cast(j.m_value.number_integer)); } else if (j.m_value.number_integer <= (std::numeric_limits::max)()) { oa->write_character(to_char_type(0x1A)); write_number(static_cast(j.m_value.number_integer)); } else { oa->write_character(to_char_type(0x1B)); write_number(static_cast(j.m_value.number_integer)); } } else { // The conversions below encode the sign in the first // byte, and the value is converted to a positive number. const auto positive_number = -1 - j.m_value.number_integer; if (j.m_value.number_integer >= -24) { write_number(static_cast(0x20 + positive_number)); } else if (positive_number <= (std::numeric_limits::max)()) { oa->write_character(to_char_type(0x38)); write_number(static_cast(positive_number)); } else if (positive_number <= (std::numeric_limits::max)()) { oa->write_character(to_char_type(0x39)); write_number(static_cast(positive_number)); } else if (positive_number <= (std::numeric_limits::max)()) { oa->write_character(to_char_type(0x3A)); write_number(static_cast(positive_number)); } else { oa->write_character(to_char_type(0x3B)); write_number(static_cast(positive_number)); } } break; } case value_t::number_unsigned: { if (j.m_value.number_unsigned <= 0x17) { write_number(static_cast(j.m_value.number_unsigned)); } else if (j.m_value.number_unsigned <= (std::numeric_limits::max)()) { oa->write_character(to_char_type(0x18)); write_number(static_cast(j.m_value.number_unsigned)); } else if (j.m_value.number_unsigned <= (std::numeric_limits::max)()) { oa->write_character(to_char_type(0x19)); write_number(static_cast(j.m_value.number_unsigned)); } else if (j.m_value.number_unsigned <= (std::numeric_limits::max)()) { oa->write_character(to_char_type(0x1A)); write_number(static_cast(j.m_value.number_unsigned)); } else { oa->write_character(to_char_type(0x1B)); write_number(static_cast(j.m_value.number_unsigned)); } break; } case value_t::number_float: { if (std::isnan(j.m_value.number_float)) { // NaN is 0xf97e00 in CBOR oa->write_character(to_char_type(0xF9)); oa->write_character(to_char_type(0x7E)); oa->write_character(to_char_type(0x00)); } else if (std::isinf(j.m_value.number_float)) { // Infinity is 0xf97c00, -Infinity is 0xf9fc00 oa->write_character(to_char_type(0xf9)); oa->write_character(j.m_value.number_float > 0 ? to_char_type(0x7C) : to_char_type(0xFC)); oa->write_character(to_char_type(0x00)); } else { write_compact_float(j.m_value.number_float, detail::input_format_t::cbor); } break; } case value_t::string: { // step 1: write control byte and the string length const auto N = j.m_value.string->size(); if (N <= 0x17) { write_number(static_cast(0x60 + N)); } else if (N <= (std::numeric_limits::max)()) { oa->write_character(to_char_type(0x78)); write_number(static_cast(N)); } else if (N <= (std::numeric_limits::max)()) { oa->write_character(to_char_type(0x79)); write_number(static_cast(N)); } else if (N <= (std::numeric_limits::max)()) { oa->write_character(to_char_type(0x7A)); write_number(static_cast(N)); } // LCOV_EXCL_START else if (N <= (std::numeric_limits::max)()) { oa->write_character(to_char_type(0x7B)); write_number(static_cast(N)); } // LCOV_EXCL_STOP // step 2: write the string oa->write_characters( reinterpret_cast(j.m_value.string->c_str()), j.m_value.string->size()); break; } case value_t::array: { // step 1: write control byte and the array size const auto N = j.m_value.array->size(); if (N <= 0x17) { write_number(static_cast(0x80 + N)); } else if (N <= (std::numeric_limits::max)()) { oa->write_character(to_char_type(0x98)); write_number(static_cast(N)); } else if (N <= (std::numeric_limits::max)()) { oa->write_character(to_char_type(0x99)); write_number(static_cast(N)); } else if (N <= (std::numeric_limits::max)()) { oa->write_character(to_char_type(0x9A)); write_number(static_cast(N)); } // LCOV_EXCL_START else if (N <= (std::numeric_limits::max)()) { oa->write_character(to_char_type(0x9B)); write_number(static_cast(N)); } // LCOV_EXCL_STOP // step 2: write each element for (const auto& el : *j.m_value.array) { write_cbor(el); } break; } case value_t::binary: { if (j.m_value.binary->has_subtype()) { write_number(static_cast(0xd8)); write_number(j.m_value.binary->subtype()); } // step 1: write control byte and the binary array size const auto N = j.m_value.binary->size(); if (N <= 0x17) { write_number(static_cast(0x40 + N)); } else if (N <= (std::numeric_limits::max)()) { oa->write_character(to_char_type(0x58)); write_number(static_cast(N)); } else if (N <= (std::numeric_limits::max)()) { oa->write_character(to_char_type(0x59)); write_number(static_cast(N)); } else if (N <= (std::numeric_limits::max)()) { oa->write_character(to_char_type(0x5A)); write_number(static_cast(N)); } // LCOV_EXCL_START else if (N <= (std::numeric_limits::max)()) { oa->write_character(to_char_type(0x5B)); write_number(static_cast(N)); } // LCOV_EXCL_STOP // step 2: write each element oa->write_characters( reinterpret_cast(j.m_value.binary->data()), N); break; } case value_t::object: { // step 1: write control byte and the object size const auto N = j.m_value.object->size(); if (N <= 0x17) { write_number(static_cast(0xA0 + N)); } else if (N <= (std::numeric_limits::max)()) { oa->write_character(to_char_type(0xB8)); write_number(static_cast(N)); } else if (N <= (std::numeric_limits::max)()) { oa->write_character(to_char_type(0xB9)); write_number(static_cast(N)); } else if (N <= (std::numeric_limits::max)()) { oa->write_character(to_char_type(0xBA)); write_number(static_cast(N)); } // LCOV_EXCL_START else if (N <= (std::numeric_limits::max)()) { oa->write_character(to_char_type(0xBB)); write_number(static_cast(N)); } // LCOV_EXCL_STOP // step 2: write each element for (const auto& el : *j.m_value.object) { write_cbor(el.first); write_cbor(el.second); } break; } default: break; } } /*! @param[in] j JSON value to serialize */ void write_msgpack(const BasicJsonType& j) { switch (j.type()) { case value_t::null: // nil { oa->write_character(to_char_type(0xC0)); break; } case value_t::boolean: // true and false { oa->write_character(j.m_value.boolean ? to_char_type(0xC3) : to_char_type(0xC2)); break; } case value_t::number_integer: { if (j.m_value.number_integer >= 0) { // MessagePack does not differentiate between positive // signed integers and unsigned integers. Therefore, we used // the code from the value_t::number_unsigned case here. if (j.m_value.number_unsigned < 128) { // positive fixnum write_number(static_cast(j.m_value.number_integer)); } else if (j.m_value.number_unsigned <= (std::numeric_limits::max)()) { // uint 8 oa->write_character(to_char_type(0xCC)); write_number(static_cast(j.m_value.number_integer)); } else if (j.m_value.number_unsigned <= (std::numeric_limits::max)()) { // uint 16 oa->write_character(to_char_type(0xCD)); write_number(static_cast(j.m_value.number_integer)); } else if (j.m_value.number_unsigned <= (std::numeric_limits::max)()) { // uint 32 oa->write_character(to_char_type(0xCE)); write_number(static_cast(j.m_value.number_integer)); } else if (j.m_value.number_unsigned <= (std::numeric_limits::max)()) { // uint 64 oa->write_character(to_char_type(0xCF)); write_number(static_cast(j.m_value.number_integer)); } } else { if (j.m_value.number_integer >= -32) { // negative fixnum write_number(static_cast(j.m_value.number_integer)); } else if (j.m_value.number_integer >= (std::numeric_limits::min)() && j.m_value.number_integer <= (std::numeric_limits::max)()) { // int 8 oa->write_character(to_char_type(0xD0)); write_number(static_cast(j.m_value.number_integer)); } else if (j.m_value.number_integer >= (std::numeric_limits::min)() && j.m_value.number_integer <= (std::numeric_limits::max)()) { // int 16 oa->write_character(to_char_type(0xD1)); write_number(static_cast(j.m_value.number_integer)); } else if (j.m_value.number_integer >= (std::numeric_limits::min)() && j.m_value.number_integer <= (std::numeric_limits::max)()) { // int 32 oa->write_character(to_char_type(0xD2)); write_number(static_cast(j.m_value.number_integer)); } else if (j.m_value.number_integer >= (std::numeric_limits::min)() && j.m_value.number_integer <= (std::numeric_limits::max)()) { // int 64 oa->write_character(to_char_type(0xD3)); write_number(static_cast(j.m_value.number_integer)); } } break; } case value_t::number_unsigned: { if (j.m_value.number_unsigned < 128) { // positive fixnum write_number(static_cast(j.m_value.number_integer)); } else if (j.m_value.number_unsigned <= (std::numeric_limits::max)()) { // uint 8 oa->write_character(to_char_type(0xCC)); write_number(static_cast(j.m_value.number_integer)); } else if (j.m_value.number_unsigned <= (std::numeric_limits::max)()) { // uint 16 oa->write_character(to_char_type(0xCD)); write_number(static_cast(j.m_value.number_integer)); } else if (j.m_value.number_unsigned <= (std::numeric_limits::max)()) { // uint 32 oa->write_character(to_char_type(0xCE)); write_number(static_cast(j.m_value.number_integer)); } else if (j.m_value.number_unsigned <= (std::numeric_limits::max)()) { // uint 64 oa->write_character(to_char_type(0xCF)); write_number(static_cast(j.m_value.number_integer)); } break; } case value_t::number_float: { write_compact_float(j.m_value.number_float, detail::input_format_t::msgpack); break; } case value_t::string: { // step 1: write control byte and the string length const auto N = j.m_value.string->size(); if (N <= 31) { // fixstr write_number(static_cast(0xA0 | N)); } else if (N <= (std::numeric_limits::max)()) { // str 8 oa->write_character(to_char_type(0xD9)); write_number(static_cast(N)); } else if (N <= (std::numeric_limits::max)()) { // str 16 oa->write_character(to_char_type(0xDA)); write_number(static_cast(N)); } else if (N <= (std::numeric_limits::max)()) { // str 32 oa->write_character(to_char_type(0xDB)); write_number(static_cast(N)); } // step 2: write the string oa->write_characters( reinterpret_cast(j.m_value.string->c_str()), j.m_value.string->size()); break; } case value_t::array: { // step 1: write control byte and the array size const auto N = j.m_value.array->size(); if (N <= 15) { // fixarray write_number(static_cast(0x90 | N)); } else if (N <= (std::numeric_limits::max)()) { // array 16 oa->write_character(to_char_type(0xDC)); write_number(static_cast(N)); } else if (N <= (std::numeric_limits::max)()) { // array 32 oa->write_character(to_char_type(0xDD)); write_number(static_cast(N)); } // step 2: write each element for (const auto& el : *j.m_value.array) { write_msgpack(el); } break; } case value_t::binary: { // step 0: determine if the binary type has a set subtype to // determine whether or not to use the ext or fixext types const bool use_ext = j.m_value.binary->has_subtype(); // step 1: write control byte and the byte string length const auto N = j.m_value.binary->size(); if (N <= (std::numeric_limits::max)()) { std::uint8_t output_type{}; bool fixed = true; if (use_ext) { switch (N) { case 1: output_type = 0xD4; // fixext 1 break; case 2: output_type = 0xD5; // fixext 2 break; case 4: output_type = 0xD6; // fixext 4 break; case 8: output_type = 0xD7; // fixext 8 break; case 16: output_type = 0xD8; // fixext 16 break; default: output_type = 0xC7; // ext 8 fixed = false; break; } } else { output_type = 0xC4; // bin 8 fixed = false; } oa->write_character(to_char_type(output_type)); if (!fixed) { write_number(static_cast(N)); } } else if (N <= (std::numeric_limits::max)()) { std::uint8_t output_type = use_ext ? 0xC8 // ext 16 : 0xC5; // bin 16 oa->write_character(to_char_type(output_type)); write_number(static_cast(N)); } else if (N <= (std::numeric_limits::max)()) { std::uint8_t output_type = use_ext ? 0xC9 // ext 32 : 0xC6; // bin 32 oa->write_character(to_char_type(output_type)); write_number(static_cast(N)); } // step 1.5: if this is an ext type, write the subtype if (use_ext) { write_number(static_cast(j.m_value.binary->subtype())); } // step 2: write the byte string oa->write_characters( reinterpret_cast(j.m_value.binary->data()), N); break; } case value_t::object: { // step 1: write control byte and the object size const auto N = j.m_value.object->size(); if (N <= 15) { // fixmap write_number(static_cast(0x80 | (N & 0xF))); } else if (N <= (std::numeric_limits::max)()) { // map 16 oa->write_character(to_char_type(0xDE)); write_number(static_cast(N)); } else if (N <= (std::numeric_limits::max)()) { // map 32 oa->write_character(to_char_type(0xDF)); write_number(static_cast(N)); } // step 2: write each element for (const auto& el : *j.m_value.object) { write_msgpack(el.first); write_msgpack(el.second); } break; } default: break; } } /*! @param[in] j JSON value to serialize @param[in] use_count whether to use '#' prefixes (optimized format) @param[in] use_type whether to use '$' prefixes (optimized format) @param[in] add_prefix whether prefixes need to be used for this value */ void write_ubjson(const BasicJsonType& j, const bool use_count, const bool use_type, const bool add_prefix = true) { switch (j.type()) { case value_t::null: { if (add_prefix) { oa->write_character(to_char_type('Z')); } break; } case value_t::boolean: { if (add_prefix) { oa->write_character(j.m_value.boolean ? to_char_type('T') : to_char_type('F')); } break; } case value_t::number_integer: { write_number_with_ubjson_prefix(j.m_value.number_integer, add_prefix); break; } case value_t::number_unsigned: { write_number_with_ubjson_prefix(j.m_value.number_unsigned, add_prefix); break; } case value_t::number_float: { write_number_with_ubjson_prefix(j.m_value.number_float, add_prefix); break; } case value_t::string: { if (add_prefix) { oa->write_character(to_char_type('S')); } write_number_with_ubjson_prefix(j.m_value.string->size(), true); oa->write_characters( reinterpret_cast(j.m_value.string->c_str()), j.m_value.string->size()); break; } case value_t::array: { if (add_prefix) { oa->write_character(to_char_type('[')); } bool prefix_required = true; if (use_type && !j.m_value.array->empty()) { JSON_ASSERT(use_count); const CharType first_prefix = ubjson_prefix(j.front()); const bool same_prefix = std::all_of(j.begin() + 1, j.end(), [this, first_prefix](const BasicJsonType & v) { return ubjson_prefix(v) == first_prefix; }); if (same_prefix) { prefix_required = false; oa->write_character(to_char_type('$')); oa->write_character(first_prefix); } } if (use_count) { oa->write_character(to_char_type('#')); write_number_with_ubjson_prefix(j.m_value.array->size(), true); } for (const auto& el : *j.m_value.array) { write_ubjson(el, use_count, use_type, prefix_required); } if (!use_count) { oa->write_character(to_char_type(']')); } break; } case value_t::binary: { if (add_prefix) { oa->write_character(to_char_type('[')); } if (use_type && !j.m_value.binary->empty()) { JSON_ASSERT(use_count); oa->write_character(to_char_type('$')); oa->write_character('U'); } if (use_count) { oa->write_character(to_char_type('#')); write_number_with_ubjson_prefix(j.m_value.binary->size(), true); } if (use_type) { oa->write_characters( reinterpret_cast(j.m_value.binary->data()), j.m_value.binary->size()); } else { for (size_t i = 0; i < j.m_value.binary->size(); ++i) { oa->write_character(to_char_type('U')); oa->write_character(j.m_value.binary->data()[i]); } } if (!use_count) { oa->write_character(to_char_type(']')); } break; } case value_t::object: { if (add_prefix) { oa->write_character(to_char_type('{')); } bool prefix_required = true; if (use_type && !j.m_value.object->empty()) { JSON_ASSERT(use_count); const CharType first_prefix = ubjson_prefix(j.front()); const bool same_prefix = std::all_of(j.begin(), j.end(), [this, first_prefix](const BasicJsonType & v) { return ubjson_prefix(v) == first_prefix; }); if (same_prefix) { prefix_required = false; oa->write_character(to_char_type('$')); oa->write_character(first_prefix); } } if (use_count) { oa->write_character(to_char_type('#')); write_number_with_ubjson_prefix(j.m_value.object->size(), true); } for (const auto& el : *j.m_value.object) { write_number_with_ubjson_prefix(el.first.size(), true); oa->write_characters( reinterpret_cast(el.first.c_str()), el.first.size()); write_ubjson(el.second, use_count, use_type, prefix_required); } if (!use_count) { oa->write_character(to_char_type('}')); } break; } default: break; } } private: ////////// // BSON // ////////// /*! @return The size of a BSON document entry header, including the id marker and the entry name size (and its null-terminator). */ static std::size_t calc_bson_entry_header_size(const string_t& name) { const auto it = name.find(static_cast(0)); if (JSON_HEDLEY_UNLIKELY(it != BasicJsonType::string_t::npos)) { JSON_THROW(out_of_range::create(409, "BSON key cannot contain code point U+0000 (at byte " + std::to_string(it) + ")")); } return /*id*/ 1ul + name.size() + /*zero-terminator*/1u; } /*! @brief Writes the given @a element_type and @a name to the output adapter */ void write_bson_entry_header(const string_t& name, const std::uint8_t element_type) { oa->write_character(to_char_type(element_type)); // boolean oa->write_characters( reinterpret_cast(name.c_str()), name.size() + 1u); } /*! @brief Writes a BSON element with key @a name and boolean value @a value */ void write_bson_boolean(const string_t& name, const bool value) { write_bson_entry_header(name, 0x08); oa->write_character(value ? to_char_type(0x01) : to_char_type(0x00)); } /*! @brief Writes a BSON element with key @a name and double value @a value */ void write_bson_double(const string_t& name, const double value) { write_bson_entry_header(name, 0x01); write_number(value); } /*! @return The size of the BSON-encoded string in @a value */ static std::size_t calc_bson_string_size(const string_t& value) { return sizeof(std::int32_t) + value.size() + 1ul; } /*! @brief Writes a BSON element with key @a name and string value @a value */ void write_bson_string(const string_t& name, const string_t& value) { write_bson_entry_header(name, 0x02); write_number(static_cast(value.size() + 1ul)); oa->write_characters( reinterpret_cast(value.c_str()), value.size() + 1); } /*! @brief Writes a BSON element with key @a name and null value */ void write_bson_null(const string_t& name) { write_bson_entry_header(name, 0x0A); } /*! @return The size of the BSON-encoded integer @a value */ static std::size_t calc_bson_integer_size(const std::int64_t value) { return (std::numeric_limits::min)() <= value && value <= (std::numeric_limits::max)() ? sizeof(std::int32_t) : sizeof(std::int64_t); } /*! @brief Writes a BSON element with key @a name and integer @a value */ void write_bson_integer(const string_t& name, const std::int64_t value) { if ((std::numeric_limits::min)() <= value && value <= (std::numeric_limits::max)()) { write_bson_entry_header(name, 0x10); // int32 write_number(static_cast(value)); } else { write_bson_entry_header(name, 0x12); // int64 write_number(static_cast(value)); } } /*! @return The size of the BSON-encoded unsigned integer in @a j */ static constexpr std::size_t calc_bson_unsigned_size(const std::uint64_t value) noexcept { return (value <= static_cast((std::numeric_limits::max)())) ? sizeof(std::int32_t) : sizeof(std::int64_t); } /*! @brief Writes a BSON element with key @a name and unsigned @a value */ void write_bson_unsigned(const string_t& name, const std::uint64_t value) { if (value <= static_cast((std::numeric_limits::max)())) { write_bson_entry_header(name, 0x10 /* int32 */); write_number(static_cast(value)); } else if (value <= static_cast((std::numeric_limits::max)())) { write_bson_entry_header(name, 0x12 /* int64 */); write_number(static_cast(value)); } else { JSON_THROW(out_of_range::create(407, "integer number " + std::to_string(value) + " cannot be represented by BSON as it does not fit int64")); } } /*! @brief Writes a BSON element with key @a name and object @a value */ void write_bson_object_entry(const string_t& name, const typename BasicJsonType::object_t& value) { write_bson_entry_header(name, 0x03); // object write_bson_object(value); } /*! @return The size of the BSON-encoded array @a value */ static std::size_t calc_bson_array_size(const typename BasicJsonType::array_t& value) { std::size_t array_index = 0ul; const std::size_t embedded_document_size = std::accumulate(std::begin(value), std::end(value), std::size_t(0), [&array_index](std::size_t result, const typename BasicJsonType::array_t::value_type & el) { return result + calc_bson_element_size(std::to_string(array_index++), el); }); return sizeof(std::int32_t) + embedded_document_size + 1ul; } /*! @return The size of the BSON-encoded binary array @a value */ static std::size_t calc_bson_binary_size(const typename BasicJsonType::binary_t& value) { return sizeof(std::int32_t) + value.size() + 1ul; } /*! @brief Writes a BSON element with key @a name and array @a value */ void write_bson_array(const string_t& name, const typename BasicJsonType::array_t& value) { write_bson_entry_header(name, 0x04); // array write_number(static_cast(calc_bson_array_size(value))); std::size_t array_index = 0ul; for (const auto& el : value) { write_bson_element(std::to_string(array_index++), el); } oa->write_character(to_char_type(0x00)); } /*! @brief Writes a BSON element with key @a name and binary value @a value */ void write_bson_binary(const string_t& name, const binary_t& value) { write_bson_entry_header(name, 0x05); write_number(static_cast(value.size())); write_number(value.has_subtype() ? value.subtype() : std::uint8_t(0x00)); oa->write_characters(reinterpret_cast(value.data()), value.size()); } /*! @brief Calculates the size necessary to serialize the JSON value @a j with its @a name @return The calculated size for the BSON document entry for @a j with the given @a name. */ static std::size_t calc_bson_element_size(const string_t& name, const BasicJsonType& j) { const auto header_size = calc_bson_entry_header_size(name); switch (j.type()) { case value_t::object: return header_size + calc_bson_object_size(*j.m_value.object); case value_t::array: return header_size + calc_bson_array_size(*j.m_value.array); case value_t::binary: return header_size + calc_bson_binary_size(*j.m_value.binary); case value_t::boolean: return header_size + 1ul; case value_t::number_float: return header_size + 8ul; case value_t::number_integer: return header_size + calc_bson_integer_size(j.m_value.number_integer); case value_t::number_unsigned: return header_size + calc_bson_unsigned_size(j.m_value.number_unsigned); case value_t::string: return header_size + calc_bson_string_size(*j.m_value.string); case value_t::null: return header_size + 0ul; // LCOV_EXCL_START default: JSON_ASSERT(false); return 0ul; // LCOV_EXCL_STOP } } /*! @brief Serializes the JSON value @a j to BSON and associates it with the key @a name. @param name The name to associate with the JSON entity @a j within the current BSON document @return The size of the BSON entry */ void write_bson_element(const string_t& name, const BasicJsonType& j) { switch (j.type()) { case value_t::object: return write_bson_object_entry(name, *j.m_value.object); case value_t::array: return write_bson_array(name, *j.m_value.array); case value_t::binary: return write_bson_binary(name, *j.m_value.binary); case value_t::boolean: return write_bson_boolean(name, j.m_value.boolean); case value_t::number_float: return write_bson_double(name, j.m_value.number_float); case value_t::number_integer: return write_bson_integer(name, j.m_value.number_integer); case value_t::number_unsigned: return write_bson_unsigned(name, j.m_value.number_unsigned); case value_t::string: return write_bson_string(name, *j.m_value.string); case value_t::null: return write_bson_null(name); // LCOV_EXCL_START default: JSON_ASSERT(false); return; // LCOV_EXCL_STOP } } /*! @brief Calculates the size of the BSON serialization of the given JSON-object @a j. @param[in] j JSON value to serialize @pre j.type() == value_t::object */ static std::size_t calc_bson_object_size(const typename BasicJsonType::object_t& value) { std::size_t document_size = std::accumulate(value.begin(), value.end(), std::size_t(0), [](size_t result, const typename BasicJsonType::object_t::value_type & el) { return result += calc_bson_element_size(el.first, el.second); }); return sizeof(std::int32_t) + document_size + 1ul; } /*! @param[in] j JSON value to serialize @pre j.type() == value_t::object */ void write_bson_object(const typename BasicJsonType::object_t& value) { write_number(static_cast(calc_bson_object_size(value))); for (const auto& el : value) { write_bson_element(el.first, el.second); } oa->write_character(to_char_type(0x00)); } ////////// // CBOR // ////////// static constexpr CharType get_cbor_float_prefix(float /*unused*/) { return to_char_type(0xFA); // Single-Precision Float } static constexpr CharType get_cbor_float_prefix(double /*unused*/) { return to_char_type(0xFB); // Double-Precision Float } ///////////// // MsgPack // ///////////// static constexpr CharType get_msgpack_float_prefix(float /*unused*/) { return to_char_type(0xCA); // float 32 } static constexpr CharType get_msgpack_float_prefix(double /*unused*/) { return to_char_type(0xCB); // float 64 } //////////// // UBJSON // //////////// // UBJSON: write number (floating point) template::value, int>::type = 0> void write_number_with_ubjson_prefix(const NumberType n, const bool add_prefix) { if (add_prefix) { oa->write_character(get_ubjson_float_prefix(n)); } write_number(n); } // UBJSON: write number (unsigned integer) template::value, int>::type = 0> void write_number_with_ubjson_prefix(const NumberType n, const bool add_prefix) { if (n <= static_cast((std::numeric_limits::max)())) { if (add_prefix) { oa->write_character(to_char_type('i')); // int8 } write_number(static_cast(n)); } else if (n <= (std::numeric_limits::max)()) { if (add_prefix) { oa->write_character(to_char_type('U')); // uint8 } write_number(static_cast(n)); } else if (n <= static_cast((std::numeric_limits::max)())) { if (add_prefix) { oa->write_character(to_char_type('I')); // int16 } write_number(static_cast(n)); } else if (n <= static_cast((std::numeric_limits::max)())) { if (add_prefix) { oa->write_character(to_char_type('l')); // int32 } write_number(static_cast(n)); } else if (n <= static_cast((std::numeric_limits::max)())) { if (add_prefix) { oa->write_character(to_char_type('L')); // int64 } write_number(static_cast(n)); } else { if (add_prefix) { oa->write_character(to_char_type('H')); // high-precision number } const auto number = BasicJsonType(n).dump(); write_number_with_ubjson_prefix(number.size(), true); for (std::size_t i = 0; i < number.size(); ++i) { oa->write_character(to_char_type(static_cast(number[i]))); } } } // UBJSON: write number (signed integer) template < typename NumberType, typename std::enable_if < std::is_signed::value&& !std::is_floating_point::value, int >::type = 0 > void write_number_with_ubjson_prefix(const NumberType n, const bool add_prefix) { if ((std::numeric_limits::min)() <= n && n <= (std::numeric_limits::max)()) { if (add_prefix) { oa->write_character(to_char_type('i')); // int8 } write_number(static_cast(n)); } else if (static_cast((std::numeric_limits::min)()) <= n && n <= static_cast((std::numeric_limits::max)())) { if (add_prefix) { oa->write_character(to_char_type('U')); // uint8 } write_number(static_cast(n)); } else if ((std::numeric_limits::min)() <= n && n <= (std::numeric_limits::max)()) { if (add_prefix) { oa->write_character(to_char_type('I')); // int16 } write_number(static_cast(n)); } else if ((std::numeric_limits::min)() <= n && n <= (std::numeric_limits::max)()) { if (add_prefix) { oa->write_character(to_char_type('l')); // int32 } write_number(static_cast(n)); } else if ((std::numeric_limits::min)() <= n && n <= (std::numeric_limits::max)()) { if (add_prefix) { oa->write_character(to_char_type('L')); // int64 } write_number(static_cast(n)); } // LCOV_EXCL_START else { if (add_prefix) { oa->write_character(to_char_type('H')); // high-precision number } const auto number = BasicJsonType(n).dump(); write_number_with_ubjson_prefix(number.size(), true); for (std::size_t i = 0; i < number.size(); ++i) { oa->write_character(to_char_type(static_cast(number[i]))); } } // LCOV_EXCL_STOP } /*! @brief determine the type prefix of container values */ CharType ubjson_prefix(const BasicJsonType& j) const noexcept { switch (j.type()) { case value_t::null: return 'Z'; case value_t::boolean: return j.m_value.boolean ? 'T' : 'F'; case value_t::number_integer: { if ((std::numeric_limits::min)() <= j.m_value.number_integer && j.m_value.number_integer <= (std::numeric_limits::max)()) { return 'i'; } if ((std::numeric_limits::min)() <= j.m_value.number_integer && j.m_value.number_integer <= (std::numeric_limits::max)()) { return 'U'; } if ((std::numeric_limits::min)() <= j.m_value.number_integer && j.m_value.number_integer <= (std::numeric_limits::max)()) { return 'I'; } if ((std::numeric_limits::min)() <= j.m_value.number_integer && j.m_value.number_integer <= (std::numeric_limits::max)()) { return 'l'; } if ((std::numeric_limits::min)() <= j.m_value.number_integer && j.m_value.number_integer <= (std::numeric_limits::max)()) { return 'L'; } // anything else is treated as high-precision number return 'H'; // LCOV_EXCL_LINE } case value_t::number_unsigned: { if (j.m_value.number_unsigned <= static_cast((std::numeric_limits::max)())) { return 'i'; } if (j.m_value.number_unsigned <= static_cast((std::numeric_limits::max)())) { return 'U'; } if (j.m_value.number_unsigned <= static_cast((std::numeric_limits::max)())) { return 'I'; } if (j.m_value.number_unsigned <= static_cast((std::numeric_limits::max)())) { return 'l'; } if (j.m_value.number_unsigned <= static_cast((std::numeric_limits::max)())) { return 'L'; } // anything else is treated as high-precision number return 'H'; // LCOV_EXCL_LINE } case value_t::number_float: return get_ubjson_float_prefix(j.m_value.number_float); case value_t::string: return 'S'; case value_t::array: // fallthrough case value_t::binary: return '['; case value_t::object: return '{'; default: // discarded values return 'N'; } } static constexpr CharType get_ubjson_float_prefix(float /*unused*/) { return 'd'; // float 32 } static constexpr CharType get_ubjson_float_prefix(double /*unused*/) { return 'D'; // float 64 } /////////////////////// // Utility functions // /////////////////////// /* @brief write a number to output input @param[in] n number of type @a NumberType @tparam NumberType the type of the number @tparam OutputIsLittleEndian Set to true if output data is required to be little endian @note This function needs to respect the system's endianess, because bytes in CBOR, MessagePack, and UBJSON are stored in network order (big endian) and therefore need reordering on little endian systems. */ template void write_number(const NumberType n) { // step 1: write number to array of length NumberType std::array vec; std::memcpy(vec.data(), &n, sizeof(NumberType)); // step 2: write array to output (with possible reordering) if (is_little_endian != OutputIsLittleEndian) { // reverse byte order prior to conversion if necessary std::reverse(vec.begin(), vec.end()); } oa->write_characters(vec.data(), sizeof(NumberType)); } void write_compact_float(const number_float_t n, detail::input_format_t format) { if (static_cast(n) >= static_cast(std::numeric_limits::lowest()) && static_cast(n) <= static_cast((std::numeric_limits::max)()) && static_cast(static_cast(n)) == static_cast(n)) { oa->write_character(format == detail::input_format_t::cbor ? get_cbor_float_prefix(static_cast(n)) : get_msgpack_float_prefix(static_cast(n))); write_number(static_cast(n)); } else { oa->write_character(format == detail::input_format_t::cbor ? get_cbor_float_prefix(n) : get_msgpack_float_prefix(n)); write_number(n); } } public: // The following to_char_type functions are implement the conversion // between uint8_t and CharType. In case CharType is not unsigned, // such a conversion is required to allow values greater than 128. // See for a discussion. template < typename C = CharType, enable_if_t < std::is_signed::value && std::is_signed::value > * = nullptr > static constexpr CharType to_char_type(std::uint8_t x) noexcept { return *reinterpret_cast(&x); } template < typename C = CharType, enable_if_t < std::is_signed::value && std::is_unsigned::value > * = nullptr > static CharType to_char_type(std::uint8_t x) noexcept { static_assert(sizeof(std::uint8_t) == sizeof(CharType), "size of CharType must be equal to std::uint8_t"); static_assert(std::is_trivial::value, "CharType must be trivial"); CharType result; std::memcpy(&result, &x, sizeof(x)); return result; } template::value>* = nullptr> static constexpr CharType to_char_type(std::uint8_t x) noexcept { return x; } template < typename InputCharType, typename C = CharType, enable_if_t < std::is_signed::value && std::is_signed::value && std::is_same::type>::value > * = nullptr > static constexpr CharType to_char_type(InputCharType x) noexcept { return x; } private: /// whether we can assume little endianess const bool is_little_endian = little_endianess(); /// the output output_adapter_t oa = nullptr; }; } // namespace detail } // namespace nlohmann // #include // #include #include // reverse, remove, fill, find, none_of #include // array #include // localeconv, lconv #include // labs, isfinite, isnan, signbit #include // size_t, ptrdiff_t #include // uint8_t #include // snprintf #include // numeric_limits #include // string, char_traits #include // is_same #include // move // #include #include // array #include // signbit, isfinite #include // intN_t, uintN_t #include // memcpy, memmove #include // numeric_limits #include // conditional // #include namespace nlohmann { namespace detail { /*! @brief implements the Grisu2 algorithm for binary to decimal floating-point conversion. This implementation is a slightly modified version of the reference implementation which may be obtained from http://florian.loitsch.com/publications (bench.tar.gz). The code is distributed under the MIT license, Copyright (c) 2009 Florian Loitsch. For a detailed description of the algorithm see: [1] Loitsch, "Printing Floating-Point Numbers Quickly and Accurately with Integers", Proceedings of the ACM SIGPLAN 2010 Conference on Programming Language Design and Implementation, PLDI 2010 [2] Burger, Dybvig, "Printing Floating-Point Numbers Quickly and Accurately", Proceedings of the ACM SIGPLAN 1996 Conference on Programming Language Design and Implementation, PLDI 1996 */ namespace dtoa_impl { template Target reinterpret_bits(const Source source) { static_assert(sizeof(Target) == sizeof(Source), "size mismatch"); Target target; std::memcpy(&target, &source, sizeof(Source)); return target; } struct diyfp // f * 2^e { static constexpr int kPrecision = 64; // = q std::uint64_t f = 0; int e = 0; constexpr diyfp(std::uint64_t f_, int e_) noexcept : f(f_), e(e_) {} /*! @brief returns x - y @pre x.e == y.e and x.f >= y.f */ static diyfp sub(const diyfp& x, const diyfp& y) noexcept { JSON_ASSERT(x.e == y.e); JSON_ASSERT(x.f >= y.f); return {x.f - y.f, x.e}; } /*! @brief returns x * y @note The result is rounded. (Only the upper q bits are returned.) */ static diyfp mul(const diyfp& x, const diyfp& y) noexcept { static_assert(kPrecision == 64, "internal error"); // Computes: // f = round((x.f * y.f) / 2^q) // e = x.e + y.e + q // Emulate the 64-bit * 64-bit multiplication: // // p = u * v // = (u_lo + 2^32 u_hi) (v_lo + 2^32 v_hi) // = (u_lo v_lo ) + 2^32 ((u_lo v_hi ) + (u_hi v_lo )) + 2^64 (u_hi v_hi ) // = (p0 ) + 2^32 ((p1 ) + (p2 )) + 2^64 (p3 ) // = (p0_lo + 2^32 p0_hi) + 2^32 ((p1_lo + 2^32 p1_hi) + (p2_lo + 2^32 p2_hi)) + 2^64 (p3 ) // = (p0_lo ) + 2^32 (p0_hi + p1_lo + p2_lo ) + 2^64 (p1_hi + p2_hi + p3) // = (p0_lo ) + 2^32 (Q ) + 2^64 (H ) // = (p0_lo ) + 2^32 (Q_lo + 2^32 Q_hi ) + 2^64 (H ) // // (Since Q might be larger than 2^32 - 1) // // = (p0_lo + 2^32 Q_lo) + 2^64 (Q_hi + H) // // (Q_hi + H does not overflow a 64-bit int) // // = p_lo + 2^64 p_hi const std::uint64_t u_lo = x.f & 0xFFFFFFFFu; const std::uint64_t u_hi = x.f >> 32u; const std::uint64_t v_lo = y.f & 0xFFFFFFFFu; const std::uint64_t v_hi = y.f >> 32u; const std::uint64_t p0 = u_lo * v_lo; const std::uint64_t p1 = u_lo * v_hi; const std::uint64_t p2 = u_hi * v_lo; const std::uint64_t p3 = u_hi * v_hi; const std::uint64_t p0_hi = p0 >> 32u; const std::uint64_t p1_lo = p1 & 0xFFFFFFFFu; const std::uint64_t p1_hi = p1 >> 32u; const std::uint64_t p2_lo = p2 & 0xFFFFFFFFu; const std::uint64_t p2_hi = p2 >> 32u; std::uint64_t Q = p0_hi + p1_lo + p2_lo; // The full product might now be computed as // // p_hi = p3 + p2_hi + p1_hi + (Q >> 32) // p_lo = p0_lo + (Q << 32) // // But in this particular case here, the full p_lo is not required. // Effectively we only need to add the highest bit in p_lo to p_hi (and // Q_hi + 1 does not overflow). Q += std::uint64_t{1} << (64u - 32u - 1u); // round, ties up const std::uint64_t h = p3 + p2_hi + p1_hi + (Q >> 32u); return {h, x.e + y.e + 64}; } /*! @brief normalize x such that the significand is >= 2^(q-1) @pre x.f != 0 */ static diyfp normalize(diyfp x) noexcept { JSON_ASSERT(x.f != 0); while ((x.f >> 63u) == 0) { x.f <<= 1u; x.e--; } return x; } /*! @brief normalize x such that the result has the exponent E @pre e >= x.e and the upper e - x.e bits of x.f must be zero. */ static diyfp normalize_to(const diyfp& x, const int target_exponent) noexcept { const int delta = x.e - target_exponent; JSON_ASSERT(delta >= 0); JSON_ASSERT(((x.f << delta) >> delta) == x.f); return {x.f << delta, target_exponent}; } }; struct boundaries { diyfp w; diyfp minus; diyfp plus; }; /*! Compute the (normalized) diyfp representing the input number 'value' and its boundaries. @pre value must be finite and positive */ template boundaries compute_boundaries(FloatType value) { JSON_ASSERT(std::isfinite(value)); JSON_ASSERT(value > 0); // Convert the IEEE representation into a diyfp. // // If v is denormal: // value = 0.F * 2^(1 - bias) = ( F) * 2^(1 - bias - (p-1)) // If v is normalized: // value = 1.F * 2^(E - bias) = (2^(p-1) + F) * 2^(E - bias - (p-1)) static_assert(std::numeric_limits::is_iec559, "internal error: dtoa_short requires an IEEE-754 floating-point implementation"); constexpr int kPrecision = std::numeric_limits::digits; // = p (includes the hidden bit) constexpr int kBias = std::numeric_limits::max_exponent - 1 + (kPrecision - 1); constexpr int kMinExp = 1 - kBias; constexpr std::uint64_t kHiddenBit = std::uint64_t{1} << (kPrecision - 1); // = 2^(p-1) using bits_type = typename std::conditional::type; const std::uint64_t bits = reinterpret_bits(value); const std::uint64_t E = bits >> (kPrecision - 1); const std::uint64_t F = bits & (kHiddenBit - 1); const bool is_denormal = E == 0; const diyfp v = is_denormal ? diyfp(F, kMinExp) : diyfp(F + kHiddenBit, static_cast(E) - kBias); // Compute the boundaries m- and m+ of the floating-point value // v = f * 2^e. // // Determine v- and v+, the floating-point predecessor and successor if v, // respectively. // // v- = v - 2^e if f != 2^(p-1) or e == e_min (A) // = v - 2^(e-1) if f == 2^(p-1) and e > e_min (B) // // v+ = v + 2^e // // Let m- = (v- + v) / 2 and m+ = (v + v+) / 2. All real numbers _strictly_ // between m- and m+ round to v, regardless of how the input rounding // algorithm breaks ties. // // ---+-------------+-------------+-------------+-------------+--- (A) // v- m- v m+ v+ // // -----------------+------+------+-------------+-------------+--- (B) // v- m- v m+ v+ const bool lower_boundary_is_closer = F == 0 && E > 1; const diyfp m_plus = diyfp(2 * v.f + 1, v.e - 1); const diyfp m_minus = lower_boundary_is_closer ? diyfp(4 * v.f - 1, v.e - 2) // (B) : diyfp(2 * v.f - 1, v.e - 1); // (A) // Determine the normalized w+ = m+. const diyfp w_plus = diyfp::normalize(m_plus); // Determine w- = m- such that e_(w-) = e_(w+). const diyfp w_minus = diyfp::normalize_to(m_minus, w_plus.e); return {diyfp::normalize(v), w_minus, w_plus}; } // Given normalized diyfp w, Grisu needs to find a (normalized) cached // power-of-ten c, such that the exponent of the product c * w = f * 2^e lies // within a certain range [alpha, gamma] (Definition 3.2 from [1]) // // alpha <= e = e_c + e_w + q <= gamma // // or // // f_c * f_w * 2^alpha <= f_c 2^(e_c) * f_w 2^(e_w) * 2^q // <= f_c * f_w * 2^gamma // // Since c and w are normalized, i.e. 2^(q-1) <= f < 2^q, this implies // // 2^(q-1) * 2^(q-1) * 2^alpha <= c * w * 2^q < 2^q * 2^q * 2^gamma // // or // // 2^(q - 2 + alpha) <= c * w < 2^(q + gamma) // // The choice of (alpha,gamma) determines the size of the table and the form of // the digit generation procedure. Using (alpha,gamma)=(-60,-32) works out well // in practice: // // The idea is to cut the number c * w = f * 2^e into two parts, which can be // processed independently: An integral part p1, and a fractional part p2: // // f * 2^e = ( (f div 2^-e) * 2^-e + (f mod 2^-e) ) * 2^e // = (f div 2^-e) + (f mod 2^-e) * 2^e // = p1 + p2 * 2^e // // The conversion of p1 into decimal form requires a series of divisions and // modulos by (a power of) 10. These operations are faster for 32-bit than for // 64-bit integers, so p1 should ideally fit into a 32-bit integer. This can be // achieved by choosing // // -e >= 32 or e <= -32 := gamma // // In order to convert the fractional part // // p2 * 2^e = p2 / 2^-e = d[-1] / 10^1 + d[-2] / 10^2 + ... // // into decimal form, the fraction is repeatedly multiplied by 10 and the digits // d[-i] are extracted in order: // // (10 * p2) div 2^-e = d[-1] // (10 * p2) mod 2^-e = d[-2] / 10^1 + ... // // The multiplication by 10 must not overflow. It is sufficient to choose // // 10 * p2 < 16 * p2 = 2^4 * p2 <= 2^64. // // Since p2 = f mod 2^-e < 2^-e, // // -e <= 60 or e >= -60 := alpha constexpr int kAlpha = -60; constexpr int kGamma = -32; struct cached_power // c = f * 2^e ~= 10^k { std::uint64_t f; int e; int k; }; /*! For a normalized diyfp w = f * 2^e, this function returns a (normalized) cached power-of-ten c = f_c * 2^e_c, such that the exponent of the product w * c satisfies (Definition 3.2 from [1]) alpha <= e_c + e + q <= gamma. */ inline cached_power get_cached_power_for_binary_exponent(int e) { // Now // // alpha <= e_c + e + q <= gamma (1) // ==> f_c * 2^alpha <= c * 2^e * 2^q // // and since the c's are normalized, 2^(q-1) <= f_c, // // ==> 2^(q - 1 + alpha) <= c * 2^(e + q) // ==> 2^(alpha - e - 1) <= c // // If c were an exact power of ten, i.e. c = 10^k, one may determine k as // // k = ceil( log_10( 2^(alpha - e - 1) ) ) // = ceil( (alpha - e - 1) * log_10(2) ) // // From the paper: // "In theory the result of the procedure could be wrong since c is rounded, // and the computation itself is approximated [...]. In practice, however, // this simple function is sufficient." // // For IEEE double precision floating-point numbers converted into // normalized diyfp's w = f * 2^e, with q = 64, // // e >= -1022 (min IEEE exponent) // -52 (p - 1) // -52 (p - 1, possibly normalize denormal IEEE numbers) // -11 (normalize the diyfp) // = -1137 // // and // // e <= +1023 (max IEEE exponent) // -52 (p - 1) // -11 (normalize the diyfp) // = 960 // // This binary exponent range [-1137,960] results in a decimal exponent // range [-307,324]. One does not need to store a cached power for each // k in this range. For each such k it suffices to find a cached power // such that the exponent of the product lies in [alpha,gamma]. // This implies that the difference of the decimal exponents of adjacent // table entries must be less than or equal to // // floor( (gamma - alpha) * log_10(2) ) = 8. // // (A smaller distance gamma-alpha would require a larger table.) // NB: // Actually this function returns c, such that -60 <= e_c + e + 64 <= -34. constexpr int kCachedPowersMinDecExp = -300; constexpr int kCachedPowersDecStep = 8; static constexpr std::array kCachedPowers = { { { 0xAB70FE17C79AC6CA, -1060, -300 }, { 0xFF77B1FCBEBCDC4F, -1034, -292 }, { 0xBE5691EF416BD60C, -1007, -284 }, { 0x8DD01FAD907FFC3C, -980, -276 }, { 0xD3515C2831559A83, -954, -268 }, { 0x9D71AC8FADA6C9B5, -927, -260 }, { 0xEA9C227723EE8BCB, -901, -252 }, { 0xAECC49914078536D, -874, -244 }, { 0x823C12795DB6CE57, -847, -236 }, { 0xC21094364DFB5637, -821, -228 }, { 0x9096EA6F3848984F, -794, -220 }, { 0xD77485CB25823AC7, -768, -212 }, { 0xA086CFCD97BF97F4, -741, -204 }, { 0xEF340A98172AACE5, -715, -196 }, { 0xB23867FB2A35B28E, -688, -188 }, { 0x84C8D4DFD2C63F3B, -661, -180 }, { 0xC5DD44271AD3CDBA, -635, -172 }, { 0x936B9FCEBB25C996, -608, -164 }, { 0xDBAC6C247D62A584, -582, -156 }, { 0xA3AB66580D5FDAF6, -555, -148 }, { 0xF3E2F893DEC3F126, -529, -140 }, { 0xB5B5ADA8AAFF80B8, -502, -132 }, { 0x87625F056C7C4A8B, -475, -124 }, { 0xC9BCFF6034C13053, -449, -116 }, { 0x964E858C91BA2655, -422, -108 }, { 0xDFF9772470297EBD, -396, -100 }, { 0xA6DFBD9FB8E5B88F, -369, -92 }, { 0xF8A95FCF88747D94, -343, -84 }, { 0xB94470938FA89BCF, -316, -76 }, { 0x8A08F0F8BF0F156B, -289, -68 }, { 0xCDB02555653131B6, -263, -60 }, { 0x993FE2C6D07B7FAC, -236, -52 }, { 0xE45C10C42A2B3B06, -210, -44 }, { 0xAA242499697392D3, -183, -36 }, { 0xFD87B5F28300CA0E, -157, -28 }, { 0xBCE5086492111AEB, -130, -20 }, { 0x8CBCCC096F5088CC, -103, -12 }, { 0xD1B71758E219652C, -77, -4 }, { 0x9C40000000000000, -50, 4 }, { 0xE8D4A51000000000, -24, 12 }, { 0xAD78EBC5AC620000, 3, 20 }, { 0x813F3978F8940984, 30, 28 }, { 0xC097CE7BC90715B3, 56, 36 }, { 0x8F7E32CE7BEA5C70, 83, 44 }, { 0xD5D238A4ABE98068, 109, 52 }, { 0x9F4F2726179A2245, 136, 60 }, { 0xED63A231D4C4FB27, 162, 68 }, { 0xB0DE65388CC8ADA8, 189, 76 }, { 0x83C7088E1AAB65DB, 216, 84 }, { 0xC45D1DF942711D9A, 242, 92 }, { 0x924D692CA61BE758, 269, 100 }, { 0xDA01EE641A708DEA, 295, 108 }, { 0xA26DA3999AEF774A, 322, 116 }, { 0xF209787BB47D6B85, 348, 124 }, { 0xB454E4A179DD1877, 375, 132 }, { 0x865B86925B9BC5C2, 402, 140 }, { 0xC83553C5C8965D3D, 428, 148 }, { 0x952AB45CFA97A0B3, 455, 156 }, { 0xDE469FBD99A05FE3, 481, 164 }, { 0xA59BC234DB398C25, 508, 172 }, { 0xF6C69A72A3989F5C, 534, 180 }, { 0xB7DCBF5354E9BECE, 561, 188 }, { 0x88FCF317F22241E2, 588, 196 }, { 0xCC20CE9BD35C78A5, 614, 204 }, { 0x98165AF37B2153DF, 641, 212 }, { 0xE2A0B5DC971F303A, 667, 220 }, { 0xA8D9D1535CE3B396, 694, 228 }, { 0xFB9B7CD9A4A7443C, 720, 236 }, { 0xBB764C4CA7A44410, 747, 244 }, { 0x8BAB8EEFB6409C1A, 774, 252 }, { 0xD01FEF10A657842C, 800, 260 }, { 0x9B10A4E5E9913129, 827, 268 }, { 0xE7109BFBA19C0C9D, 853, 276 }, { 0xAC2820D9623BF429, 880, 284 }, { 0x80444B5E7AA7CF85, 907, 292 }, { 0xBF21E44003ACDD2D, 933, 300 }, { 0x8E679C2F5E44FF8F, 960, 308 }, { 0xD433179D9C8CB841, 986, 316 }, { 0x9E19DB92B4E31BA9, 1013, 324 }, } }; // This computation gives exactly the same results for k as // k = ceil((kAlpha - e - 1) * 0.30102999566398114) // for |e| <= 1500, but doesn't require floating-point operations. // NB: log_10(2) ~= 78913 / 2^18 JSON_ASSERT(e >= -1500); JSON_ASSERT(e <= 1500); const int f = kAlpha - e - 1; const int k = (f * 78913) / (1 << 18) + static_cast(f > 0); const int index = (-kCachedPowersMinDecExp + k + (kCachedPowersDecStep - 1)) / kCachedPowersDecStep; JSON_ASSERT(index >= 0); JSON_ASSERT(static_cast(index) < kCachedPowers.size()); const cached_power cached = kCachedPowers[static_cast(index)]; JSON_ASSERT(kAlpha <= cached.e + e + 64); JSON_ASSERT(kGamma >= cached.e + e + 64); return cached; } /*! For n != 0, returns k, such that pow10 := 10^(k-1) <= n < 10^k. For n == 0, returns 1 and sets pow10 := 1. */ inline int find_largest_pow10(const std::uint32_t n, std::uint32_t& pow10) { // LCOV_EXCL_START if (n >= 1000000000) { pow10 = 1000000000; return 10; } // LCOV_EXCL_STOP else if (n >= 100000000) { pow10 = 100000000; return 9; } else if (n >= 10000000) { pow10 = 10000000; return 8; } else if (n >= 1000000) { pow10 = 1000000; return 7; } else if (n >= 100000) { pow10 = 100000; return 6; } else if (n >= 10000) { pow10 = 10000; return 5; } else if (n >= 1000) { pow10 = 1000; return 4; } else if (n >= 100) { pow10 = 100; return 3; } else if (n >= 10) { pow10 = 10; return 2; } else { pow10 = 1; return 1; } } inline void grisu2_round(char* buf, int len, std::uint64_t dist, std::uint64_t delta, std::uint64_t rest, std::uint64_t ten_k) { JSON_ASSERT(len >= 1); JSON_ASSERT(dist <= delta); JSON_ASSERT(rest <= delta); JSON_ASSERT(ten_k > 0); // <--------------------------- delta ----> // <---- dist ---------> // --------------[------------------+-------------------]-------------- // M- w M+ // // ten_k // <------> // <---- rest ----> // --------------[------------------+----+--------------]-------------- // w V // = buf * 10^k // // ten_k represents a unit-in-the-last-place in the decimal representation // stored in buf. // Decrement buf by ten_k while this takes buf closer to w. // The tests are written in this order to avoid overflow in unsigned // integer arithmetic. while (rest < dist && delta - rest >= ten_k && (rest + ten_k < dist || dist - rest > rest + ten_k - dist)) { JSON_ASSERT(buf[len - 1] != '0'); buf[len - 1]--; rest += ten_k; } } /*! Generates V = buffer * 10^decimal_exponent, such that M- <= V <= M+. M- and M+ must be normalized and share the same exponent -60 <= e <= -32. */ inline void grisu2_digit_gen(char* buffer, int& length, int& decimal_exponent, diyfp M_minus, diyfp w, diyfp M_plus) { static_assert(kAlpha >= -60, "internal error"); static_assert(kGamma <= -32, "internal error"); // Generates the digits (and the exponent) of a decimal floating-point // number V = buffer * 10^decimal_exponent in the range [M-, M+]. The diyfp's // w, M- and M+ share the same exponent e, which satisfies alpha <= e <= gamma. // // <--------------------------- delta ----> // <---- dist ---------> // --------------[------------------+-------------------]-------------- // M- w M+ // // Grisu2 generates the digits of M+ from left to right and stops as soon as // V is in [M-,M+]. JSON_ASSERT(M_plus.e >= kAlpha); JSON_ASSERT(M_plus.e <= kGamma); std::uint64_t delta = diyfp::sub(M_plus, M_minus).f; // (significand of (M+ - M-), implicit exponent is e) std::uint64_t dist = diyfp::sub(M_plus, w ).f; // (significand of (M+ - w ), implicit exponent is e) // Split M+ = f * 2^e into two parts p1 and p2 (note: e < 0): // // M+ = f * 2^e // = ((f div 2^-e) * 2^-e + (f mod 2^-e)) * 2^e // = ((p1 ) * 2^-e + (p2 )) * 2^e // = p1 + p2 * 2^e const diyfp one(std::uint64_t{1} << -M_plus.e, M_plus.e); auto p1 = static_cast(M_plus.f >> -one.e); // p1 = f div 2^-e (Since -e >= 32, p1 fits into a 32-bit int.) std::uint64_t p2 = M_plus.f & (one.f - 1); // p2 = f mod 2^-e // 1) // // Generate the digits of the integral part p1 = d[n-1]...d[1]d[0] JSON_ASSERT(p1 > 0); std::uint32_t pow10; const int k = find_largest_pow10(p1, pow10); // 10^(k-1) <= p1 < 10^k, pow10 = 10^(k-1) // // p1 = (p1 div 10^(k-1)) * 10^(k-1) + (p1 mod 10^(k-1)) // = (d[k-1] ) * 10^(k-1) + (p1 mod 10^(k-1)) // // M+ = p1 + p2 * 2^e // = d[k-1] * 10^(k-1) + (p1 mod 10^(k-1)) + p2 * 2^e // = d[k-1] * 10^(k-1) + ((p1 mod 10^(k-1)) * 2^-e + p2) * 2^e // = d[k-1] * 10^(k-1) + ( rest) * 2^e // // Now generate the digits d[n] of p1 from left to right (n = k-1,...,0) // // p1 = d[k-1]...d[n] * 10^n + d[n-1]...d[0] // // but stop as soon as // // rest * 2^e = (d[n-1]...d[0] * 2^-e + p2) * 2^e <= delta * 2^e int n = k; while (n > 0) { // Invariants: // M+ = buffer * 10^n + (p1 + p2 * 2^e) (buffer = 0 for n = k) // pow10 = 10^(n-1) <= p1 < 10^n // const std::uint32_t d = p1 / pow10; // d = p1 div 10^(n-1) const std::uint32_t r = p1 % pow10; // r = p1 mod 10^(n-1) // // M+ = buffer * 10^n + (d * 10^(n-1) + r) + p2 * 2^e // = (buffer * 10 + d) * 10^(n-1) + (r + p2 * 2^e) // JSON_ASSERT(d <= 9); buffer[length++] = static_cast('0' + d); // buffer := buffer * 10 + d // // M+ = buffer * 10^(n-1) + (r + p2 * 2^e) // p1 = r; n--; // // M+ = buffer * 10^n + (p1 + p2 * 2^e) // pow10 = 10^n // // Now check if enough digits have been generated. // Compute // // p1 + p2 * 2^e = (p1 * 2^-e + p2) * 2^e = rest * 2^e // // Note: // Since rest and delta share the same exponent e, it suffices to // compare the significands. const std::uint64_t rest = (std::uint64_t{p1} << -one.e) + p2; if (rest <= delta) { // V = buffer * 10^n, with M- <= V <= M+. decimal_exponent += n; // We may now just stop. But instead look if the buffer could be // decremented to bring V closer to w. // // pow10 = 10^n is now 1 ulp in the decimal representation V. // The rounding procedure works with diyfp's with an implicit // exponent of e. // // 10^n = (10^n * 2^-e) * 2^e = ulp * 2^e // const std::uint64_t ten_n = std::uint64_t{pow10} << -one.e; grisu2_round(buffer, length, dist, delta, rest, ten_n); return; } pow10 /= 10; // // pow10 = 10^(n-1) <= p1 < 10^n // Invariants restored. } // 2) // // The digits of the integral part have been generated: // // M+ = d[k-1]...d[1]d[0] + p2 * 2^e // = buffer + p2 * 2^e // // Now generate the digits of the fractional part p2 * 2^e. // // Note: // No decimal point is generated: the exponent is adjusted instead. // // p2 actually represents the fraction // // p2 * 2^e // = p2 / 2^-e // = d[-1] / 10^1 + d[-2] / 10^2 + ... // // Now generate the digits d[-m] of p1 from left to right (m = 1,2,...) // // p2 * 2^e = d[-1]d[-2]...d[-m] * 10^-m // + 10^-m * (d[-m-1] / 10^1 + d[-m-2] / 10^2 + ...) // // using // // 10^m * p2 = ((10^m * p2) div 2^-e) * 2^-e + ((10^m * p2) mod 2^-e) // = ( d) * 2^-e + ( r) // // or // 10^m * p2 * 2^e = d + r * 2^e // // i.e. // // M+ = buffer + p2 * 2^e // = buffer + 10^-m * (d + r * 2^e) // = (buffer * 10^m + d) * 10^-m + 10^-m * r * 2^e // // and stop as soon as 10^-m * r * 2^e <= delta * 2^e JSON_ASSERT(p2 > delta); int m = 0; for (;;) { // Invariant: // M+ = buffer * 10^-m + 10^-m * (d[-m-1] / 10 + d[-m-2] / 10^2 + ...) * 2^e // = buffer * 10^-m + 10^-m * (p2 ) * 2^e // = buffer * 10^-m + 10^-m * (1/10 * (10 * p2) ) * 2^e // = buffer * 10^-m + 10^-m * (1/10 * ((10*p2 div 2^-e) * 2^-e + (10*p2 mod 2^-e)) * 2^e // JSON_ASSERT(p2 <= (std::numeric_limits::max)() / 10); p2 *= 10; const std::uint64_t d = p2 >> -one.e; // d = (10 * p2) div 2^-e const std::uint64_t r = p2 & (one.f - 1); // r = (10 * p2) mod 2^-e // // M+ = buffer * 10^-m + 10^-m * (1/10 * (d * 2^-e + r) * 2^e // = buffer * 10^-m + 10^-m * (1/10 * (d + r * 2^e)) // = (buffer * 10 + d) * 10^(-m-1) + 10^(-m-1) * r * 2^e // JSON_ASSERT(d <= 9); buffer[length++] = static_cast('0' + d); // buffer := buffer * 10 + d // // M+ = buffer * 10^(-m-1) + 10^(-m-1) * r * 2^e // p2 = r; m++; // // M+ = buffer * 10^-m + 10^-m * p2 * 2^e // Invariant restored. // Check if enough digits have been generated. // // 10^-m * p2 * 2^e <= delta * 2^e // p2 * 2^e <= 10^m * delta * 2^e // p2 <= 10^m * delta delta *= 10; dist *= 10; if (p2 <= delta) { break; } } // V = buffer * 10^-m, with M- <= V <= M+. decimal_exponent -= m; // 1 ulp in the decimal representation is now 10^-m. // Since delta and dist are now scaled by 10^m, we need to do the // same with ulp in order to keep the units in sync. // // 10^m * 10^-m = 1 = 2^-e * 2^e = ten_m * 2^e // const std::uint64_t ten_m = one.f; grisu2_round(buffer, length, dist, delta, p2, ten_m); // By construction this algorithm generates the shortest possible decimal // number (Loitsch, Theorem 6.2) which rounds back to w. // For an input number of precision p, at least // // N = 1 + ceil(p * log_10(2)) // // decimal digits are sufficient to identify all binary floating-point // numbers (Matula, "In-and-Out conversions"). // This implies that the algorithm does not produce more than N decimal // digits. // // N = 17 for p = 53 (IEEE double precision) // N = 9 for p = 24 (IEEE single precision) } /*! v = buf * 10^decimal_exponent len is the length of the buffer (number of decimal digits) The buffer must be large enough, i.e. >= max_digits10. */ JSON_HEDLEY_NON_NULL(1) inline void grisu2(char* buf, int& len, int& decimal_exponent, diyfp m_minus, diyfp v, diyfp m_plus) { JSON_ASSERT(m_plus.e == m_minus.e); JSON_ASSERT(m_plus.e == v.e); // --------(-----------------------+-----------------------)-------- (A) // m- v m+ // // --------------------(-----------+-----------------------)-------- (B) // m- v m+ // // First scale v (and m- and m+) such that the exponent is in the range // [alpha, gamma]. const cached_power cached = get_cached_power_for_binary_exponent(m_plus.e); const diyfp c_minus_k(cached.f, cached.e); // = c ~= 10^-k // The exponent of the products is = v.e + c_minus_k.e + q and is in the range [alpha,gamma] const diyfp w = diyfp::mul(v, c_minus_k); const diyfp w_minus = diyfp::mul(m_minus, c_minus_k); const diyfp w_plus = diyfp::mul(m_plus, c_minus_k); // ----(---+---)---------------(---+---)---------------(---+---)---- // w- w w+ // = c*m- = c*v = c*m+ // // diyfp::mul rounds its result and c_minus_k is approximated too. w, w- and // w+ are now off by a small amount. // In fact: // // w - v * 10^k < 1 ulp // // To account for this inaccuracy, add resp. subtract 1 ulp. // // --------+---[---------------(---+---)---------------]---+-------- // w- M- w M+ w+ // // Now any number in [M-, M+] (bounds included) will round to w when input, // regardless of how the input rounding algorithm breaks ties. // // And digit_gen generates the shortest possible such number in [M-, M+]. // Note that this does not mean that Grisu2 always generates the shortest // possible number in the interval (m-, m+). const diyfp M_minus(w_minus.f + 1, w_minus.e); const diyfp M_plus (w_plus.f - 1, w_plus.e ); decimal_exponent = -cached.k; // = -(-k) = k grisu2_digit_gen(buf, len, decimal_exponent, M_minus, w, M_plus); } /*! v = buf * 10^decimal_exponent len is the length of the buffer (number of decimal digits) The buffer must be large enough, i.e. >= max_digits10. */ template JSON_HEDLEY_NON_NULL(1) void grisu2(char* buf, int& len, int& decimal_exponent, FloatType value) { static_assert(diyfp::kPrecision >= std::numeric_limits::digits + 3, "internal error: not enough precision"); JSON_ASSERT(std::isfinite(value)); JSON_ASSERT(value > 0); // If the neighbors (and boundaries) of 'value' are always computed for double-precision // numbers, all float's can be recovered using strtod (and strtof). However, the resulting // decimal representations are not exactly "short". // // The documentation for 'std::to_chars' (https://en.cppreference.com/w/cpp/utility/to_chars) // says "value is converted to a string as if by std::sprintf in the default ("C") locale" // and since sprintf promotes float's to double's, I think this is exactly what 'std::to_chars' // does. // On the other hand, the documentation for 'std::to_chars' requires that "parsing the // representation using the corresponding std::from_chars function recovers value exactly". That // indicates that single precision floating-point numbers should be recovered using // 'std::strtof'. // // NB: If the neighbors are computed for single-precision numbers, there is a single float // (7.0385307e-26f) which can't be recovered using strtod. The resulting double precision // value is off by 1 ulp. #if 0 const boundaries w = compute_boundaries(static_cast(value)); #else const boundaries w = compute_boundaries(value); #endif grisu2(buf, len, decimal_exponent, w.minus, w.w, w.plus); } /*! @brief appends a decimal representation of e to buf @return a pointer to the element following the exponent. @pre -1000 < e < 1000 */ JSON_HEDLEY_NON_NULL(1) JSON_HEDLEY_RETURNS_NON_NULL inline char* append_exponent(char* buf, int e) { JSON_ASSERT(e > -1000); JSON_ASSERT(e < 1000); if (e < 0) { e = -e; *buf++ = '-'; } else { *buf++ = '+'; } auto k = static_cast(e); if (k < 10) { // Always print at least two digits in the exponent. // This is for compatibility with printf("%g"). *buf++ = '0'; *buf++ = static_cast('0' + k); } else if (k < 100) { *buf++ = static_cast('0' + k / 10); k %= 10; *buf++ = static_cast('0' + k); } else { *buf++ = static_cast('0' + k / 100); k %= 100; *buf++ = static_cast('0' + k / 10); k %= 10; *buf++ = static_cast('0' + k); } return buf; } /*! @brief prettify v = buf * 10^decimal_exponent If v is in the range [10^min_exp, 10^max_exp) it will be printed in fixed-point notation. Otherwise it will be printed in exponential notation. @pre min_exp < 0 @pre max_exp > 0 */ JSON_HEDLEY_NON_NULL(1) JSON_HEDLEY_RETURNS_NON_NULL inline char* format_buffer(char* buf, int len, int decimal_exponent, int min_exp, int max_exp) { JSON_ASSERT(min_exp < 0); JSON_ASSERT(max_exp > 0); const int k = len; const int n = len + decimal_exponent; // v = buf * 10^(n-k) // k is the length of the buffer (number of decimal digits) // n is the position of the decimal point relative to the start of the buffer. if (k <= n && n <= max_exp) { // digits[000] // len <= max_exp + 2 std::memset(buf + k, '0', static_cast(n) - static_cast(k)); // Make it look like a floating-point number (#362, #378) buf[n + 0] = '.'; buf[n + 1] = '0'; return buf + (static_cast(n) + 2); } if (0 < n && n <= max_exp) { // dig.its // len <= max_digits10 + 1 JSON_ASSERT(k > n); std::memmove(buf + (static_cast(n) + 1), buf + n, static_cast(k) - static_cast(n)); buf[n] = '.'; return buf + (static_cast(k) + 1U); } if (min_exp < n && n <= 0) { // 0.[000]digits // len <= 2 + (-min_exp - 1) + max_digits10 std::memmove(buf + (2 + static_cast(-n)), buf, static_cast(k)); buf[0] = '0'; buf[1] = '.'; std::memset(buf + 2, '0', static_cast(-n)); return buf + (2U + static_cast(-n) + static_cast(k)); } if (k == 1) { // dE+123 // len <= 1 + 5 buf += 1; } else { // d.igitsE+123 // len <= max_digits10 + 1 + 5 std::memmove(buf + 2, buf + 1, static_cast(k) - 1); buf[1] = '.'; buf += 1 + static_cast(k); } *buf++ = 'e'; return append_exponent(buf, n - 1); } } // namespace dtoa_impl /*! @brief generates a decimal representation of the floating-point number value in [first, last). The format of the resulting decimal representation is similar to printf's %g format. Returns an iterator pointing past-the-end of the decimal representation. @note The input number must be finite, i.e. NaN's and Inf's are not supported. @note The buffer must be large enough. @note The result is NOT null-terminated. */ template JSON_HEDLEY_NON_NULL(1, 2) JSON_HEDLEY_RETURNS_NON_NULL char* to_chars(char* first, const char* last, FloatType value) { static_cast(last); // maybe unused - fix warning JSON_ASSERT(std::isfinite(value)); // Use signbit(value) instead of (value < 0) since signbit works for -0. if (std::signbit(value)) { value = -value; *first++ = '-'; } if (value == 0) // +-0 { *first++ = '0'; // Make it look like a floating-point number (#362, #378) *first++ = '.'; *first++ = '0'; return first; } JSON_ASSERT(last - first >= std::numeric_limits::max_digits10); // Compute v = buffer * 10^decimal_exponent. // The decimal digits are stored in the buffer, which needs to be interpreted // as an unsigned decimal integer. // len is the length of the buffer, i.e. the number of decimal digits. int len = 0; int decimal_exponent = 0; dtoa_impl::grisu2(first, len, decimal_exponent, value); JSON_ASSERT(len <= std::numeric_limits::max_digits10); // Format the buffer like printf("%.*g", prec, value) constexpr int kMinExp = -4; // Use digits10 here to increase compatibility with version 2. constexpr int kMaxExp = std::numeric_limits::digits10; JSON_ASSERT(last - first >= kMaxExp + 2); JSON_ASSERT(last - first >= 2 + (-kMinExp - 1) + std::numeric_limits::max_digits10); JSON_ASSERT(last - first >= std::numeric_limits::max_digits10 + 6); return dtoa_impl::format_buffer(first, len, decimal_exponent, kMinExp, kMaxExp); } } // namespace detail } // namespace nlohmann // #include // #include // #include // #include // #include // #include namespace nlohmann { namespace detail { /////////////////// // serialization // /////////////////// /// how to treat decoding errors enum class error_handler_t { strict, ///< throw a type_error exception in case of invalid UTF-8 replace, ///< replace invalid UTF-8 sequences with U+FFFD ignore ///< ignore invalid UTF-8 sequences }; template class serializer { using string_t = typename BasicJsonType::string_t; using number_float_t = typename BasicJsonType::number_float_t; using number_integer_t = typename BasicJsonType::number_integer_t; using number_unsigned_t = typename BasicJsonType::number_unsigned_t; using binary_char_t = typename BasicJsonType::binary_t::value_type; static constexpr std::uint8_t UTF8_ACCEPT = 0; static constexpr std::uint8_t UTF8_REJECT = 1; public: /*! @param[in] s output stream to serialize to @param[in] ichar indentation character to use @param[in] error_handler_ how to react on decoding errors */ serializer(output_adapter_t s, const char ichar, error_handler_t error_handler_ = error_handler_t::strict) : o(std::move(s)) , loc(std::localeconv()) , thousands_sep(loc->thousands_sep == nullptr ? '\0' : std::char_traits::to_char_type(* (loc->thousands_sep))) , decimal_point(loc->decimal_point == nullptr ? '\0' : std::char_traits::to_char_type(* (loc->decimal_point))) , indent_char(ichar) , indent_string(512, indent_char) , error_handler(error_handler_) {} // delete because of pointer members serializer(const serializer&) = delete; serializer& operator=(const serializer&) = delete; serializer(serializer&&) = delete; serializer& operator=(serializer&&) = delete; ~serializer() = default; /*! @brief internal implementation of the serialization function This function is called by the public member function dump and organizes the serialization internally. The indentation level is propagated as additional parameter. In case of arrays and objects, the function is called recursively. - strings and object keys are escaped using `escape_string()` - integer numbers are converted implicitly via `operator<<` - floating-point numbers are converted to a string using `"%g"` format - binary values are serialized as objects containing the subtype and the byte array @param[in] val value to serialize @param[in] pretty_print whether the output shall be pretty-printed @param[in] ensure_ascii If @a ensure_ascii is true, all non-ASCII characters in the output are escaped with `\uXXXX` sequences, and the result consists of ASCII characters only. @param[in] indent_step the indent level @param[in] current_indent the current indent level (only used internally) */ void dump(const BasicJsonType& val, const bool pretty_print, const bool ensure_ascii, const unsigned int indent_step, const unsigned int current_indent = 0) { switch (val.m_type) { case value_t::object: { if (val.m_value.object->empty()) { o->write_characters("{}", 2); return; } if (pretty_print) { o->write_characters("{\n", 2); // variable to hold indentation for recursive calls const auto new_indent = current_indent + indent_step; if (JSON_HEDLEY_UNLIKELY(indent_string.size() < new_indent)) { indent_string.resize(indent_string.size() * 2, ' '); } // first n-1 elements auto i = val.m_value.object->cbegin(); for (std::size_t cnt = 0; cnt < val.m_value.object->size() - 1; ++cnt, ++i) { o->write_characters(indent_string.c_str(), new_indent); o->write_character('\"'); dump_escaped(i->first, ensure_ascii); o->write_characters("\": ", 3); dump(i->second, true, ensure_ascii, indent_step, new_indent); o->write_characters(",\n", 2); } // last element JSON_ASSERT(i != val.m_value.object->cend()); JSON_ASSERT(std::next(i) == val.m_value.object->cend()); o->write_characters(indent_string.c_str(), new_indent); o->write_character('\"'); dump_escaped(i->first, ensure_ascii); o->write_characters("\": ", 3); dump(i->second, true, ensure_ascii, indent_step, new_indent); o->write_character('\n'); o->write_characters(indent_string.c_str(), current_indent); o->write_character('}'); } else { o->write_character('{'); // first n-1 elements auto i = val.m_value.object->cbegin(); for (std::size_t cnt = 0; cnt < val.m_value.object->size() - 1; ++cnt, ++i) { o->write_character('\"'); dump_escaped(i->first, ensure_ascii); o->write_characters("\":", 2); dump(i->second, false, ensure_ascii, indent_step, current_indent); o->write_character(','); } // last element JSON_ASSERT(i != val.m_value.object->cend()); JSON_ASSERT(std::next(i) == val.m_value.object->cend()); o->write_character('\"'); dump_escaped(i->first, ensure_ascii); o->write_characters("\":", 2); dump(i->second, false, ensure_ascii, indent_step, current_indent); o->write_character('}'); } return; } case value_t::array: { if (val.m_value.array->empty()) { o->write_characters("[]", 2); return; } if (pretty_print) { o->write_characters("[\n", 2); // variable to hold indentation for recursive calls const auto new_indent = current_indent + indent_step; if (JSON_HEDLEY_UNLIKELY(indent_string.size() < new_indent)) { indent_string.resize(indent_string.size() * 2, ' '); } // first n-1 elements for (auto i = val.m_value.array->cbegin(); i != val.m_value.array->cend() - 1; ++i) { o->write_characters(indent_string.c_str(), new_indent); dump(*i, true, ensure_ascii, indent_step, new_indent); o->write_characters(",\n", 2); } // last element JSON_ASSERT(!val.m_value.array->empty()); o->write_characters(indent_string.c_str(), new_indent); dump(val.m_value.array->back(), true, ensure_ascii, indent_step, new_indent); o->write_character('\n'); o->write_characters(indent_string.c_str(), current_indent); o->write_character(']'); } else { o->write_character('['); // first n-1 elements for (auto i = val.m_value.array->cbegin(); i != val.m_value.array->cend() - 1; ++i) { dump(*i, false, ensure_ascii, indent_step, current_indent); o->write_character(','); } // last element JSON_ASSERT(!val.m_value.array->empty()); dump(val.m_value.array->back(), false, ensure_ascii, indent_step, current_indent); o->write_character(']'); } return; } case value_t::string: { o->write_character('\"'); dump_escaped(*val.m_value.string, ensure_ascii); o->write_character('\"'); return; } case value_t::binary: { if (pretty_print) { o->write_characters("{\n", 2); // variable to hold indentation for recursive calls const auto new_indent = current_indent + indent_step; if (JSON_HEDLEY_UNLIKELY(indent_string.size() < new_indent)) { indent_string.resize(indent_string.size() * 2, ' '); } o->write_characters(indent_string.c_str(), new_indent); o->write_characters("\"bytes\": [", 10); if (!val.m_value.binary->empty()) { for (auto i = val.m_value.binary->cbegin(); i != val.m_value.binary->cend() - 1; ++i) { dump_integer(*i); o->write_characters(", ", 2); } dump_integer(val.m_value.binary->back()); } o->write_characters("],\n", 3); o->write_characters(indent_string.c_str(), new_indent); o->write_characters("\"subtype\": ", 11); if (val.m_value.binary->has_subtype()) { dump_integer(val.m_value.binary->subtype()); } else { o->write_characters("null", 4); } o->write_character('\n'); o->write_characters(indent_string.c_str(), current_indent); o->write_character('}'); } else { o->write_characters("{\"bytes\":[", 10); if (!val.m_value.binary->empty()) { for (auto i = val.m_value.binary->cbegin(); i != val.m_value.binary->cend() - 1; ++i) { dump_integer(*i); o->write_character(','); } dump_integer(val.m_value.binary->back()); } o->write_characters("],\"subtype\":", 12); if (val.m_value.binary->has_subtype()) { dump_integer(val.m_value.binary->subtype()); o->write_character('}'); } else { o->write_characters("null}", 5); } } return; } case value_t::boolean: { if (val.m_value.boolean) { o->write_characters("true", 4); } else { o->write_characters("false", 5); } return; } case value_t::number_integer: { dump_integer(val.m_value.number_integer); return; } case value_t::number_unsigned: { dump_integer(val.m_value.number_unsigned); return; } case value_t::number_float: { dump_float(val.m_value.number_float); return; } case value_t::discarded: { o->write_characters("", 11); return; } case value_t::null: { o->write_characters("null", 4); return; } default: // LCOV_EXCL_LINE JSON_ASSERT(false); // LCOV_EXCL_LINE } } private: /*! @brief dump escaped string Escape a string by replacing certain special characters by a sequence of an escape character (backslash) and another character and other control characters by a sequence of "\u" followed by a four-digit hex representation. The escaped string is written to output stream @a o. @param[in] s the string to escape @param[in] ensure_ascii whether to escape non-ASCII characters with \uXXXX sequences @complexity Linear in the length of string @a s. */ void dump_escaped(const string_t& s, const bool ensure_ascii) { std::uint32_t codepoint; std::uint8_t state = UTF8_ACCEPT; std::size_t bytes = 0; // number of bytes written to string_buffer // number of bytes written at the point of the last valid byte std::size_t bytes_after_last_accept = 0; std::size_t undumped_chars = 0; for (std::size_t i = 0; i < s.size(); ++i) { const auto byte = static_cast(s[i]); switch (decode(state, codepoint, byte)) { case UTF8_ACCEPT: // decode found a new code point { switch (codepoint) { case 0x08: // backspace { string_buffer[bytes++] = '\\'; string_buffer[bytes++] = 'b'; break; } case 0x09: // horizontal tab { string_buffer[bytes++] = '\\'; string_buffer[bytes++] = 't'; break; } case 0x0A: // newline { string_buffer[bytes++] = '\\'; string_buffer[bytes++] = 'n'; break; } case 0x0C: // formfeed { string_buffer[bytes++] = '\\'; string_buffer[bytes++] = 'f'; break; } case 0x0D: // carriage return { string_buffer[bytes++] = '\\'; string_buffer[bytes++] = 'r'; break; } case 0x22: // quotation mark { string_buffer[bytes++] = '\\'; string_buffer[bytes++] = '\"'; break; } case 0x5C: // reverse solidus { string_buffer[bytes++] = '\\'; string_buffer[bytes++] = '\\'; break; } default: { // escape control characters (0x00..0x1F) or, if // ensure_ascii parameter is used, non-ASCII characters if ((codepoint <= 0x1F) || (ensure_ascii && (codepoint >= 0x7F))) { if (codepoint <= 0xFFFF) { (std::snprintf)(string_buffer.data() + bytes, 7, "\\u%04x", static_cast(codepoint)); bytes += 6; } else { (std::snprintf)(string_buffer.data() + bytes, 13, "\\u%04x\\u%04x", static_cast(0xD7C0u + (codepoint >> 10u)), static_cast(0xDC00u + (codepoint & 0x3FFu))); bytes += 12; } } else { // copy byte to buffer (all previous bytes // been copied have in default case above) string_buffer[bytes++] = s[i]; } break; } } // write buffer and reset index; there must be 13 bytes // left, as this is the maximal number of bytes to be // written ("\uxxxx\uxxxx\0") for one code point if (string_buffer.size() - bytes < 13) { o->write_characters(string_buffer.data(), bytes); bytes = 0; } // remember the byte position of this accept bytes_after_last_accept = bytes; undumped_chars = 0; break; } case UTF8_REJECT: // decode found invalid UTF-8 byte { switch (error_handler) { case error_handler_t::strict: { std::string sn(3, '\0'); (std::snprintf)(&sn[0], sn.size(), "%.2X", byte); JSON_THROW(type_error::create(316, "invalid UTF-8 byte at index " + std::to_string(i) + ": 0x" + sn)); } case error_handler_t::ignore: case error_handler_t::replace: { // in case we saw this character the first time, we // would like to read it again, because the byte // may be OK for itself, but just not OK for the // previous sequence if (undumped_chars > 0) { --i; } // reset length buffer to the last accepted index; // thus removing/ignoring the invalid characters bytes = bytes_after_last_accept; if (error_handler == error_handler_t::replace) { // add a replacement character if (ensure_ascii) { string_buffer[bytes++] = '\\'; string_buffer[bytes++] = 'u'; string_buffer[bytes++] = 'f'; string_buffer[bytes++] = 'f'; string_buffer[bytes++] = 'f'; string_buffer[bytes++] = 'd'; } else { string_buffer[bytes++] = detail::binary_writer::to_char_type('\xEF'); string_buffer[bytes++] = detail::binary_writer::to_char_type('\xBF'); string_buffer[bytes++] = detail::binary_writer::to_char_type('\xBD'); } // write buffer and reset index; there must be 13 bytes // left, as this is the maximal number of bytes to be // written ("\uxxxx\uxxxx\0") for one code point if (string_buffer.size() - bytes < 13) { o->write_characters(string_buffer.data(), bytes); bytes = 0; } bytes_after_last_accept = bytes; } undumped_chars = 0; // continue processing the string state = UTF8_ACCEPT; break; } default: // LCOV_EXCL_LINE JSON_ASSERT(false); // LCOV_EXCL_LINE } break; } default: // decode found yet incomplete multi-byte code point { if (!ensure_ascii) { // code point will not be escaped - copy byte to buffer string_buffer[bytes++] = s[i]; } ++undumped_chars; break; } } } // we finished processing the string if (JSON_HEDLEY_LIKELY(state == UTF8_ACCEPT)) { // write buffer if (bytes > 0) { o->write_characters(string_buffer.data(), bytes); } } else { // we finish reading, but do not accept: string was incomplete switch (error_handler) { case error_handler_t::strict: { std::string sn(3, '\0'); (std::snprintf)(&sn[0], sn.size(), "%.2X", static_cast(s.back())); JSON_THROW(type_error::create(316, "incomplete UTF-8 string; last byte: 0x" + sn)); } case error_handler_t::ignore: { // write all accepted bytes o->write_characters(string_buffer.data(), bytes_after_last_accept); break; } case error_handler_t::replace: { // write all accepted bytes o->write_characters(string_buffer.data(), bytes_after_last_accept); // add a replacement character if (ensure_ascii) { o->write_characters("\\ufffd", 6); } else { o->write_characters("\xEF\xBF\xBD", 3); } break; } default: // LCOV_EXCL_LINE JSON_ASSERT(false); // LCOV_EXCL_LINE } } } /*! @brief count digits Count the number of decimal (base 10) digits for an input unsigned integer. @param[in] x unsigned integer number to count its digits @return number of decimal digits */ inline unsigned int count_digits(number_unsigned_t x) noexcept { unsigned int n_digits = 1; for (;;) { if (x < 10) { return n_digits; } if (x < 100) { return n_digits + 1; } if (x < 1000) { return n_digits + 2; } if (x < 10000) { return n_digits + 3; } x = x / 10000u; n_digits += 4; } } /*! @brief dump an integer Dump a given integer to output stream @a o. Works internally with @a number_buffer. @param[in] x integer number (signed or unsigned) to dump @tparam NumberType either @a number_integer_t or @a number_unsigned_t */ template < typename NumberType, detail::enable_if_t < std::is_same::value || std::is_same::value || std::is_same::value, int > = 0 > void dump_integer(NumberType x) { static constexpr std::array, 100> digits_to_99 { { {{'0', '0'}}, {{'0', '1'}}, {{'0', '2'}}, {{'0', '3'}}, {{'0', '4'}}, {{'0', '5'}}, {{'0', '6'}}, {{'0', '7'}}, {{'0', '8'}}, {{'0', '9'}}, {{'1', '0'}}, {{'1', '1'}}, {{'1', '2'}}, {{'1', '3'}}, {{'1', '4'}}, {{'1', '5'}}, {{'1', '6'}}, {{'1', '7'}}, {{'1', '8'}}, {{'1', '9'}}, {{'2', '0'}}, {{'2', '1'}}, {{'2', '2'}}, {{'2', '3'}}, {{'2', '4'}}, {{'2', '5'}}, {{'2', '6'}}, {{'2', '7'}}, {{'2', '8'}}, {{'2', '9'}}, {{'3', '0'}}, {{'3', '1'}}, {{'3', '2'}}, {{'3', '3'}}, {{'3', '4'}}, {{'3', '5'}}, {{'3', '6'}}, {{'3', '7'}}, {{'3', '8'}}, {{'3', '9'}}, {{'4', '0'}}, {{'4', '1'}}, {{'4', '2'}}, {{'4', '3'}}, {{'4', '4'}}, {{'4', '5'}}, {{'4', '6'}}, {{'4', '7'}}, {{'4', '8'}}, {{'4', '9'}}, {{'5', '0'}}, {{'5', '1'}}, {{'5', '2'}}, {{'5', '3'}}, {{'5', '4'}}, {{'5', '5'}}, {{'5', '6'}}, {{'5', '7'}}, {{'5', '8'}}, {{'5', '9'}}, {{'6', '0'}}, {{'6', '1'}}, {{'6', '2'}}, {{'6', '3'}}, {{'6', '4'}}, {{'6', '5'}}, {{'6', '6'}}, {{'6', '7'}}, {{'6', '8'}}, {{'6', '9'}}, {{'7', '0'}}, {{'7', '1'}}, {{'7', '2'}}, {{'7', '3'}}, {{'7', '4'}}, {{'7', '5'}}, {{'7', '6'}}, {{'7', '7'}}, {{'7', '8'}}, {{'7', '9'}}, {{'8', '0'}}, {{'8', '1'}}, {{'8', '2'}}, {{'8', '3'}}, {{'8', '4'}}, {{'8', '5'}}, {{'8', '6'}}, {{'8', '7'}}, {{'8', '8'}}, {{'8', '9'}}, {{'9', '0'}}, {{'9', '1'}}, {{'9', '2'}}, {{'9', '3'}}, {{'9', '4'}}, {{'9', '5'}}, {{'9', '6'}}, {{'9', '7'}}, {{'9', '8'}}, {{'9', '9'}}, } }; // special case for "0" if (x == 0) { o->write_character('0'); return; } // use a pointer to fill the buffer auto buffer_ptr = number_buffer.begin(); const bool is_negative = std::is_same::value && !(x >= 0); // see issue #755 number_unsigned_t abs_value; unsigned int n_chars; if (is_negative) { *buffer_ptr = '-'; abs_value = remove_sign(static_cast(x)); // account one more byte for the minus sign n_chars = 1 + count_digits(abs_value); } else { abs_value = static_cast(x); n_chars = count_digits(abs_value); } // spare 1 byte for '\0' JSON_ASSERT(n_chars < number_buffer.size() - 1); // jump to the end to generate the string from backward // so we later avoid reversing the result buffer_ptr += n_chars; // Fast int2ascii implementation inspired by "Fastware" talk by Andrei Alexandrescu // See: https://www.youtube.com/watch?v=o4-CwDo2zpg while (abs_value >= 100) { const auto digits_index = static_cast((abs_value % 100)); abs_value /= 100; *(--buffer_ptr) = digits_to_99[digits_index][1]; *(--buffer_ptr) = digits_to_99[digits_index][0]; } if (abs_value >= 10) { const auto digits_index = static_cast(abs_value); *(--buffer_ptr) = digits_to_99[digits_index][1]; *(--buffer_ptr) = digits_to_99[digits_index][0]; } else { *(--buffer_ptr) = static_cast('0' + abs_value); } o->write_characters(number_buffer.data(), n_chars); } /*! @brief dump a floating-point number Dump a given floating-point number to output stream @a o. Works internally with @a number_buffer. @param[in] x floating-point number to dump */ void dump_float(number_float_t x) { // NaN / inf if (!std::isfinite(x)) { o->write_characters("null", 4); return; } // If number_float_t is an IEEE-754 single or double precision number, // use the Grisu2 algorithm to produce short numbers which are // guaranteed to round-trip, using strtof and strtod, resp. // // NB: The test below works if == . static constexpr bool is_ieee_single_or_double = (std::numeric_limits::is_iec559 && std::numeric_limits::digits == 24 && std::numeric_limits::max_exponent == 128) || (std::numeric_limits::is_iec559 && std::numeric_limits::digits == 53 && std::numeric_limits::max_exponent == 1024); dump_float(x, std::integral_constant()); } void dump_float(number_float_t x, std::true_type /*is_ieee_single_or_double*/) { char* begin = number_buffer.data(); char* end = ::nlohmann::detail::to_chars(begin, begin + number_buffer.size(), x); o->write_characters(begin, static_cast(end - begin)); } void dump_float(number_float_t x, std::false_type /*is_ieee_single_or_double*/) { // get number of digits for a float -> text -> float round-trip static constexpr auto d = std::numeric_limits::max_digits10; // the actual conversion std::ptrdiff_t len = (std::snprintf)(number_buffer.data(), number_buffer.size(), "%.*g", d, x); // negative value indicates an error JSON_ASSERT(len > 0); // check if buffer was large enough JSON_ASSERT(static_cast(len) < number_buffer.size()); // erase thousands separator if (thousands_sep != '\0') { const auto end = std::remove(number_buffer.begin(), number_buffer.begin() + len, thousands_sep); std::fill(end, number_buffer.end(), '\0'); JSON_ASSERT((end - number_buffer.begin()) <= len); len = (end - number_buffer.begin()); } // convert decimal point to '.' if (decimal_point != '\0' && decimal_point != '.') { const auto dec_pos = std::find(number_buffer.begin(), number_buffer.end(), decimal_point); if (dec_pos != number_buffer.end()) { *dec_pos = '.'; } } o->write_characters(number_buffer.data(), static_cast(len)); // determine if need to append ".0" const bool value_is_int_like = std::none_of(number_buffer.begin(), number_buffer.begin() + len + 1, [](char c) { return c == '.' || c == 'e'; }); if (value_is_int_like) { o->write_characters(".0", 2); } } /*! @brief check whether a string is UTF-8 encoded The function checks each byte of a string whether it is UTF-8 encoded. The result of the check is stored in the @a state parameter. The function must be called initially with state 0 (accept). State 1 means the string must be rejected, because the current byte is not allowed. If the string is completely processed, but the state is non-zero, the string ended prematurely; that is, the last byte indicated more bytes should have followed. @param[in,out] state the state of the decoding @param[in,out] codep codepoint (valid only if resulting state is UTF8_ACCEPT) @param[in] byte next byte to decode @return new state @note The function has been edited: a std::array is used. @copyright Copyright (c) 2008-2009 Bjoern Hoehrmann @sa http://bjoern.hoehrmann.de/utf-8/decoder/dfa/ */ static std::uint8_t decode(std::uint8_t& state, std::uint32_t& codep, const std::uint8_t byte) noexcept { static const std::array utf8d = { { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 00..1F 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 20..3F 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 40..5F 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 60..7F 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, // 80..9F 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, // A0..BF 8, 8, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, // C0..DF 0xA, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x4, 0x3, 0x3, // E0..EF 0xB, 0x6, 0x6, 0x6, 0x5, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, // F0..FF 0x0, 0x1, 0x2, 0x3, 0x5, 0x8, 0x7, 0x1, 0x1, 0x1, 0x4, 0x6, 0x1, 0x1, 0x1, 0x1, // s0..s0 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 0, 1, 0, 1, 1, 1, 1, 1, 1, // s1..s2 1, 2, 1, 1, 1, 1, 1, 2, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, // s3..s4 1, 2, 1, 1, 1, 1, 1, 1, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 3, 1, 3, 1, 1, 1, 1, 1, 1, // s5..s6 1, 3, 1, 1, 1, 1, 1, 3, 1, 3, 1, 1, 1, 1, 1, 1, 1, 3, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 // s7..s8 } }; const std::uint8_t type = utf8d[byte]; codep = (state != UTF8_ACCEPT) ? (byte & 0x3fu) | (codep << 6u) : (0xFFu >> type) & (byte); std::size_t index = 256u + static_cast(state) * 16u + static_cast(type); JSON_ASSERT(index < 400); state = utf8d[index]; return state; } /* * Overload to make the compiler happy while it is instantiating * dump_integer for number_unsigned_t. * Must never be called. */ number_unsigned_t remove_sign(number_unsigned_t x) { JSON_ASSERT(false); // LCOV_EXCL_LINE return x; // LCOV_EXCL_LINE } /* * Helper function for dump_integer * * This function takes a negative signed integer and returns its absolute * value as unsigned integer. The plus/minus shuffling is necessary as we can * not directly remove the sign of an arbitrary signed integer as the * absolute values of INT_MIN and INT_MAX are usually not the same. See * #1708 for details. */ inline number_unsigned_t remove_sign(number_integer_t x) noexcept { JSON_ASSERT(x < 0 && x < (std::numeric_limits::max)()); return static_cast(-(x + 1)) + 1; } private: /// the output of the serializer output_adapter_t o = nullptr; /// a (hopefully) large enough character buffer std::array number_buffer{{}}; /// the locale const std::lconv* loc = nullptr; /// the locale's thousand separator character const char thousands_sep = '\0'; /// the locale's decimal point character const char decimal_point = '\0'; /// string buffer std::array string_buffer{{}}; /// the indentation character const char indent_char; /// the indentation string string_t indent_string; /// error_handler how to react on decoding errors const error_handler_t error_handler; }; } // namespace detail } // namespace nlohmann // #include // #include // #include #include // less #include // allocator #include // pair #include // vector namespace nlohmann { /// ordered_map: a minimal map-like container that preserves insertion order /// for use within nlohmann::basic_json template , class Allocator = std::allocator>> struct ordered_map : std::vector, Allocator> { using key_type = Key; using mapped_type = T; using Container = std::vector, Allocator>; using typename Container::iterator; using typename Container::const_iterator; using typename Container::size_type; using typename Container::value_type; // Explicit constructors instead of `using Container::Container` // otherwise older compilers choke on it (GCC <= 5.5, xcode <= 9.4) ordered_map(const Allocator& alloc = Allocator()) : Container{alloc} {} template ordered_map(It first, It last, const Allocator& alloc = Allocator()) : Container{first, last, alloc} {} ordered_map(std::initializer_list init, const Allocator& alloc = Allocator() ) : Container{init, alloc} {} std::pair emplace(const key_type& key, T&& t) { for (auto it = this->begin(); it != this->end(); ++it) { if (it->first == key) { return {it, false}; } } Container::emplace_back(key, t); return {--this->end(), true}; } T& operator[](const Key& key) { return emplace(key, T{}).first->second; } const T& operator[](const Key& key) const { return at(key); } T& at(const Key& key) { for (auto it = this->begin(); it != this->end(); ++it) { if (it->first == key) { return it->second; } } throw std::out_of_range("key not found"); } const T& at(const Key& key) const { for (auto it = this->begin(); it != this->end(); ++it) { if (it->first == key) { return it->second; } } throw std::out_of_range("key not found"); } size_type erase(const Key& key) { for (auto it = this->begin(); it != this->end(); ++it) { if (it->first == key) { // Since we cannot move const Keys, re-construct them in place for (auto next = it; ++next != this->end(); ++it) { it->~value_type(); // Destroy but keep allocation new (&*it) value_type{std::move(*next)}; } Container::pop_back(); return 1; } } return 0; } iterator erase(iterator pos) { auto it = pos; // Since we cannot move const Keys, re-construct them in place for (auto next = it; ++next != this->end(); ++it) { it->~value_type(); // Destroy but keep allocation new (&*it) value_type{std::move(*next)}; } Container::pop_back(); return pos; } size_type count(const Key& key) const { for (auto it = this->begin(); it != this->end(); ++it) { if (it->first == key) { return 1; } } return 0; } iterator find(const Key& key) { for (auto it = this->begin(); it != this->end(); ++it) { if (it->first == key) { return it; } } return Container::end(); } const_iterator find(const Key& key) const { for (auto it = this->begin(); it != this->end(); ++it) { if (it->first == key) { return it; } } return Container::end(); } std::pair insert( value_type&& value ) { return emplace(value.first, std::move(value.second)); } std::pair insert( const value_type& value ) { for (auto it = this->begin(); it != this->end(); ++it) { if (it->first == value.first) { return {it, false}; } } Container::push_back(value); return {--this->end(), true}; } }; } // namespace nlohmann /*! @brief namespace for Niels Lohmann @see https://github.com/nlohmann @since version 1.0.0 */ namespace nlohmann { /*! @brief a class to store JSON values @tparam ObjectType type for JSON objects (`std::map` by default; will be used in @ref object_t) @tparam ArrayType type for JSON arrays (`std::vector` by default; will be used in @ref array_t) @tparam StringType type for JSON strings and object keys (`std::string` by default; will be used in @ref string_t) @tparam BooleanType type for JSON booleans (`bool` by default; will be used in @ref boolean_t) @tparam NumberIntegerType type for JSON integer numbers (`int64_t` by default; will be used in @ref number_integer_t) @tparam NumberUnsignedType type for JSON unsigned integer numbers (@c `uint64_t` by default; will be used in @ref number_unsigned_t) @tparam NumberFloatType type for JSON floating-point numbers (`double` by default; will be used in @ref number_float_t) @tparam BinaryType type for packed binary data for compatibility with binary serialization formats (`std::vector` by default; will be used in @ref binary_t) @tparam AllocatorType type of the allocator to use (`std::allocator` by default) @tparam JSONSerializer the serializer to resolve internal calls to `to_json()` and `from_json()` (@ref adl_serializer by default) @requirement The class satisfies the following concept requirements: - Basic - [DefaultConstructible](https://en.cppreference.com/w/cpp/named_req/DefaultConstructible): JSON values can be default constructed. The result will be a JSON null value. - [MoveConstructible](https://en.cppreference.com/w/cpp/named_req/MoveConstructible): A JSON value can be constructed from an rvalue argument. - [CopyConstructible](https://en.cppreference.com/w/cpp/named_req/CopyConstructible): A JSON value can be copy-constructed from an lvalue expression. - [MoveAssignable](https://en.cppreference.com/w/cpp/named_req/MoveAssignable): A JSON value van be assigned from an rvalue argument. - [CopyAssignable](https://en.cppreference.com/w/cpp/named_req/CopyAssignable): A JSON value can be copy-assigned from an lvalue expression. - [Destructible](https://en.cppreference.com/w/cpp/named_req/Destructible): JSON values can be destructed. - Layout - [StandardLayoutType](https://en.cppreference.com/w/cpp/named_req/StandardLayoutType): JSON values have [standard layout](https://en.cppreference.com/w/cpp/language/data_members#Standard_layout): All non-static data members are private and standard layout types, the class has no virtual functions or (virtual) base classes. - Library-wide - [EqualityComparable](https://en.cppreference.com/w/cpp/named_req/EqualityComparable): JSON values can be compared with `==`, see @ref operator==(const_reference,const_reference). - [LessThanComparable](https://en.cppreference.com/w/cpp/named_req/LessThanComparable): JSON values can be compared with `<`, see @ref operator<(const_reference,const_reference). - [Swappable](https://en.cppreference.com/w/cpp/named_req/Swappable): Any JSON lvalue or rvalue of can be swapped with any lvalue or rvalue of other compatible types, using unqualified function call @ref swap(). - [NullablePointer](https://en.cppreference.com/w/cpp/named_req/NullablePointer): JSON values can be compared against `std::nullptr_t` objects which are used to model the `null` value. - Container - [Container](https://en.cppreference.com/w/cpp/named_req/Container): JSON values can be used like STL containers and provide iterator access. - [ReversibleContainer](https://en.cppreference.com/w/cpp/named_req/ReversibleContainer); JSON values can be used like STL containers and provide reverse iterator access. @invariant The member variables @a m_value and @a m_type have the following relationship: - If `m_type == value_t::object`, then `m_value.object != nullptr`. - If `m_type == value_t::array`, then `m_value.array != nullptr`. - If `m_type == value_t::string`, then `m_value.string != nullptr`. The invariants are checked by member function assert_invariant(). @internal @note ObjectType trick from https://stackoverflow.com/a/9860911 @endinternal @see [RFC 7159: The JavaScript Object Notation (JSON) Data Interchange Format](http://rfc7159.net/rfc7159) @since version 1.0.0 @nosubgrouping */ NLOHMANN_BASIC_JSON_TPL_DECLARATION class basic_json { private: template friend struct detail::external_constructor; friend ::nlohmann::json_pointer; template friend class ::nlohmann::detail::parser; friend ::nlohmann::detail::serializer; template friend class ::nlohmann::detail::iter_impl; template friend class ::nlohmann::detail::binary_writer; template friend class ::nlohmann::detail::binary_reader; template friend class ::nlohmann::detail::json_sax_dom_parser; template friend class ::nlohmann::detail::json_sax_dom_callback_parser; /// workaround type for MSVC using basic_json_t = NLOHMANN_BASIC_JSON_TPL; // convenience aliases for types residing in namespace detail; using lexer = ::nlohmann::detail::lexer_base; template static ::nlohmann::detail::parser parser( InputAdapterType adapter, detail::parser_callback_tcb = nullptr, const bool allow_exceptions = true, const bool ignore_comments = false ) { return ::nlohmann::detail::parser(std::move(adapter), std::move(cb), allow_exceptions, ignore_comments); } using primitive_iterator_t = ::nlohmann::detail::primitive_iterator_t; template using internal_iterator = ::nlohmann::detail::internal_iterator; template using iter_impl = ::nlohmann::detail::iter_impl; template using iteration_proxy = ::nlohmann::detail::iteration_proxy; template using json_reverse_iterator = ::nlohmann::detail::json_reverse_iterator; template using output_adapter_t = ::nlohmann::detail::output_adapter_t; template using binary_reader = ::nlohmann::detail::binary_reader; template using binary_writer = ::nlohmann::detail::binary_writer; using serializer = ::nlohmann::detail::serializer; public: using value_t = detail::value_t; /// JSON Pointer, see @ref nlohmann::json_pointer using json_pointer = ::nlohmann::json_pointer; template using json_serializer = JSONSerializer; /// how to treat decoding errors using error_handler_t = detail::error_handler_t; /// how to treat CBOR tags using cbor_tag_handler_t = detail::cbor_tag_handler_t; /// helper type for initializer lists of basic_json values using initializer_list_t = std::initializer_list>; using input_format_t = detail::input_format_t; /// SAX interface type, see @ref nlohmann::json_sax using json_sax_t = json_sax; //////////////// // exceptions // //////////////// /// @name exceptions /// Classes to implement user-defined exceptions. /// @{ /// @copydoc detail::exception using exception = detail::exception; /// @copydoc detail::parse_error using parse_error = detail::parse_error; /// @copydoc detail::invalid_iterator using invalid_iterator = detail::invalid_iterator; /// @copydoc detail::type_error using type_error = detail::type_error; /// @copydoc detail::out_of_range using out_of_range = detail::out_of_range; /// @copydoc detail::other_error using other_error = detail::other_error; /// @} ///////////////////// // container types // ///////////////////// /// @name container types /// The canonic container types to use @ref basic_json like any other STL /// container. /// @{ /// the type of elements in a basic_json container using value_type = basic_json; /// the type of an element reference using reference = value_type&; /// the type of an element const reference using const_reference = const value_type&; /// a type to represent differences between iterators using difference_type = std::ptrdiff_t; /// a type to represent container sizes using size_type = std::size_t; /// the allocator type using allocator_type = AllocatorType; /// the type of an element pointer using pointer = typename std::allocator_traits::pointer; /// the type of an element const pointer using const_pointer = typename std::allocator_traits::const_pointer; /// an iterator for a basic_json container using iterator = iter_impl; /// a const iterator for a basic_json container using const_iterator = iter_impl; /// a reverse iterator for a basic_json container using reverse_iterator = json_reverse_iterator; /// a const reverse iterator for a basic_json container using const_reverse_iterator = json_reverse_iterator; /// @} /*! @brief returns the allocator associated with the container */ static allocator_type get_allocator() { return allocator_type(); } /*! @brief returns version information on the library This function returns a JSON object with information about the library, including the version number and information on the platform and compiler. @return JSON object holding version information key | description ----------- | --------------- `compiler` | Information on the used compiler. It is an object with the following keys: `c++` (the used C++ standard), `family` (the compiler family; possible values are `clang`, `icc`, `gcc`, `ilecpp`, `msvc`, `pgcpp`, `sunpro`, and `unknown`), and `version` (the compiler version). `copyright` | The copyright line for the library as string. `name` | The name of the library as string. `platform` | The used platform as string. Possible values are `win32`, `linux`, `apple`, `unix`, and `unknown`. `url` | The URL of the project as string. `version` | The version of the library. It is an object with the following keys: `major`, `minor`, and `patch` as defined by [Semantic Versioning](http://semver.org), and `string` (the version string). @liveexample{The following code shows an example output of the `meta()` function.,meta} @exceptionsafety Strong guarantee: if an exception is thrown, there are no changes to any JSON value. @complexity Constant. @since 2.1.0 */ JSON_HEDLEY_WARN_UNUSED_RESULT static basic_json meta() { basic_json result; result["copyright"] = "(C) 2013-2020 Niels Lohmann"; result["name"] = "JSON for Modern C++"; result["url"] = "https://github.com/nlohmann/json"; result["version"]["string"] = std::to_string(NLOHMANN_JSON_VERSION_MAJOR) + "." + std::to_string(NLOHMANN_JSON_VERSION_MINOR) + "." + std::to_string(NLOHMANN_JSON_VERSION_PATCH); result["version"]["major"] = NLOHMANN_JSON_VERSION_MAJOR; result["version"]["minor"] = NLOHMANN_JSON_VERSION_MINOR; result["version"]["patch"] = NLOHMANN_JSON_VERSION_PATCH; #ifdef _WIN32 result["platform"] = "win32"; #elif defined __linux__ result["platform"] = "linux"; #elif defined __APPLE__ result["platform"] = "apple"; #elif defined __unix__ result["platform"] = "unix"; #else result["platform"] = "unknown"; #endif #if defined(__ICC) || defined(__INTEL_COMPILER) result["compiler"] = {{"family", "icc"}, {"version", __INTEL_COMPILER}}; #elif defined(__clang__) result["compiler"] = {{"family", "clang"}, {"version", __clang_version__}}; #elif defined(__GNUC__) || defined(__GNUG__) result["compiler"] = {{"family", "gcc"}, {"version", std::to_string(__GNUC__) + "." + std::to_string(__GNUC_MINOR__) + "." + std::to_string(__GNUC_PATCHLEVEL__)}}; #elif defined(__HP_cc) || defined(__HP_aCC) result["compiler"] = "hp" #elif defined(__IBMCPP__) result["compiler"] = {{"family", "ilecpp"}, {"version", __IBMCPP__}}; #elif defined(_MSC_VER) result["compiler"] = {{"family", "msvc"}, {"version", _MSC_VER}}; #elif defined(__PGI) result["compiler"] = {{"family", "pgcpp"}, {"version", __PGI}}; #elif defined(__SUNPRO_CC) result["compiler"] = {{"family", "sunpro"}, {"version", __SUNPRO_CC}}; #else result["compiler"] = {{"family", "unknown"}, {"version", "unknown"}}; #endif #ifdef __cplusplus result["compiler"]["c++"] = std::to_string(__cplusplus); #else result["compiler"]["c++"] = "unknown"; #endif return result; } /////////////////////////// // JSON value data types // /////////////////////////// /// @name JSON value data types /// The data types to store a JSON value. These types are derived from /// the template arguments passed to class @ref basic_json. /// @{ #if defined(JSON_HAS_CPP_14) // Use transparent comparator if possible, combined with perfect forwarding // on find() and count() calls prevents unnecessary string construction. using object_comparator_t = std::less<>; #else using object_comparator_t = std::less; #endif /*! @brief a type for an object [RFC 7159](http://rfc7159.net/rfc7159) describes JSON objects as follows: > An object is an unordered collection of zero or more name/value pairs, > where a name is a string and a value is a string, number, boolean, null, > object, or array. To store objects in C++, a type is defined by the template parameters described below. @tparam ObjectType the container to store objects (e.g., `std::map` or `std::unordered_map`) @tparam StringType the type of the keys or names (e.g., `std::string`). The comparison function `std::less` is used to order elements inside the container. @tparam AllocatorType the allocator to use for objects (e.g., `std::allocator`) #### Default type With the default values for @a ObjectType (`std::map`), @a StringType (`std::string`), and @a AllocatorType (`std::allocator`), the default value for @a object_t is: @code {.cpp} std::map< std::string, // key_type basic_json, // value_type std::less, // key_compare std::allocator> // allocator_type > @endcode #### Behavior The choice of @a object_t influences the behavior of the JSON class. With the default type, objects have the following behavior: - When all names are unique, objects will be interoperable in the sense that all software implementations receiving that object will agree on the name-value mappings. - When the names within an object are not unique, it is unspecified which one of the values for a given key will be chosen. For instance, `{"key": 2, "key": 1}` could be equal to either `{"key": 1}` or `{"key": 2}`. - Internally, name/value pairs are stored in lexicographical order of the names. Objects will also be serialized (see @ref dump) in this order. For instance, `{"b": 1, "a": 2}` and `{"a": 2, "b": 1}` will be stored and serialized as `{"a": 2, "b": 1}`. - When comparing objects, the order of the name/value pairs is irrelevant. This makes objects interoperable in the sense that they will not be affected by these differences. For instance, `{"b": 1, "a": 2}` and `{"a": 2, "b": 1}` will be treated as equal. #### Limits [RFC 7159](http://rfc7159.net/rfc7159) specifies: > An implementation may set limits on the maximum depth of nesting. In this class, the object's limit of nesting is not explicitly constrained. However, a maximum depth of nesting may be introduced by the compiler or runtime environment. A theoretical limit can be queried by calling the @ref max_size function of a JSON object. #### Storage Objects are stored as pointers in a @ref basic_json type. That is, for any access to object values, a pointer of type `object_t*` must be dereferenced. @sa @ref array_t -- type for an array value @since version 1.0.0 @note The order name/value pairs are added to the object is *not* preserved by the library. Therefore, iterating an object may return name/value pairs in a different order than they were originally stored. In fact, keys will be traversed in alphabetical order as `std::map` with `std::less` is used by default. Please note this behavior conforms to [RFC 7159](http://rfc7159.net/rfc7159), because any order implements the specified "unordered" nature of JSON objects. */ using object_t = ObjectType>>; /*! @brief a type for an array [RFC 7159](http://rfc7159.net/rfc7159) describes JSON arrays as follows: > An array is an ordered sequence of zero or more values. To store objects in C++, a type is defined by the template parameters explained below. @tparam ArrayType container type to store arrays (e.g., `std::vector` or `std::list`) @tparam AllocatorType allocator to use for arrays (e.g., `std::allocator`) #### Default type With the default values for @a ArrayType (`std::vector`) and @a AllocatorType (`std::allocator`), the default value for @a array_t is: @code {.cpp} std::vector< basic_json, // value_type std::allocator // allocator_type > @endcode #### Limits [RFC 7159](http://rfc7159.net/rfc7159) specifies: > An implementation may set limits on the maximum depth of nesting. In this class, the array's limit of nesting is not explicitly constrained. However, a maximum depth of nesting may be introduced by the compiler or runtime environment. A theoretical limit can be queried by calling the @ref max_size function of a JSON array. #### Storage Arrays are stored as pointers in a @ref basic_json type. That is, for any access to array values, a pointer of type `array_t*` must be dereferenced. @sa @ref object_t -- type for an object value @since version 1.0.0 */ using array_t = ArrayType>; /*! @brief a type for a string [RFC 7159](http://rfc7159.net/rfc7159) describes JSON strings as follows: > A string is a sequence of zero or more Unicode characters. To store objects in C++, a type is defined by the template parameter described below. Unicode values are split by the JSON class into byte-sized characters during deserialization. @tparam StringType the container to store strings (e.g., `std::string`). Note this container is used for keys/names in objects, see @ref object_t. #### Default type With the default values for @a StringType (`std::string`), the default value for @a string_t is: @code {.cpp} std::string @endcode #### Encoding Strings are stored in UTF-8 encoding. Therefore, functions like `std::string::size()` or `std::string::length()` return the number of bytes in the string rather than the number of characters or glyphs. #### String comparison [RFC 7159](http://rfc7159.net/rfc7159) states: > Software implementations are typically required to test names of object > members for equality. Implementations that transform the textual > representation into sequences of Unicode code units and then perform the > comparison numerically, code unit by code unit, are interoperable in the > sense that implementations will agree in all cases on equality or > inequality of two strings. For example, implementations that compare > strings with escaped characters unconverted may incorrectly find that > `"a\\b"` and `"a\u005Cb"` are not equal. This implementation is interoperable as it does compare strings code unit by code unit. #### Storage String values are stored as pointers in a @ref basic_json type. That is, for any access to string values, a pointer of type `string_t*` must be dereferenced. @since version 1.0.0 */ using string_t = StringType; /*! @brief a type for a boolean [RFC 7159](http://rfc7159.net/rfc7159) implicitly describes a boolean as a type which differentiates the two literals `true` and `false`. To store objects in C++, a type is defined by the template parameter @a BooleanType which chooses the type to use. #### Default type With the default values for @a BooleanType (`bool`), the default value for @a boolean_t is: @code {.cpp} bool @endcode #### Storage Boolean values are stored directly inside a @ref basic_json type. @since version 1.0.0 */ using boolean_t = BooleanType; /*! @brief a type for a number (integer) [RFC 7159](http://rfc7159.net/rfc7159) describes numbers as follows: > The representation of numbers is similar to that used in most > programming languages. A number is represented in base 10 using decimal > digits. It contains an integer component that may be prefixed with an > optional minus sign, which may be followed by a fraction part and/or an > exponent part. Leading zeros are not allowed. (...) Numeric values that > cannot be represented in the grammar below (such as Infinity and NaN) > are not permitted. This description includes both integer and floating-point numbers. However, C++ allows more precise storage if it is known whether the number is a signed integer, an unsigned integer or a floating-point number. Therefore, three different types, @ref number_integer_t, @ref number_unsigned_t and @ref number_float_t are used. To store integer numbers in C++, a type is defined by the template parameter @a NumberIntegerType which chooses the type to use. #### Default type With the default values for @a NumberIntegerType (`int64_t`), the default value for @a number_integer_t is: @code {.cpp} int64_t @endcode #### Default behavior - The restrictions about leading zeros is not enforced in C++. Instead, leading zeros in integer literals lead to an interpretation as octal number. Internally, the value will be stored as decimal number. For instance, the C++ integer literal `010` will be serialized to `8`. During deserialization, leading zeros yield an error. - Not-a-number (NaN) values will be serialized to `null`. #### Limits [RFC 7159](http://rfc7159.net/rfc7159) specifies: > An implementation may set limits on the range and precision of numbers. When the default type is used, the maximal integer number that can be stored is `9223372036854775807` (INT64_MAX) and the minimal integer number that can be stored is `-9223372036854775808` (INT64_MIN). Integer numbers that are out of range will yield over/underflow when used in a constructor. During deserialization, too large or small integer numbers will be automatically be stored as @ref number_unsigned_t or @ref number_float_t. [RFC 7159](http://rfc7159.net/rfc7159) further states: > Note that when such software is used, numbers that are integers and are > in the range \f$[-2^{53}+1, 2^{53}-1]\f$ are interoperable in the sense > that implementations will agree exactly on their numeric values. As this range is a subrange of the exactly supported range [INT64_MIN, INT64_MAX], this class's integer type is interoperable. #### Storage Integer number values are stored directly inside a @ref basic_json type. @sa @ref number_float_t -- type for number values (floating-point) @sa @ref number_unsigned_t -- type for number values (unsigned integer) @since version 1.0.0 */ using number_integer_t = NumberIntegerType; /*! @brief a type for a number (unsigned) [RFC 7159](http://rfc7159.net/rfc7159) describes numbers as follows: > The representation of numbers is similar to that used in most > programming languages. A number is represented in base 10 using decimal > digits. It contains an integer component that may be prefixed with an > optional minus sign, which may be followed by a fraction part and/or an > exponent part. Leading zeros are not allowed. (...) Numeric values that > cannot be represented in the grammar below (such as Infinity and NaN) > are not permitted. This description includes both integer and floating-point numbers. However, C++ allows more precise storage if it is known whether the number is a signed integer, an unsigned integer or a floating-point number. Therefore, three different types, @ref number_integer_t, @ref number_unsigned_t and @ref number_float_t are used. To store unsigned integer numbers in C++, a type is defined by the template parameter @a NumberUnsignedType which chooses the type to use. #### Default type With the default values for @a NumberUnsignedType (`uint64_t`), the default value for @a number_unsigned_t is: @code {.cpp} uint64_t @endcode #### Default behavior - The restrictions about leading zeros is not enforced in C++. Instead, leading zeros in integer literals lead to an interpretation as octal number. Internally, the value will be stored as decimal number. For instance, the C++ integer literal `010` will be serialized to `8`. During deserialization, leading zeros yield an error. - Not-a-number (NaN) values will be serialized to `null`. #### Limits [RFC 7159](http://rfc7159.net/rfc7159) specifies: > An implementation may set limits on the range and precision of numbers. When the default type is used, the maximal integer number that can be stored is `18446744073709551615` (UINT64_MAX) and the minimal integer number that can be stored is `0`. Integer numbers that are out of range will yield over/underflow when used in a constructor. During deserialization, too large or small integer numbers will be automatically be stored as @ref number_integer_t or @ref number_float_t. [RFC 7159](http://rfc7159.net/rfc7159) further states: > Note that when such software is used, numbers that are integers and are > in the range \f$[-2^{53}+1, 2^{53}-1]\f$ are interoperable in the sense > that implementations will agree exactly on their numeric values. As this range is a subrange (when considered in conjunction with the number_integer_t type) of the exactly supported range [0, UINT64_MAX], this class's integer type is interoperable. #### Storage Integer number values are stored directly inside a @ref basic_json type. @sa @ref number_float_t -- type for number values (floating-point) @sa @ref number_integer_t -- type for number values (integer) @since version 2.0.0 */ using number_unsigned_t = NumberUnsignedType; /*! @brief a type for a number (floating-point) [RFC 7159](http://rfc7159.net/rfc7159) describes numbers as follows: > The representation of numbers is similar to that used in most > programming languages. A number is represented in base 10 using decimal > digits. It contains an integer component that may be prefixed with an > optional minus sign, which may be followed by a fraction part and/or an > exponent part. Leading zeros are not allowed. (...) Numeric values that > cannot be represented in the grammar below (such as Infinity and NaN) > are not permitted. This description includes both integer and floating-point numbers. However, C++ allows more precise storage if it is known whether the number is a signed integer, an unsigned integer or a floating-point number. Therefore, three different types, @ref number_integer_t, @ref number_unsigned_t and @ref number_float_t are used. To store floating-point numbers in C++, a type is defined by the template parameter @a NumberFloatType which chooses the type to use. #### Default type With the default values for @a NumberFloatType (`double`), the default value for @a number_float_t is: @code {.cpp} double @endcode #### Default behavior - The restrictions about leading zeros is not enforced in C++. Instead, leading zeros in floating-point literals will be ignored. Internally, the value will be stored as decimal number. For instance, the C++ floating-point literal `01.2` will be serialized to `1.2`. During deserialization, leading zeros yield an error. - Not-a-number (NaN) values will be serialized to `null`. #### Limits [RFC 7159](http://rfc7159.net/rfc7159) states: > This specification allows implementations to set limits on the range and > precision of numbers accepted. Since software that implements IEEE > 754-2008 binary64 (double precision) numbers is generally available and > widely used, good interoperability can be achieved by implementations > that expect no more precision or range than these provide, in the sense > that implementations will approximate JSON numbers within the expected > precision. This implementation does exactly follow this approach, as it uses double precision floating-point numbers. Note values smaller than `-1.79769313486232e+308` and values greater than `1.79769313486232e+308` will be stored as NaN internally and be serialized to `null`. #### Storage Floating-point number values are stored directly inside a @ref basic_json type. @sa @ref number_integer_t -- type for number values (integer) @sa @ref number_unsigned_t -- type for number values (unsigned integer) @since version 1.0.0 */ using number_float_t = NumberFloatType; /*! @brief a type for a packed binary type This type is a type designed to carry binary data that appears in various serialized formats, such as CBOR's Major Type 2, MessagePack's bin, and BSON's generic binary subtype. This type is NOT a part of standard JSON and exists solely for compatibility with these binary types. As such, it is simply defined as an ordered sequence of zero or more byte values. Additionally, as an implementation detail, the subtype of the binary data is carried around as a `std::uint8_t`, which is compatible with both of the binary data formats that use binary subtyping, (though the specific numbering is incompatible with each other, and it is up to the user to translate between them). [CBOR's RFC 7049](https://tools.ietf.org/html/rfc7049) describes this type as: > Major type 2: a byte string. The string's length in bytes is represented > following the rules for positive integers (major type 0). [MessagePack's documentation on the bin type family](https://github.com/msgpack/msgpack/blob/master/spec.md#bin-format-family) describes this type as: > Bin format family stores an byte array in 2, 3, or 5 bytes of extra bytes > in addition to the size of the byte array. [BSON's specifications](http://bsonspec.org/spec.html) describe several binary types; however, this type is intended to represent the generic binary type which has the description: > Generic binary subtype - This is the most commonly used binary subtype and > should be the 'default' for drivers and tools. None of these impose any limitations on the internal representation other than the basic unit of storage be some type of array whose parts are decomposable into bytes. The default representation of this binary format is a `std::vector`, which is a very common way to represent a byte array in modern C++. #### Default type The default values for @a BinaryType is `std::vector` #### Storage Binary Arrays are stored as pointers in a @ref basic_json type. That is, for any access to array values, a pointer of the type `binary_t*` must be dereferenced. #### Notes on subtypes - CBOR - Binary values are represented as byte strings. No subtypes are supported and will be ignored when CBOR is written. - MessagePack - If a subtype is given and the binary array contains exactly 1, 2, 4, 8, or 16 elements, the fixext family (fixext1, fixext2, fixext4, fixext8) is used. For other sizes, the ext family (ext8, ext16, ext32) is used. The subtype is then added as singed 8-bit integer. - If no subtype is given, the bin family (bin8, bin16, bin32) is used. - BSON - If a subtype is given, it is used and added as unsigned 8-bit integer. - If no subtype is given, the generic binary subtype 0x00 is used. @sa @ref binary -- create a binary array @since version 3.8.0 */ using binary_t = nlohmann::byte_container_with_subtype; /// @} private: /// helper for exception-safe object creation template JSON_HEDLEY_RETURNS_NON_NULL static T* create(Args&& ... args) { AllocatorType alloc; using AllocatorTraits = std::allocator_traits>; auto deleter = [&](T * object) { AllocatorTraits::deallocate(alloc, object, 1); }; std::unique_ptr object(AllocatorTraits::allocate(alloc, 1), deleter); AllocatorTraits::construct(alloc, object.get(), std::forward(args)...); JSON_ASSERT(object != nullptr); return object.release(); } //////////////////////// // JSON value storage // //////////////////////// /*! @brief a JSON value The actual storage for a JSON value of the @ref basic_json class. This union combines the different storage types for the JSON value types defined in @ref value_t. JSON type | value_t type | used type --------- | --------------- | ------------------------ object | object | pointer to @ref object_t array | array | pointer to @ref array_t string | string | pointer to @ref string_t boolean | boolean | @ref boolean_t number | number_integer | @ref number_integer_t number | number_unsigned | @ref number_unsigned_t number | number_float | @ref number_float_t binary | binary | pointer to @ref binary_t null | null | *no value is stored* @note Variable-length types (objects, arrays, and strings) are stored as pointers. The size of the union should not exceed 64 bits if the default value types are used. @since version 1.0.0 */ union json_value { /// object (stored with pointer to save storage) object_t* object; /// array (stored with pointer to save storage) array_t* array; /// string (stored with pointer to save storage) string_t* string; /// binary (stored with pointer to save storage) binary_t* binary; /// boolean boolean_t boolean; /// number (integer) number_integer_t number_integer; /// number (unsigned integer) number_unsigned_t number_unsigned; /// number (floating-point) number_float_t number_float; /// default constructor (for null values) json_value() = default; /// constructor for booleans json_value(boolean_t v) noexcept : boolean(v) {} /// constructor for numbers (integer) json_value(number_integer_t v) noexcept : number_integer(v) {} /// constructor for numbers (unsigned) json_value(number_unsigned_t v) noexcept : number_unsigned(v) {} /// constructor for numbers (floating-point) json_value(number_float_t v) noexcept : number_float(v) {} /// constructor for empty values of a given type json_value(value_t t) { switch (t) { case value_t::object: { object = create(); break; } case value_t::array: { array = create(); break; } case value_t::string: { string = create(""); break; } case value_t::binary: { binary = create(); break; } case value_t::boolean: { boolean = boolean_t(false); break; } case value_t::number_integer: { number_integer = number_integer_t(0); break; } case value_t::number_unsigned: { number_unsigned = number_unsigned_t(0); break; } case value_t::number_float: { number_float = number_float_t(0.0); break; } case value_t::null: { object = nullptr; // silence warning, see #821 break; } default: { object = nullptr; // silence warning, see #821 if (JSON_HEDLEY_UNLIKELY(t == value_t::null)) { JSON_THROW(other_error::create(500, "961c151d2e87f2686a955a9be24d316f1362bf21 3.9.1")); // LCOV_EXCL_LINE } break; } } } /// constructor for strings json_value(const string_t& value) { string = create(value); } /// constructor for rvalue strings json_value(string_t&& value) { string = create(std::move(value)); } /// constructor for objects json_value(const object_t& value) { object = create(value); } /// constructor for rvalue objects json_value(object_t&& value) { object = create(std::move(value)); } /// constructor for arrays json_value(const array_t& value) { array = create(value); } /// constructor for rvalue arrays json_value(array_t&& value) { array = create(std::move(value)); } /// constructor for binary arrays json_value(const typename binary_t::container_type& value) { binary = create(value); } /// constructor for rvalue binary arrays json_value(typename binary_t::container_type&& value) { binary = create(std::move(value)); } /// constructor for binary arrays (internal type) json_value(const binary_t& value) { binary = create(value); } /// constructor for rvalue binary arrays (internal type) json_value(binary_t&& value) { binary = create(std::move(value)); } void destroy(value_t t) noexcept { // flatten the current json_value to a heap-allocated stack std::vector stack; // move the top-level items to stack if (t == value_t::array) { stack.reserve(array->size()); std::move(array->begin(), array->end(), std::back_inserter(stack)); } else if (t == value_t::object) { stack.reserve(object->size()); for (auto&& it : *object) { stack.push_back(std::move(it.second)); } } while (!stack.empty()) { // move the last item to local variable to be processed basic_json current_item(std::move(stack.back())); stack.pop_back(); // if current_item is array/object, move // its children to the stack to be processed later if (current_item.is_array()) { std::move(current_item.m_value.array->begin(), current_item.m_value.array->end(), std::back_inserter(stack)); current_item.m_value.array->clear(); } else if (current_item.is_object()) { for (auto&& it : *current_item.m_value.object) { stack.push_back(std::move(it.second)); } current_item.m_value.object->clear(); } // it's now safe that current_item get destructed // since it doesn't have any children } switch (t) { case value_t::object: { AllocatorType alloc; std::allocator_traits::destroy(alloc, object); std::allocator_traits::deallocate(alloc, object, 1); break; } case value_t::array: { AllocatorType alloc; std::allocator_traits::destroy(alloc, array); std::allocator_traits::deallocate(alloc, array, 1); break; } case value_t::string: { AllocatorType alloc; std::allocator_traits::destroy(alloc, string); std::allocator_traits::deallocate(alloc, string, 1); break; } case value_t::binary: { AllocatorType alloc; std::allocator_traits::destroy(alloc, binary); std::allocator_traits::deallocate(alloc, binary, 1); break; } default: { break; } } } }; /*! @brief checks the class invariants This function asserts the class invariants. It needs to be called at the end of every constructor to make sure that created objects respect the invariant. Furthermore, it has to be called each time the type of a JSON value is changed, because the invariant expresses a relationship between @a m_type and @a m_value. */ void assert_invariant() const noexcept { JSON_ASSERT(m_type != value_t::object || m_value.object != nullptr); JSON_ASSERT(m_type != value_t::array || m_value.array != nullptr); JSON_ASSERT(m_type != value_t::string || m_value.string != nullptr); JSON_ASSERT(m_type != value_t::binary || m_value.binary != nullptr); } public: ////////////////////////// // JSON parser callback // ////////////////////////// /*! @brief parser event types The parser callback distinguishes the following events: - `object_start`: the parser read `{` and started to process a JSON object - `key`: the parser read a key of a value in an object - `object_end`: the parser read `}` and finished processing a JSON object - `array_start`: the parser read `[` and started to process a JSON array - `array_end`: the parser read `]` and finished processing a JSON array - `value`: the parser finished reading a JSON value @image html callback_events.png "Example when certain parse events are triggered" @sa @ref parser_callback_t for more information and examples */ using parse_event_t = detail::parse_event_t; /*! @brief per-element parser callback type With a parser callback function, the result of parsing a JSON text can be influenced. When passed to @ref parse, it is called on certain events (passed as @ref parse_event_t via parameter @a event) with a set recursion depth @a depth and context JSON value @a parsed. The return value of the callback function is a boolean indicating whether the element that emitted the callback shall be kept or not. We distinguish six scenarios (determined by the event type) in which the callback function can be called. The following table describes the values of the parameters @a depth, @a event, and @a parsed. parameter @a event | description | parameter @a depth | parameter @a parsed ------------------ | ----------- | ------------------ | ------------------- parse_event_t::object_start | the parser read `{` and started to process a JSON object | depth of the parent of the JSON object | a JSON value with type discarded parse_event_t::key | the parser read a key of a value in an object | depth of the currently parsed JSON object | a JSON string containing the key parse_event_t::object_end | the parser read `}` and finished processing a JSON object | depth of the parent of the JSON object | the parsed JSON object parse_event_t::array_start | the parser read `[` and started to process a JSON array | depth of the parent of the JSON array | a JSON value with type discarded parse_event_t::array_end | the parser read `]` and finished processing a JSON array | depth of the parent of the JSON array | the parsed JSON array parse_event_t::value | the parser finished reading a JSON value | depth of the value | the parsed JSON value @image html callback_events.png "Example when certain parse events are triggered" Discarding a value (i.e., returning `false`) has different effects depending on the context in which function was called: - Discarded values in structured types are skipped. That is, the parser will behave as if the discarded value was never read. - In case a value outside a structured type is skipped, it is replaced with `null`. This case happens if the top-level element is skipped. @param[in] depth the depth of the recursion during parsing @param[in] event an event of type parse_event_t indicating the context in the callback function has been called @param[in,out] parsed the current intermediate parse result; note that writing to this value has no effect for parse_event_t::key events @return Whether the JSON value which called the function during parsing should be kept (`true`) or not (`false`). In the latter case, it is either skipped completely or replaced by an empty discarded object. @sa @ref parse for examples @since version 1.0.0 */ using parser_callback_t = detail::parser_callback_t; ////////////////// // constructors // ////////////////// /// @name constructors and destructors /// Constructors of class @ref basic_json, copy/move constructor, copy /// assignment, static functions creating objects, and the destructor. /// @{ /*! @brief create an empty value with a given type Create an empty JSON value with a given type. The value will be default initialized with an empty value which depends on the type: Value type | initial value ----------- | ------------- null | `null` boolean | `false` string | `""` number | `0` object | `{}` array | `[]` binary | empty array @param[in] v the type of the value to create @complexity Constant. @exceptionsafety Strong guarantee: if an exception is thrown, there are no changes to any JSON value. @liveexample{The following code shows the constructor for different @ref value_t values,basic_json__value_t} @sa @ref clear() -- restores the postcondition of this constructor @since version 1.0.0 */ basic_json(const value_t v) : m_type(v), m_value(v) { assert_invariant(); } /*! @brief create a null object Create a `null` JSON value. It either takes a null pointer as parameter (explicitly creating `null`) or no parameter (implicitly creating `null`). The passed null pointer itself is not read -- it is only used to choose the right constructor. @complexity Constant. @exceptionsafety No-throw guarantee: this constructor never throws exceptions. @liveexample{The following code shows the constructor with and without a null pointer parameter.,basic_json__nullptr_t} @since version 1.0.0 */ basic_json(std::nullptr_t = nullptr) noexcept : basic_json(value_t::null) { assert_invariant(); } /*! @brief create a JSON value This is a "catch all" constructor for all compatible JSON types; that is, types for which a `to_json()` method exists. The constructor forwards the parameter @a val to that method (to `json_serializer::to_json` method with `U = uncvref_t`, to be exact). Template type @a CompatibleType includes, but is not limited to, the following types: - **arrays**: @ref array_t and all kinds of compatible containers such as `std::vector`, `std::deque`, `std::list`, `std::forward_list`, `std::array`, `std::valarray`, `std::set`, `std::unordered_set`, `std::multiset`, and `std::unordered_multiset` with a `value_type` from which a @ref basic_json value can be constructed. - **objects**: @ref object_t and all kinds of compatible associative containers such as `std::map`, `std::unordered_map`, `std::multimap`, and `std::unordered_multimap` with a `key_type` compatible to @ref string_t and a `value_type` from which a @ref basic_json value can be constructed. - **strings**: @ref string_t, string literals, and all compatible string containers can be used. - **numbers**: @ref number_integer_t, @ref number_unsigned_t, @ref number_float_t, and all convertible number types such as `int`, `size_t`, `int64_t`, `float` or `double` can be used. - **boolean**: @ref boolean_t / `bool` can be used. - **binary**: @ref binary_t / `std::vector` may be used, unfortunately because string literals cannot be distinguished from binary character arrays by the C++ type system, all types compatible with `const char*` will be directed to the string constructor instead. This is both for backwards compatibility, and due to the fact that a binary type is not a standard JSON type. See the examples below. @tparam CompatibleType a type such that: - @a CompatibleType is not derived from `std::istream`, - @a CompatibleType is not @ref basic_json (to avoid hijacking copy/move constructors), - @a CompatibleType is not a different @ref basic_json type (i.e. with different template arguments) - @a CompatibleType is not a @ref basic_json nested type (e.g., @ref json_pointer, @ref iterator, etc ...) - @ref @ref json_serializer has a `to_json(basic_json_t&, CompatibleType&&)` method @tparam U = `uncvref_t` @param[in] val the value to be forwarded to the respective constructor @complexity Usually linear in the size of the passed @a val, also depending on the implementation of the called `to_json()` method. @exceptionsafety Depends on the called constructor. For types directly supported by the library (i.e., all types for which no `to_json()` function was provided), strong guarantee holds: if an exception is thrown, there are no changes to any JSON value. @liveexample{The following code shows the constructor with several compatible types.,basic_json__CompatibleType} @since version 2.1.0 */ template < typename CompatibleType, typename U = detail::uncvref_t, detail::enable_if_t < !detail::is_basic_json::value && detail::is_compatible_type::value, int > = 0 > basic_json(CompatibleType && val) noexcept(noexcept( JSONSerializer::to_json(std::declval(), std::forward(val)))) { JSONSerializer::to_json(*this, std::forward(val)); assert_invariant(); } /*! @brief create a JSON value from an existing one This is a constructor for existing @ref basic_json types. It does not hijack copy/move constructors, since the parameter has different template arguments than the current ones. The constructor tries to convert the internal @ref m_value of the parameter. @tparam BasicJsonType a type such that: - @a BasicJsonType is a @ref basic_json type. - @a BasicJsonType has different template arguments than @ref basic_json_t. @param[in] val the @ref basic_json value to be converted. @complexity Usually linear in the size of the passed @a val, also depending on the implementation of the called `to_json()` method. @exceptionsafety Depends on the called constructor. For types directly supported by the library (i.e., all types for which no `to_json()` function was provided), strong guarantee holds: if an exception is thrown, there are no changes to any JSON value. @since version 3.2.0 */ template < typename BasicJsonType, detail::enable_if_t < detail::is_basic_json::value&& !std::is_same::value, int > = 0 > basic_json(const BasicJsonType& val) { using other_boolean_t = typename BasicJsonType::boolean_t; using other_number_float_t = typename BasicJsonType::number_float_t; using other_number_integer_t = typename BasicJsonType::number_integer_t; using other_number_unsigned_t = typename BasicJsonType::number_unsigned_t; using other_string_t = typename BasicJsonType::string_t; using other_object_t = typename BasicJsonType::object_t; using other_array_t = typename BasicJsonType::array_t; using other_binary_t = typename BasicJsonType::binary_t; switch (val.type()) { case value_t::boolean: JSONSerializer::to_json(*this, val.template get()); break; case value_t::number_float: JSONSerializer::to_json(*this, val.template get()); break; case value_t::number_integer: JSONSerializer::to_json(*this, val.template get()); break; case value_t::number_unsigned: JSONSerializer::to_json(*this, val.template get()); break; case value_t::string: JSONSerializer::to_json(*this, val.template get_ref()); break; case value_t::object: JSONSerializer::to_json(*this, val.template get_ref()); break; case value_t::array: JSONSerializer::to_json(*this, val.template get_ref()); break; case value_t::binary: JSONSerializer::to_json(*this, val.template get_ref()); break; case value_t::null: *this = nullptr; break; case value_t::discarded: m_type = value_t::discarded; break; default: // LCOV_EXCL_LINE JSON_ASSERT(false); // LCOV_EXCL_LINE } assert_invariant(); } /*! @brief create a container (array or object) from an initializer list Creates a JSON value of type array or object from the passed initializer list @a init. In case @a type_deduction is `true` (default), the type of the JSON value to be created is deducted from the initializer list @a init according to the following rules: 1. If the list is empty, an empty JSON object value `{}` is created. 2. If the list consists of pairs whose first element is a string, a JSON object value is created where the first elements of the pairs are treated as keys and the second elements are as values. 3. In all other cases, an array is created. The rules aim to create the best fit between a C++ initializer list and JSON values. The rationale is as follows: 1. The empty initializer list is written as `{}` which is exactly an empty JSON object. 2. C++ has no way of describing mapped types other than to list a list of pairs. As JSON requires that keys must be of type string, rule 2 is the weakest constraint one can pose on initializer lists to interpret them as an object. 3. In all other cases, the initializer list could not be interpreted as JSON object type, so interpreting it as JSON array type is safe. With the rules described above, the following JSON values cannot be expressed by an initializer list: - the empty array (`[]`): use @ref array(initializer_list_t) with an empty initializer list in this case - arrays whose elements satisfy rule 2: use @ref array(initializer_list_t) with the same initializer list in this case @note When used without parentheses around an empty initializer list, @ref basic_json() is called instead of this function, yielding the JSON null value. @param[in] init initializer list with JSON values @param[in] type_deduction internal parameter; when set to `true`, the type of the JSON value is deducted from the initializer list @a init; when set to `false`, the type provided via @a manual_type is forced. This mode is used by the functions @ref array(initializer_list_t) and @ref object(initializer_list_t). @param[in] manual_type internal parameter; when @a type_deduction is set to `false`, the created JSON value will use the provided type (only @ref value_t::array and @ref value_t::object are valid); when @a type_deduction is set to `true`, this parameter has no effect @throw type_error.301 if @a type_deduction is `false`, @a manual_type is `value_t::object`, but @a init contains an element which is not a pair whose first element is a string. In this case, the constructor could not create an object. If @a type_deduction would have be `true`, an array would have been created. See @ref object(initializer_list_t) for an example. @complexity Linear in the size of the initializer list @a init. @exceptionsafety Strong guarantee: if an exception is thrown, there are no changes to any JSON value. @liveexample{The example below shows how JSON values are created from initializer lists.,basic_json__list_init_t} @sa @ref array(initializer_list_t) -- create a JSON array value from an initializer list @sa @ref object(initializer_list_t) -- create a JSON object value from an initializer list @since version 1.0.0 */ basic_json(initializer_list_t init, bool type_deduction = true, value_t manual_type = value_t::array) { // check if each element is an array with two elements whose first // element is a string bool is_an_object = std::all_of(init.begin(), init.end(), [](const detail::json_ref& element_ref) { return element_ref->is_array() && element_ref->size() == 2 && (*element_ref)[0].is_string(); }); // adjust type if type deduction is not wanted if (!type_deduction) { // if array is wanted, do not create an object though possible if (manual_type == value_t::array) { is_an_object = false; } // if object is wanted but impossible, throw an exception if (JSON_HEDLEY_UNLIKELY(manual_type == value_t::object && !is_an_object)) { JSON_THROW(type_error::create(301, "cannot create object from initializer list")); } } if (is_an_object) { // the initializer list is a list of pairs -> create object m_type = value_t::object; m_value = value_t::object; std::for_each(init.begin(), init.end(), [this](const detail::json_ref& element_ref) { auto element = element_ref.moved_or_copied(); m_value.object->emplace( std::move(*((*element.m_value.array)[0].m_value.string)), std::move((*element.m_value.array)[1])); }); } else { // the initializer list describes an array -> create array m_type = value_t::array; m_value.array = create(init.begin(), init.end()); } assert_invariant(); } /*! @brief explicitly create a binary array (without subtype) Creates a JSON binary array value from a given binary container. Binary values are part of various binary formats, such as CBOR, MessagePack, and BSON. This constructor is used to create a value for serialization to those formats. @note Note, this function exists because of the difficulty in correctly specifying the correct template overload in the standard value ctor, as both JSON arrays and JSON binary arrays are backed with some form of a `std::vector`. Because JSON binary arrays are a non-standard extension it was decided that it would be best to prevent automatic initialization of a binary array type, for backwards compatibility and so it does not happen on accident. @param[in] init container containing bytes to use as binary type @return JSON binary array value @complexity Linear in the size of @a init. @exceptionsafety Strong guarantee: if an exception is thrown, there are no changes to any JSON value. @since version 3.8.0 */ JSON_HEDLEY_WARN_UNUSED_RESULT static basic_json binary(const typename binary_t::container_type& init) { auto res = basic_json(); res.m_type = value_t::binary; res.m_value = init; return res; } /*! @brief explicitly create a binary array (with subtype) Creates a JSON binary array value from a given binary container. Binary values are part of various binary formats, such as CBOR, MessagePack, and BSON. This constructor is used to create a value for serialization to those formats. @note Note, this function exists because of the difficulty in correctly specifying the correct template overload in the standard value ctor, as both JSON arrays and JSON binary arrays are backed with some form of a `std::vector`. Because JSON binary arrays are a non-standard extension it was decided that it would be best to prevent automatic initialization of a binary array type, for backwards compatibility and so it does not happen on accident. @param[in] init container containing bytes to use as binary type @param[in] subtype subtype to use in MessagePack and BSON @return JSON binary array value @complexity Linear in the size of @a init. @exceptionsafety Strong guarantee: if an exception is thrown, there are no changes to any JSON value. @since version 3.8.0 */ JSON_HEDLEY_WARN_UNUSED_RESULT static basic_json binary(const typename binary_t::container_type& init, std::uint8_t subtype) { auto res = basic_json(); res.m_type = value_t::binary; res.m_value = binary_t(init, subtype); return res; } /// @copydoc binary(const typename binary_t::container_type&) JSON_HEDLEY_WARN_UNUSED_RESULT static basic_json binary(typename binary_t::container_type&& init) { auto res = basic_json(); res.m_type = value_t::binary; res.m_value = std::move(init); return res; } /// @copydoc binary(const typename binary_t::container_type&, std::uint8_t) JSON_HEDLEY_WARN_UNUSED_RESULT static basic_json binary(typename binary_t::container_type&& init, std::uint8_t subtype) { auto res = basic_json(); res.m_type = value_t::binary; res.m_value = binary_t(std::move(init), subtype); return res; } /*! @brief explicitly create an array from an initializer list Creates a JSON array value from a given initializer list. That is, given a list of values `a, b, c`, creates the JSON value `[a, b, c]`. If the initializer list is empty, the empty array `[]` is created. @note This function is only needed to express two edge cases that cannot be realized with the initializer list constructor (@ref basic_json(initializer_list_t, bool, value_t)). These cases are: 1. creating an array whose elements are all pairs whose first element is a string -- in this case, the initializer list constructor would create an object, taking the first elements as keys 2. creating an empty array -- passing the empty initializer list to the initializer list constructor yields an empty object @param[in] init initializer list with JSON values to create an array from (optional) @return JSON array value @complexity Linear in the size of @a init. @exceptionsafety Strong guarantee: if an exception is thrown, there are no changes to any JSON value. @liveexample{The following code shows an example for the `array` function.,array} @sa @ref basic_json(initializer_list_t, bool, value_t) -- create a JSON value from an initializer list @sa @ref object(initializer_list_t) -- create a JSON object value from an initializer list @since version 1.0.0 */ JSON_HEDLEY_WARN_UNUSED_RESULT static basic_json array(initializer_list_t init = {}) { return basic_json(init, false, value_t::array); } /*! @brief explicitly create an object from an initializer list Creates a JSON object value from a given initializer list. The initializer lists elements must be pairs, and their first elements must be strings. If the initializer list is empty, the empty object `{}` is created. @note This function is only added for symmetry reasons. In contrast to the related function @ref array(initializer_list_t), there are no cases which can only be expressed by this function. That is, any initializer list @a init can also be passed to the initializer list constructor @ref basic_json(initializer_list_t, bool, value_t). @param[in] init initializer list to create an object from (optional) @return JSON object value @throw type_error.301 if @a init is not a list of pairs whose first elements are strings. In this case, no object can be created. When such a value is passed to @ref basic_json(initializer_list_t, bool, value_t), an array would have been created from the passed initializer list @a init. See example below. @complexity Linear in the size of @a init. @exceptionsafety Strong guarantee: if an exception is thrown, there are no changes to any JSON value. @liveexample{The following code shows an example for the `object` function.,object} @sa @ref basic_json(initializer_list_t, bool, value_t) -- create a JSON value from an initializer list @sa @ref array(initializer_list_t) -- create a JSON array value from an initializer list @since version 1.0.0 */ JSON_HEDLEY_WARN_UNUSED_RESULT static basic_json object(initializer_list_t init = {}) { return basic_json(init, false, value_t::object); } /*! @brief construct an array with count copies of given value Constructs a JSON array value by creating @a cnt copies of a passed value. In case @a cnt is `0`, an empty array is created. @param[in] cnt the number of JSON copies of @a val to create @param[in] val the JSON value to copy @post `std::distance(begin(),end()) == cnt` holds. @complexity Linear in @a cnt. @exceptionsafety Strong guarantee: if an exception is thrown, there are no changes to any JSON value. @liveexample{The following code shows examples for the @ref basic_json(size_type\, const basic_json&) constructor.,basic_json__size_type_basic_json} @since version 1.0.0 */ basic_json(size_type cnt, const basic_json& val) : m_type(value_t::array) { m_value.array = create(cnt, val); assert_invariant(); } /*! @brief construct a JSON container given an iterator range Constructs the JSON value with the contents of the range `[first, last)`. The semantics depends on the different types a JSON value can have: - In case of a null type, invalid_iterator.206 is thrown. - In case of other primitive types (number, boolean, or string), @a first must be `begin()` and @a last must be `end()`. In this case, the value is copied. Otherwise, invalid_iterator.204 is thrown. - In case of structured types (array, object), the constructor behaves as similar versions for `std::vector` or `std::map`; that is, a JSON array or object is constructed from the values in the range. @tparam InputIT an input iterator type (@ref iterator or @ref const_iterator) @param[in] first begin of the range to copy from (included) @param[in] last end of the range to copy from (excluded) @pre Iterators @a first and @a last must be initialized. **This precondition is enforced with an assertion (see warning).** If assertions are switched off, a violation of this precondition yields undefined behavior. @pre Range `[first, last)` is valid. Usually, this precondition cannot be checked efficiently. Only certain edge cases are detected; see the description of the exceptions below. A violation of this precondition yields undefined behavior. @warning A precondition is enforced with a runtime assertion that will result in calling `std::abort` if this precondition is not met. Assertions can be disabled by defining `NDEBUG` at compile time. See https://en.cppreference.com/w/cpp/error/assert for more information. @throw invalid_iterator.201 if iterators @a first and @a last are not compatible (i.e., do not belong to the same JSON value). In this case, the range `[first, last)` is undefined. @throw invalid_iterator.204 if iterators @a first and @a last belong to a primitive type (number, boolean, or string), but @a first does not point to the first element any more. In this case, the range `[first, last)` is undefined. See example code below. @throw invalid_iterator.206 if iterators @a first and @a last belong to a null value. In this case, the range `[first, last)` is undefined. @complexity Linear in distance between @a first and @a last. @exceptionsafety Strong guarantee: if an exception is thrown, there are no changes to any JSON value. @liveexample{The example below shows several ways to create JSON values by specifying a subrange with iterators.,basic_json__InputIt_InputIt} @since version 1.0.0 */ template < class InputIT, typename std::enable_if < std::is_same::value || std::is_same::value, int >::type = 0 > basic_json(InputIT first, InputIT last) { JSON_ASSERT(first.m_object != nullptr); JSON_ASSERT(last.m_object != nullptr); // make sure iterator fits the current value if (JSON_HEDLEY_UNLIKELY(first.m_object != last.m_object)) { JSON_THROW(invalid_iterator::create(201, "iterators are not compatible")); } // copy type from first iterator m_type = first.m_object->m_type; // check if iterator range is complete for primitive values switch (m_type) { case value_t::boolean: case value_t::number_float: case value_t::number_integer: case value_t::number_unsigned: case value_t::string: { if (JSON_HEDLEY_UNLIKELY(!first.m_it.primitive_iterator.is_begin() || !last.m_it.primitive_iterator.is_end())) { JSON_THROW(invalid_iterator::create(204, "iterators out of range")); } break; } default: break; } switch (m_type) { case value_t::number_integer: { m_value.number_integer = first.m_object->m_value.number_integer; break; } case value_t::number_unsigned: { m_value.number_unsigned = first.m_object->m_value.number_unsigned; break; } case value_t::number_float: { m_value.number_float = first.m_object->m_value.number_float; break; } case value_t::boolean: { m_value.boolean = first.m_object->m_value.boolean; break; } case value_t::string: { m_value = *first.m_object->m_value.string; break; } case value_t::object: { m_value.object = create(first.m_it.object_iterator, last.m_it.object_iterator); break; } case value_t::array: { m_value.array = create(first.m_it.array_iterator, last.m_it.array_iterator); break; } case value_t::binary: { m_value = *first.m_object->m_value.binary; break; } default: JSON_THROW(invalid_iterator::create(206, "cannot construct with iterators from " + std::string(first.m_object->type_name()))); } assert_invariant(); } /////////////////////////////////////// // other constructors and destructor // /////////////////////////////////////// template, std::is_same>::value, int> = 0 > basic_json(const JsonRef& ref) : basic_json(ref.moved_or_copied()) {} /*! @brief copy constructor Creates a copy of a given JSON value. @param[in] other the JSON value to copy @post `*this == other` @complexity Linear in the size of @a other. @exceptionsafety Strong guarantee: if an exception is thrown, there are no changes to any JSON value. @requirement This function helps `basic_json` satisfying the [Container](https://en.cppreference.com/w/cpp/named_req/Container) requirements: - The complexity is linear. - As postcondition, it holds: `other == basic_json(other)`. @liveexample{The following code shows an example for the copy constructor.,basic_json__basic_json} @since version 1.0.0 */ basic_json(const basic_json& other) : m_type(other.m_type) { // check of passed value is valid other.assert_invariant(); switch (m_type) { case value_t::object: { m_value = *other.m_value.object; break; } case value_t::array: { m_value = *other.m_value.array; break; } case value_t::string: { m_value = *other.m_value.string; break; } case value_t::boolean: { m_value = other.m_value.boolean; break; } case value_t::number_integer: { m_value = other.m_value.number_integer; break; } case value_t::number_unsigned: { m_value = other.m_value.number_unsigned; break; } case value_t::number_float: { m_value = other.m_value.number_float; break; } case value_t::binary: { m_value = *other.m_value.binary; break; } default: break; } assert_invariant(); } /*! @brief move constructor Move constructor. Constructs a JSON value with the contents of the given value @a other using move semantics. It "steals" the resources from @a other and leaves it as JSON null value. @param[in,out] other value to move to this object @post `*this` has the same value as @a other before the call. @post @a other is a JSON null value. @complexity Constant. @exceptionsafety No-throw guarantee: this constructor never throws exceptions. @requirement This function helps `basic_json` satisfying the [MoveConstructible](https://en.cppreference.com/w/cpp/named_req/MoveConstructible) requirements. @liveexample{The code below shows the move constructor explicitly called via std::move.,basic_json__moveconstructor} @since version 1.0.0 */ basic_json(basic_json&& other) noexcept : m_type(std::move(other.m_type)), m_value(std::move(other.m_value)) { // check that passed value is valid other.assert_invariant(); // invalidate payload other.m_type = value_t::null; other.m_value = {}; assert_invariant(); } /*! @brief copy assignment Copy assignment operator. Copies a JSON value via the "copy and swap" strategy: It is expressed in terms of the copy constructor, destructor, and the `swap()` member function. @param[in] other value to copy from @complexity Linear. @requirement This function helps `basic_json` satisfying the [Container](https://en.cppreference.com/w/cpp/named_req/Container) requirements: - The complexity is linear. @liveexample{The code below shows and example for the copy assignment. It creates a copy of value `a` which is then swapped with `b`. Finally\, the copy of `a` (which is the null value after the swap) is destroyed.,basic_json__copyassignment} @since version 1.0.0 */ basic_json& operator=(basic_json other) noexcept ( std::is_nothrow_move_constructible::value&& std::is_nothrow_move_assignable::value&& std::is_nothrow_move_constructible::value&& std::is_nothrow_move_assignable::value ) { // check that passed value is valid other.assert_invariant(); using std::swap; swap(m_type, other.m_type); swap(m_value, other.m_value); assert_invariant(); return *this; } /*! @brief destructor Destroys the JSON value and frees all allocated memory. @complexity Linear. @requirement This function helps `basic_json` satisfying the [Container](https://en.cppreference.com/w/cpp/named_req/Container) requirements: - The complexity is linear. - All stored elements are destroyed and all memory is freed. @since version 1.0.0 */ ~basic_json() noexcept { assert_invariant(); m_value.destroy(m_type); } /// @} public: /////////////////////// // object inspection // /////////////////////// /// @name object inspection /// Functions to inspect the type of a JSON value. /// @{ /*! @brief serialization Serialization function for JSON values. The function tries to mimic Python's `json.dumps()` function, and currently supports its @a indent and @a ensure_ascii parameters. @param[in] indent If indent is nonnegative, then array elements and object members will be pretty-printed with that indent level. An indent level of `0` will only insert newlines. `-1` (the default) selects the most compact representation. @param[in] indent_char The character to use for indentation if @a indent is greater than `0`. The default is ` ` (space). @param[in] ensure_ascii If @a ensure_ascii is true, all non-ASCII characters in the output are escaped with `\uXXXX` sequences, and the result consists of ASCII characters only. @param[in] error_handler how to react on decoding errors; there are three possible values: `strict` (throws and exception in case a decoding error occurs; default), `replace` (replace invalid UTF-8 sequences with U+FFFD), and `ignore` (ignore invalid UTF-8 sequences during serialization; all bytes are copied to the output unchanged). @return string containing the serialization of the JSON value @throw type_error.316 if a string stored inside the JSON value is not UTF-8 encoded and @a error_handler is set to strict @note Binary values are serialized as object containing two keys: - "bytes": an array of bytes as integers - "subtype": the subtype as integer or "null" if the binary has no subtype @complexity Linear. @exceptionsafety Strong guarantee: if an exception is thrown, there are no changes in the JSON value. @liveexample{The following example shows the effect of different @a indent\, @a indent_char\, and @a ensure_ascii parameters to the result of the serialization.,dump} @see https://docs.python.org/2/library/json.html#json.dump @since version 1.0.0; indentation character @a indent_char, option @a ensure_ascii and exceptions added in version 3.0.0; error handlers added in version 3.4.0; serialization of binary values added in version 3.8.0. */ string_t dump(const int indent = -1, const char indent_char = ' ', const bool ensure_ascii = false, const error_handler_t error_handler = error_handler_t::strict) const { string_t result; serializer s(detail::output_adapter(result), indent_char, error_handler); if (indent >= 0) { s.dump(*this, true, ensure_ascii, static_cast(indent)); } else { s.dump(*this, false, ensure_ascii, 0); } return result; } /*! @brief return the type of the JSON value (explicit) Return the type of the JSON value as a value from the @ref value_t enumeration. @return the type of the JSON value Value type | return value ------------------------- | ------------------------- null | value_t::null boolean | value_t::boolean string | value_t::string number (integer) | value_t::number_integer number (unsigned integer) | value_t::number_unsigned number (floating-point) | value_t::number_float object | value_t::object array | value_t::array binary | value_t::binary discarded | value_t::discarded @complexity Constant. @exceptionsafety No-throw guarantee: this member function never throws exceptions. @liveexample{The following code exemplifies `type()` for all JSON types.,type} @sa @ref operator value_t() -- return the type of the JSON value (implicit) @sa @ref type_name() -- return the type as string @since version 1.0.0 */ constexpr value_t type() const noexcept { return m_type; } /*! @brief return whether type is primitive This function returns true if and only if the JSON type is primitive (string, number, boolean, or null). @return `true` if type is primitive (string, number, boolean, or null), `false` otherwise. @complexity Constant. @exceptionsafety No-throw guarantee: this member function never throws exceptions. @liveexample{The following code exemplifies `is_primitive()` for all JSON types.,is_primitive} @sa @ref is_structured() -- returns whether JSON value is structured @sa @ref is_null() -- returns whether JSON value is `null` @sa @ref is_string() -- returns whether JSON value is a string @sa @ref is_boolean() -- returns whether JSON value is a boolean @sa @ref is_number() -- returns whether JSON value is a number @sa @ref is_binary() -- returns whether JSON value is a binary array @since version 1.0.0 */ constexpr bool is_primitive() const noexcept { return is_null() || is_string() || is_boolean() || is_number() || is_binary(); } /*! @brief return whether type is structured This function returns true if and only if the JSON type is structured (array or object). @return `true` if type is structured (array or object), `false` otherwise. @complexity Constant. @exceptionsafety No-throw guarantee: this member function never throws exceptions. @liveexample{The following code exemplifies `is_structured()` for all JSON types.,is_structured} @sa @ref is_primitive() -- returns whether value is primitive @sa @ref is_array() -- returns whether value is an array @sa @ref is_object() -- returns whether value is an object @since version 1.0.0 */ constexpr bool is_structured() const noexcept { return is_array() || is_object(); } /*! @brief return whether value is null This function returns true if and only if the JSON value is null. @return `true` if type is null, `false` otherwise. @complexity Constant. @exceptionsafety No-throw guarantee: this member function never throws exceptions. @liveexample{The following code exemplifies `is_null()` for all JSON types.,is_null} @since version 1.0.0 */ constexpr bool is_null() const noexcept { return m_type == value_t::null; } /*! @brief return whether value is a boolean This function returns true if and only if the JSON value is a boolean. @return `true` if type is boolean, `false` otherwise. @complexity Constant. @exceptionsafety No-throw guarantee: this member function never throws exceptions. @liveexample{The following code exemplifies `is_boolean()` for all JSON types.,is_boolean} @since version 1.0.0 */ constexpr bool is_boolean() const noexcept { return m_type == value_t::boolean; } /*! @brief return whether value is a number This function returns true if and only if the JSON value is a number. This includes both integer (signed and unsigned) and floating-point values. @return `true` if type is number (regardless whether integer, unsigned integer or floating-type), `false` otherwise. @complexity Constant. @exceptionsafety No-throw guarantee: this member function never throws exceptions. @liveexample{The following code exemplifies `is_number()` for all JSON types.,is_number} @sa @ref is_number_integer() -- check if value is an integer or unsigned integer number @sa @ref is_number_unsigned() -- check if value is an unsigned integer number @sa @ref is_number_float() -- check if value is a floating-point number @since version 1.0.0 */ constexpr bool is_number() const noexcept { return is_number_integer() || is_number_float(); } /*! @brief return whether value is an integer number This function returns true if and only if the JSON value is a signed or unsigned integer number. This excludes floating-point values. @return `true` if type is an integer or unsigned integer number, `false` otherwise. @complexity Constant. @exceptionsafety No-throw guarantee: this member function never throws exceptions. @liveexample{The following code exemplifies `is_number_integer()` for all JSON types.,is_number_integer} @sa @ref is_number() -- check if value is a number @sa @ref is_number_unsigned() -- check if value is an unsigned integer number @sa @ref is_number_float() -- check if value is a floating-point number @since version 1.0.0 */ constexpr bool is_number_integer() const noexcept { return m_type == value_t::number_integer || m_type == value_t::number_unsigned; } /*! @brief return whether value is an unsigned integer number This function returns true if and only if the JSON value is an unsigned integer number. This excludes floating-point and signed integer values. @return `true` if type is an unsigned integer number, `false` otherwise. @complexity Constant. @exceptionsafety No-throw guarantee: this member function never throws exceptions. @liveexample{The following code exemplifies `is_number_unsigned()` for all JSON types.,is_number_unsigned} @sa @ref is_number() -- check if value is a number @sa @ref is_number_integer() -- check if value is an integer or unsigned integer number @sa @ref is_number_float() -- check if value is a floating-point number @since version 2.0.0 */ constexpr bool is_number_unsigned() const noexcept { return m_type == value_t::number_unsigned; } /*! @brief return whether value is a floating-point number This function returns true if and only if the JSON value is a floating-point number. This excludes signed and unsigned integer values. @return `true` if type is a floating-point number, `false` otherwise. @complexity Constant. @exceptionsafety No-throw guarantee: this member function never throws exceptions. @liveexample{The following code exemplifies `is_number_float()` for all JSON types.,is_number_float} @sa @ref is_number() -- check if value is number @sa @ref is_number_integer() -- check if value is an integer number @sa @ref is_number_unsigned() -- check if value is an unsigned integer number @since version 1.0.0 */ constexpr bool is_number_float() const noexcept { return m_type == value_t::number_float; } /*! @brief return whether value is an object This function returns true if and only if the JSON value is an object. @return `true` if type is object, `false` otherwise. @complexity Constant. @exceptionsafety No-throw guarantee: this member function never throws exceptions. @liveexample{The following code exemplifies `is_object()` for all JSON types.,is_object} @since version 1.0.0 */ constexpr bool is_object() const noexcept { return m_type == value_t::object; } /*! @brief return whether value is an array This function returns true if and only if the JSON value is an array. @return `true` if type is array, `false` otherwise. @complexity Constant. @exceptionsafety No-throw guarantee: this member function never throws exceptions. @liveexample{The following code exemplifies `is_array()` for all JSON types.,is_array} @since version 1.0.0 */ constexpr bool is_array() const noexcept { return m_type == value_t::array; } /*! @brief return whether value is a string This function returns true if and only if the JSON value is a string. @return `true` if type is string, `false` otherwise. @complexity Constant. @exceptionsafety No-throw guarantee: this member function never throws exceptions. @liveexample{The following code exemplifies `is_string()` for all JSON types.,is_string} @since version 1.0.0 */ constexpr bool is_string() const noexcept { return m_type == value_t::string; } /*! @brief return whether value is a binary array This function returns true if and only if the JSON value is a binary array. @return `true` if type is binary array, `false` otherwise. @complexity Constant. @exceptionsafety No-throw guarantee: this member function never throws exceptions. @liveexample{The following code exemplifies `is_binary()` for all JSON types.,is_binary} @since version 3.8.0 */ constexpr bool is_binary() const noexcept { return m_type == value_t::binary; } /*! @brief return whether value is discarded This function returns true if and only if the JSON value was discarded during parsing with a callback function (see @ref parser_callback_t). @note This function will always be `false` for JSON values after parsing. That is, discarded values can only occur during parsing, but will be removed when inside a structured value or replaced by null in other cases. @return `true` if type is discarded, `false` otherwise. @complexity Constant. @exceptionsafety No-throw guarantee: this member function never throws exceptions. @liveexample{The following code exemplifies `is_discarded()` for all JSON types.,is_discarded} @since version 1.0.0 */ constexpr bool is_discarded() const noexcept { return m_type == value_t::discarded; } /*! @brief return the type of the JSON value (implicit) Implicitly return the type of the JSON value as a value from the @ref value_t enumeration. @return the type of the JSON value @complexity Constant. @exceptionsafety No-throw guarantee: this member function never throws exceptions. @liveexample{The following code exemplifies the @ref value_t operator for all JSON types.,operator__value_t} @sa @ref type() -- return the type of the JSON value (explicit) @sa @ref type_name() -- return the type as string @since version 1.0.0 */ constexpr operator value_t() const noexcept { return m_type; } /// @} private: ////////////////// // value access // ////////////////// /// get a boolean (explicit) boolean_t get_impl(boolean_t* /*unused*/) const { if (JSON_HEDLEY_LIKELY(is_boolean())) { return m_value.boolean; } JSON_THROW(type_error::create(302, "type must be boolean, but is " + std::string(type_name()))); } /// get a pointer to the value (object) object_t* get_impl_ptr(object_t* /*unused*/) noexcept { return is_object() ? m_value.object : nullptr; } /// get a pointer to the value (object) constexpr const object_t* get_impl_ptr(const object_t* /*unused*/) const noexcept { return is_object() ? m_value.object : nullptr; } /// get a pointer to the value (array) array_t* get_impl_ptr(array_t* /*unused*/) noexcept { return is_array() ? m_value.array : nullptr; } /// get a pointer to the value (array) constexpr const array_t* get_impl_ptr(const array_t* /*unused*/) const noexcept { return is_array() ? m_value.array : nullptr; } /// get a pointer to the value (string) string_t* get_impl_ptr(string_t* /*unused*/) noexcept { return is_string() ? m_value.string : nullptr; } /// get a pointer to the value (string) constexpr const string_t* get_impl_ptr(const string_t* /*unused*/) const noexcept { return is_string() ? m_value.string : nullptr; } /// get a pointer to the value (boolean) boolean_t* get_impl_ptr(boolean_t* /*unused*/) noexcept { return is_boolean() ? &m_value.boolean : nullptr; } /// get a pointer to the value (boolean) constexpr const boolean_t* get_impl_ptr(const boolean_t* /*unused*/) const noexcept { return is_boolean() ? &m_value.boolean : nullptr; } /// get a pointer to the value (integer number) number_integer_t* get_impl_ptr(number_integer_t* /*unused*/) noexcept { return is_number_integer() ? &m_value.number_integer : nullptr; } /// get a pointer to the value (integer number) constexpr const number_integer_t* get_impl_ptr(const number_integer_t* /*unused*/) const noexcept { return is_number_integer() ? &m_value.number_integer : nullptr; } /// get a pointer to the value (unsigned number) number_unsigned_t* get_impl_ptr(number_unsigned_t* /*unused*/) noexcept { return is_number_unsigned() ? &m_value.number_unsigned : nullptr; } /// get a pointer to the value (unsigned number) constexpr const number_unsigned_t* get_impl_ptr(const number_unsigned_t* /*unused*/) const noexcept { return is_number_unsigned() ? &m_value.number_unsigned : nullptr; } /// get a pointer to the value (floating-point number) number_float_t* get_impl_ptr(number_float_t* /*unused*/) noexcept { return is_number_float() ? &m_value.number_float : nullptr; } /// get a pointer to the value (floating-point number) constexpr const number_float_t* get_impl_ptr(const number_float_t* /*unused*/) const noexcept { return is_number_float() ? &m_value.number_float : nullptr; } /// get a pointer to the value (binary) binary_t* get_impl_ptr(binary_t* /*unused*/) noexcept { return is_binary() ? m_value.binary : nullptr; } /// get a pointer to the value (binary) constexpr const binary_t* get_impl_ptr(const binary_t* /*unused*/) const noexcept { return is_binary() ? m_value.binary : nullptr; } /*! @brief helper function to implement get_ref() This function helps to implement get_ref() without code duplication for const and non-const overloads @tparam ThisType will be deduced as `basic_json` or `const basic_json` @throw type_error.303 if ReferenceType does not match underlying value type of the current JSON */ template static ReferenceType get_ref_impl(ThisType& obj) { // delegate the call to get_ptr<>() auto ptr = obj.template get_ptr::type>(); if (JSON_HEDLEY_LIKELY(ptr != nullptr)) { return *ptr; } JSON_THROW(type_error::create(303, "incompatible ReferenceType for get_ref, actual type is " + std::string(obj.type_name()))); } public: /// @name value access /// Direct access to the stored value of a JSON value. /// @{ /*! @brief get special-case overload This overloads avoids a lot of template boilerplate, it can be seen as the identity method @tparam BasicJsonType == @ref basic_json @return a copy of *this @complexity Constant. @since version 2.1.0 */ template::type, basic_json_t>::value, int> = 0> basic_json get() const { return *this; } /*! @brief get special-case overload This overloads converts the current @ref basic_json in a different @ref basic_json type @tparam BasicJsonType == @ref basic_json @return a copy of *this, converted into @tparam BasicJsonType @complexity Depending on the implementation of the called `from_json()` method. @since version 3.2.0 */ template < typename BasicJsonType, detail::enable_if_t < !std::is_same::value&& detail::is_basic_json::value, int > = 0 > BasicJsonType get() const { return *this; } /*! @brief get a value (explicit) Explicit type conversion between the JSON value and a compatible value which is [CopyConstructible](https://en.cppreference.com/w/cpp/named_req/CopyConstructible) and [DefaultConstructible](https://en.cppreference.com/w/cpp/named_req/DefaultConstructible). The value is converted by calling the @ref json_serializer `from_json()` method. The function is equivalent to executing @code {.cpp} ValueType ret; JSONSerializer::from_json(*this, ret); return ret; @endcode This overloads is chosen if: - @a ValueType is not @ref basic_json, - @ref json_serializer has a `from_json()` method of the form `void from_json(const basic_json&, ValueType&)`, and - @ref json_serializer does not have a `from_json()` method of the form `ValueType from_json(const basic_json&)` @tparam ValueTypeCV the provided value type @tparam ValueType the returned value type @return copy of the JSON value, converted to @a ValueType @throw what @ref json_serializer `from_json()` method throws @liveexample{The example below shows several conversions from JSON values to other types. There a few things to note: (1) Floating-point numbers can be converted to integers\, (2) A JSON array can be converted to a standard `std::vector`\, (3) A JSON object can be converted to C++ associative containers such as `std::unordered_map`.,get__ValueType_const} @since version 2.1.0 */ template < typename ValueTypeCV, typename ValueType = detail::uncvref_t, detail::enable_if_t < !detail::is_basic_json::value && detail::has_from_json::value && !detail::has_non_default_from_json::value, int > = 0 > ValueType get() const noexcept(noexcept( JSONSerializer::from_json(std::declval(), std::declval()))) { // we cannot static_assert on ValueTypeCV being non-const, because // there is support for get(), which is why we // still need the uncvref static_assert(!std::is_reference::value, "get() cannot be used with reference types, you might want to use get_ref()"); static_assert(std::is_default_constructible::value, "types must be DefaultConstructible when used with get()"); ValueType ret; JSONSerializer::from_json(*this, ret); return ret; } /*! @brief get a value (explicit); special case Explicit type conversion between the JSON value and a compatible value which is **not** [CopyConstructible](https://en.cppreference.com/w/cpp/named_req/CopyConstructible) and **not** [DefaultConstructible](https://en.cppreference.com/w/cpp/named_req/DefaultConstructible). The value is converted by calling the @ref json_serializer `from_json()` method. The function is equivalent to executing @code {.cpp} return JSONSerializer::from_json(*this); @endcode This overloads is chosen if: - @a ValueType is not @ref basic_json and - @ref json_serializer has a `from_json()` method of the form `ValueType from_json(const basic_json&)` @note If @ref json_serializer has both overloads of `from_json()`, this one is chosen. @tparam ValueTypeCV the provided value type @tparam ValueType the returned value type @return copy of the JSON value, converted to @a ValueType @throw what @ref json_serializer `from_json()` method throws @since version 2.1.0 */ template < typename ValueTypeCV, typename ValueType = detail::uncvref_t, detail::enable_if_t < !std::is_same::value && detail::has_non_default_from_json::value, int > = 0 > ValueType get() const noexcept(noexcept( JSONSerializer::from_json(std::declval()))) { static_assert(!std::is_reference::value, "get() cannot be used with reference types, you might want to use get_ref()"); return JSONSerializer::from_json(*this); } /*! @brief get a value (explicit) Explicit type conversion between the JSON value and a compatible value. The value is filled into the input parameter by calling the @ref json_serializer `from_json()` method. The function is equivalent to executing @code {.cpp} ValueType v; JSONSerializer::from_json(*this, v); @endcode This overloads is chosen if: - @a ValueType is not @ref basic_json, - @ref json_serializer has a `from_json()` method of the form `void from_json(const basic_json&, ValueType&)`, and @tparam ValueType the input parameter type. @return the input parameter, allowing chaining calls. @throw what @ref json_serializer `from_json()` method throws @liveexample{The example below shows several conversions from JSON values to other types. There a few things to note: (1) Floating-point numbers can be converted to integers\, (2) A JSON array can be converted to a standard `std::vector`\, (3) A JSON object can be converted to C++ associative containers such as `std::unordered_map`.,get_to} @since version 3.3.0 */ template < typename ValueType, detail::enable_if_t < !detail::is_basic_json::value&& detail::has_from_json::value, int > = 0 > ValueType & get_to(ValueType& v) const noexcept(noexcept( JSONSerializer::from_json(std::declval(), v))) { JSONSerializer::from_json(*this, v); return v; } // specialization to allow to call get_to with a basic_json value // see https://github.com/nlohmann/json/issues/2175 template::value, int> = 0> ValueType & get_to(ValueType& v) const { v = *this; return v; } template < typename T, std::size_t N, typename Array = T (&)[N], detail::enable_if_t < detail::has_from_json::value, int > = 0 > Array get_to(T (&v)[N]) const noexcept(noexcept(JSONSerializer::from_json( std::declval(), v))) { JSONSerializer::from_json(*this, v); return v; } /*! @brief get a pointer value (implicit) Implicit pointer access to the internally stored JSON value. No copies are made. @warning Writing data to the pointee of the result yields an undefined state. @tparam PointerType pointer type; must be a pointer to @ref array_t, @ref object_t, @ref string_t, @ref boolean_t, @ref number_integer_t, @ref number_unsigned_t, or @ref number_float_t. Enforced by a static assertion. @return pointer to the internally stored JSON value if the requested pointer type @a PointerType fits to the JSON value; `nullptr` otherwise @complexity Constant. @liveexample{The example below shows how pointers to internal values of a JSON value can be requested. Note that no type conversions are made and a `nullptr` is returned if the value and the requested pointer type does not match.,get_ptr} @since version 1.0.0 */ template::value, int>::type = 0> auto get_ptr() noexcept -> decltype(std::declval().get_impl_ptr(std::declval())) { // delegate the call to get_impl_ptr<>() return get_impl_ptr(static_cast(nullptr)); } /*! @brief get a pointer value (implicit) @copydoc get_ptr() */ template < typename PointerType, typename std::enable_if < std::is_pointer::value&& std::is_const::type>::value, int >::type = 0 > constexpr auto get_ptr() const noexcept -> decltype(std::declval().get_impl_ptr(std::declval())) { // delegate the call to get_impl_ptr<>() const return get_impl_ptr(static_cast(nullptr)); } /*! @brief get a pointer value (explicit) Explicit pointer access to the internally stored JSON value. No copies are made. @warning The pointer becomes invalid if the underlying JSON object changes. @tparam PointerType pointer type; must be a pointer to @ref array_t, @ref object_t, @ref string_t, @ref boolean_t, @ref number_integer_t, @ref number_unsigned_t, or @ref number_float_t. @return pointer to the internally stored JSON value if the requested pointer type @a PointerType fits to the JSON value; `nullptr` otherwise @complexity Constant. @liveexample{The example below shows how pointers to internal values of a JSON value can be requested. Note that no type conversions are made and a `nullptr` is returned if the value and the requested pointer type does not match.,get__PointerType} @sa @ref get_ptr() for explicit pointer-member access @since version 1.0.0 */ template::value, int>::type = 0> auto get() noexcept -> decltype(std::declval().template get_ptr()) { // delegate the call to get_ptr return get_ptr(); } /*! @brief get a pointer value (explicit) @copydoc get() */ template::value, int>::type = 0> constexpr auto get() const noexcept -> decltype(std::declval().template get_ptr()) { // delegate the call to get_ptr return get_ptr(); } /*! @brief get a reference value (implicit) Implicit reference access to the internally stored JSON value. No copies are made. @warning Writing data to the referee of the result yields an undefined state. @tparam ReferenceType reference type; must be a reference to @ref array_t, @ref object_t, @ref string_t, @ref boolean_t, @ref number_integer_t, or @ref number_float_t. Enforced by static assertion. @return reference to the internally stored JSON value if the requested reference type @a ReferenceType fits to the JSON value; throws type_error.303 otherwise @throw type_error.303 in case passed type @a ReferenceType is incompatible with the stored JSON value; see example below @complexity Constant. @liveexample{The example shows several calls to `get_ref()`.,get_ref} @since version 1.1.0 */ template::value, int>::type = 0> ReferenceType get_ref() { // delegate call to get_ref_impl return get_ref_impl(*this); } /*! @brief get a reference value (implicit) @copydoc get_ref() */ template < typename ReferenceType, typename std::enable_if < std::is_reference::value&& std::is_const::type>::value, int >::type = 0 > ReferenceType get_ref() const { // delegate call to get_ref_impl return get_ref_impl(*this); } /*! @brief get a value (implicit) Implicit type conversion between the JSON value and a compatible value. The call is realized by calling @ref get() const. @tparam ValueType non-pointer type compatible to the JSON value, for instance `int` for JSON integer numbers, `bool` for JSON booleans, or `std::vector` types for JSON arrays. The character type of @ref string_t as well as an initializer list of this type is excluded to avoid ambiguities as these types implicitly convert to `std::string`. @return copy of the JSON value, converted to type @a ValueType @throw type_error.302 in case passed type @a ValueType is incompatible to the JSON value type (e.g., the JSON value is of type boolean, but a string is requested); see example below @complexity Linear in the size of the JSON value. @liveexample{The example below shows several conversions from JSON values to other types. There a few things to note: (1) Floating-point numbers can be converted to integers\, (2) A JSON array can be converted to a standard `std::vector`\, (3) A JSON object can be converted to C++ associative containers such as `std::unordered_map`.,operator__ValueType} @since version 1.0.0 */ template < typename ValueType, typename std::enable_if < !std::is_pointer::value&& !std::is_same>::value&& !std::is_same::value&& !detail::is_basic_json::value && !std::is_same>::value #if defined(JSON_HAS_CPP_17) && (defined(__GNUC__) || (defined(_MSC_VER) && _MSC_VER >= 1910 && _MSC_VER <= 1914)) && !std::is_same::value #endif && detail::is_detected::value , int >::type = 0 > JSON_EXPLICIT operator ValueType() const { // delegate the call to get<>() const return get(); } /*! @return reference to the binary value @throw type_error.302 if the value is not binary @sa @ref is_binary() to check if the value is binary @since version 3.8.0 */ binary_t& get_binary() { if (!is_binary()) { JSON_THROW(type_error::create(302, "type must be binary, but is " + std::string(type_name()))); } return *get_ptr(); } /// @copydoc get_binary() const binary_t& get_binary() const { if (!is_binary()) { JSON_THROW(type_error::create(302, "type must be binary, but is " + std::string(type_name()))); } return *get_ptr(); } /// @} //////////////////// // element access // //////////////////// /// @name element access /// Access to the JSON value. /// @{ /*! @brief access specified array element with bounds checking Returns a reference to the element at specified location @a idx, with bounds checking. @param[in] idx index of the element to access @return reference to the element at index @a idx @throw type_error.304 if the JSON value is not an array; in this case, calling `at` with an index makes no sense. See example below. @throw out_of_range.401 if the index @a idx is out of range of the array; that is, `idx >= size()`. See example below. @exceptionsafety Strong guarantee: if an exception is thrown, there are no changes in the JSON value. @complexity Constant. @since version 1.0.0 @liveexample{The example below shows how array elements can be read and written using `at()`. It also demonstrates the different exceptions that can be thrown.,at__size_type} */ reference at(size_type idx) { // at only works for arrays if (JSON_HEDLEY_LIKELY(is_array())) { JSON_TRY { return m_value.array->at(idx); } JSON_CATCH (std::out_of_range&) { // create better exception explanation JSON_THROW(out_of_range::create(401, "array index " + std::to_string(idx) + " is out of range")); } } else { JSON_THROW(type_error::create(304, "cannot use at() with " + std::string(type_name()))); } } /*! @brief access specified array element with bounds checking Returns a const reference to the element at specified location @a idx, with bounds checking. @param[in] idx index of the element to access @return const reference to the element at index @a idx @throw type_error.304 if the JSON value is not an array; in this case, calling `at` with an index makes no sense. See example below. @throw out_of_range.401 if the index @a idx is out of range of the array; that is, `idx >= size()`. See example below. @exceptionsafety Strong guarantee: if an exception is thrown, there are no changes in the JSON value. @complexity Constant. @since version 1.0.0 @liveexample{The example below shows how array elements can be read using `at()`. It also demonstrates the different exceptions that can be thrown., at__size_type_const} */ const_reference at(size_type idx) const { // at only works for arrays if (JSON_HEDLEY_LIKELY(is_array())) { JSON_TRY { return m_value.array->at(idx); } JSON_CATCH (std::out_of_range&) { // create better exception explanation JSON_THROW(out_of_range::create(401, "array index " + std::to_string(idx) + " is out of range")); } } else { JSON_THROW(type_error::create(304, "cannot use at() with " + std::string(type_name()))); } } /*! @brief access specified object element with bounds checking Returns a reference to the element at with specified key @a key, with bounds checking. @param[in] key key of the element to access @return reference to the element at key @a key @throw type_error.304 if the JSON value is not an object; in this case, calling `at` with a key makes no sense. See example below. @throw out_of_range.403 if the key @a key is is not stored in the object; that is, `find(key) == end()`. See example below. @exceptionsafety Strong guarantee: if an exception is thrown, there are no changes in the JSON value. @complexity Logarithmic in the size of the container. @sa @ref operator[](const typename object_t::key_type&) for unchecked access by reference @sa @ref value() for access by value with a default value @since version 1.0.0 @liveexample{The example below shows how object elements can be read and written using `at()`. It also demonstrates the different exceptions that can be thrown.,at__object_t_key_type} */ reference at(const typename object_t::key_type& key) { // at only works for objects if (JSON_HEDLEY_LIKELY(is_object())) { JSON_TRY { return m_value.object->at(key); } JSON_CATCH (std::out_of_range&) { // create better exception explanation JSON_THROW(out_of_range::create(403, "key '" + key + "' not found")); } } else { JSON_THROW(type_error::create(304, "cannot use at() with " + std::string(type_name()))); } } /*! @brief access specified object element with bounds checking Returns a const reference to the element at with specified key @a key, with bounds checking. @param[in] key key of the element to access @return const reference to the element at key @a key @throw type_error.304 if the JSON value is not an object; in this case, calling `at` with a key makes no sense. See example below. @throw out_of_range.403 if the key @a key is is not stored in the object; that is, `find(key) == end()`. See example below. @exceptionsafety Strong guarantee: if an exception is thrown, there are no changes in the JSON value. @complexity Logarithmic in the size of the container. @sa @ref operator[](const typename object_t::key_type&) for unchecked access by reference @sa @ref value() for access by value with a default value @since version 1.0.0 @liveexample{The example below shows how object elements can be read using `at()`. It also demonstrates the different exceptions that can be thrown., at__object_t_key_type_const} */ const_reference at(const typename object_t::key_type& key) const { // at only works for objects if (JSON_HEDLEY_LIKELY(is_object())) { JSON_TRY { return m_value.object->at(key); } JSON_CATCH (std::out_of_range&) { // create better exception explanation JSON_THROW(out_of_range::create(403, "key '" + key + "' not found")); } } else { JSON_THROW(type_error::create(304, "cannot use at() with " + std::string(type_name()))); } } /*! @brief access specified array element Returns a reference to the element at specified location @a idx. @note If @a idx is beyond the range of the array (i.e., `idx >= size()`), then the array is silently filled up with `null` values to make `idx` a valid reference to the last stored element. @param[in] idx index of the element to access @return reference to the element at index @a idx @throw type_error.305 if the JSON value is not an array or null; in that cases, using the [] operator with an index makes no sense. @complexity Constant if @a idx is in the range of the array. Otherwise linear in `idx - size()`. @liveexample{The example below shows how array elements can be read and written using `[]` operator. Note the addition of `null` values.,operatorarray__size_type} @since version 1.0.0 */ reference operator[](size_type idx) { // implicitly convert null value to an empty array if (is_null()) { m_type = value_t::array; m_value.array = create(); assert_invariant(); } // operator[] only works for arrays if (JSON_HEDLEY_LIKELY(is_array())) { // fill up array with null values if given idx is outside range if (idx >= m_value.array->size()) { m_value.array->insert(m_value.array->end(), idx - m_value.array->size() + 1, basic_json()); } return m_value.array->operator[](idx); } JSON_THROW(type_error::create(305, "cannot use operator[] with a numeric argument with " + std::string(type_name()))); } /*! @brief access specified array element Returns a const reference to the element at specified location @a idx. @param[in] idx index of the element to access @return const reference to the element at index @a idx @throw type_error.305 if the JSON value is not an array; in that case, using the [] operator with an index makes no sense. @complexity Constant. @liveexample{The example below shows how array elements can be read using the `[]` operator.,operatorarray__size_type_const} @since version 1.0.0 */ const_reference operator[](size_type idx) const { // const operator[] only works for arrays if (JSON_HEDLEY_LIKELY(is_array())) { return m_value.array->operator[](idx); } JSON_THROW(type_error::create(305, "cannot use operator[] with a numeric argument with " + std::string(type_name()))); } /*! @brief access specified object element Returns a reference to the element at with specified key @a key. @note If @a key is not found in the object, then it is silently added to the object and filled with a `null` value to make `key` a valid reference. In case the value was `null` before, it is converted to an object. @param[in] key key of the element to access @return reference to the element at key @a key @throw type_error.305 if the JSON value is not an object or null; in that cases, using the [] operator with a key makes no sense. @complexity Logarithmic in the size of the container. @liveexample{The example below shows how object elements can be read and written using the `[]` operator.,operatorarray__key_type} @sa @ref at(const typename object_t::key_type&) for access by reference with range checking @sa @ref value() for access by value with a default value @since version 1.0.0 */ reference operator[](const typename object_t::key_type& key) { // implicitly convert null value to an empty object if (is_null()) { m_type = value_t::object; m_value.object = create(); assert_invariant(); } // operator[] only works for objects if (JSON_HEDLEY_LIKELY(is_object())) { return m_value.object->operator[](key); } JSON_THROW(type_error::create(305, "cannot use operator[] with a string argument with " + std::string(type_name()))); } /*! @brief read-only access specified object element Returns a const reference to the element at with specified key @a key. No bounds checking is performed. @warning If the element with key @a key does not exist, the behavior is undefined. @param[in] key key of the element to access @return const reference to the element at key @a key @pre The element with key @a key must exist. **This precondition is enforced with an assertion.** @throw type_error.305 if the JSON value is not an object; in that case, using the [] operator with a key makes no sense. @complexity Logarithmic in the size of the container. @liveexample{The example below shows how object elements can be read using the `[]` operator.,operatorarray__key_type_const} @sa @ref at(const typename object_t::key_type&) for access by reference with range checking @sa @ref value() for access by value with a default value @since version 1.0.0 */ const_reference operator[](const typename object_t::key_type& key) const { // const operator[] only works for objects if (JSON_HEDLEY_LIKELY(is_object())) { JSON_ASSERT(m_value.object->find(key) != m_value.object->end()); return m_value.object->find(key)->second; } JSON_THROW(type_error::create(305, "cannot use operator[] with a string argument with " + std::string(type_name()))); } /*! @brief access specified object element Returns a reference to the element at with specified key @a key. @note If @a key is not found in the object, then it is silently added to the object and filled with a `null` value to make `key` a valid reference. In case the value was `null` before, it is converted to an object. @param[in] key key of the element to access @return reference to the element at key @a key @throw type_error.305 if the JSON value is not an object or null; in that cases, using the [] operator with a key makes no sense. @complexity Logarithmic in the size of the container. @liveexample{The example below shows how object elements can be read and written using the `[]` operator.,operatorarray__key_type} @sa @ref at(const typename object_t::key_type&) for access by reference with range checking @sa @ref value() for access by value with a default value @since version 1.1.0 */ template JSON_HEDLEY_NON_NULL(2) reference operator[](T* key) { // implicitly convert null to object if (is_null()) { m_type = value_t::object; m_value = value_t::object; assert_invariant(); } // at only works for objects if (JSON_HEDLEY_LIKELY(is_object())) { return m_value.object->operator[](key); } JSON_THROW(type_error::create(305, "cannot use operator[] with a string argument with " + std::string(type_name()))); } /*! @brief read-only access specified object element Returns a const reference to the element at with specified key @a key. No bounds checking is performed. @warning If the element with key @a key does not exist, the behavior is undefined. @param[in] key key of the element to access @return const reference to the element at key @a key @pre The element with key @a key must exist. **This precondition is enforced with an assertion.** @throw type_error.305 if the JSON value is not an object; in that case, using the [] operator with a key makes no sense. @complexity Logarithmic in the size of the container. @liveexample{The example below shows how object elements can be read using the `[]` operator.,operatorarray__key_type_const} @sa @ref at(const typename object_t::key_type&) for access by reference with range checking @sa @ref value() for access by value with a default value @since version 1.1.0 */ template JSON_HEDLEY_NON_NULL(2) const_reference operator[](T* key) const { // at only works for objects if (JSON_HEDLEY_LIKELY(is_object())) { JSON_ASSERT(m_value.object->find(key) != m_value.object->end()); return m_value.object->find(key)->second; } JSON_THROW(type_error::create(305, "cannot use operator[] with a string argument with " + std::string(type_name()))); } /*! @brief access specified object element with default value Returns either a copy of an object's element at the specified key @a key or a given default value if no element with key @a key exists. The function is basically equivalent to executing @code {.cpp} try { return at(key); } catch(out_of_range) { return default_value; } @endcode @note Unlike @ref at(const typename object_t::key_type&), this function does not throw if the given key @a key was not found. @note Unlike @ref operator[](const typename object_t::key_type& key), this function does not implicitly add an element to the position defined by @a key. This function is furthermore also applicable to const objects. @param[in] key key of the element to access @param[in] default_value the value to return if @a key is not found @tparam ValueType type compatible to JSON values, for instance `int` for JSON integer numbers, `bool` for JSON booleans, or `std::vector` types for JSON arrays. Note the type of the expected value at @a key and the default value @a default_value must be compatible. @return copy of the element at key @a key or @a default_value if @a key is not found @throw type_error.302 if @a default_value does not match the type of the value at @a key @throw type_error.306 if the JSON value is not an object; in that case, using `value()` with a key makes no sense. @complexity Logarithmic in the size of the container. @liveexample{The example below shows how object elements can be queried with a default value.,basic_json__value} @sa @ref at(const typename object_t::key_type&) for access by reference with range checking @sa @ref operator[](const typename object_t::key_type&) for unchecked access by reference @since version 1.0.0 */ // using std::is_convertible in a std::enable_if will fail when using explicit conversions template < class ValueType, typename std::enable_if < detail::is_getable::value && !std::is_same::value, int >::type = 0 > ValueType value(const typename object_t::key_type& key, const ValueType& default_value) const { // at only works for objects if (JSON_HEDLEY_LIKELY(is_object())) { // if key is found, return value and given default value otherwise const auto it = find(key); if (it != end()) { return it->template get(); } return default_value; } JSON_THROW(type_error::create(306, "cannot use value() with " + std::string(type_name()))); } /*! @brief overload for a default value of type const char* @copydoc basic_json::value(const typename object_t::key_type&, const ValueType&) const */ string_t value(const typename object_t::key_type& key, const char* default_value) const { return value(key, string_t(default_value)); } /*! @brief access specified object element via JSON Pointer with default value Returns either a copy of an object's element at the specified key @a key or a given default value if no element with key @a key exists. The function is basically equivalent to executing @code {.cpp} try { return at(ptr); } catch(out_of_range) { return default_value; } @endcode @note Unlike @ref at(const json_pointer&), this function does not throw if the given key @a key was not found. @param[in] ptr a JSON pointer to the element to access @param[in] default_value the value to return if @a ptr found no value @tparam ValueType type compatible to JSON values, for instance `int` for JSON integer numbers, `bool` for JSON booleans, or `std::vector` types for JSON arrays. Note the type of the expected value at @a key and the default value @a default_value must be compatible. @return copy of the element at key @a key or @a default_value if @a key is not found @throw type_error.302 if @a default_value does not match the type of the value at @a ptr @throw type_error.306 if the JSON value is not an object; in that case, using `value()` with a key makes no sense. @complexity Logarithmic in the size of the container. @liveexample{The example below shows how object elements can be queried with a default value.,basic_json__value_ptr} @sa @ref operator[](const json_pointer&) for unchecked access by reference @since version 2.0.2 */ template::value, int>::type = 0> ValueType value(const json_pointer& ptr, const ValueType& default_value) const { // at only works for objects if (JSON_HEDLEY_LIKELY(is_object())) { // if pointer resolves a value, return it or use default value JSON_TRY { return ptr.get_checked(this).template get(); } JSON_INTERNAL_CATCH (out_of_range&) { return default_value; } } JSON_THROW(type_error::create(306, "cannot use value() with " + std::string(type_name()))); } /*! @brief overload for a default value of type const char* @copydoc basic_json::value(const json_pointer&, ValueType) const */ JSON_HEDLEY_NON_NULL(3) string_t value(const json_pointer& ptr, const char* default_value) const { return value(ptr, string_t(default_value)); } /*! @brief access the first element Returns a reference to the first element in the container. For a JSON container `c`, the expression `c.front()` is equivalent to `*c.begin()`. @return In case of a structured type (array or object), a reference to the first element is returned. In case of number, string, boolean, or binary values, a reference to the value is returned. @complexity Constant. @pre The JSON value must not be `null` (would throw `std::out_of_range`) or an empty array or object (undefined behavior, **guarded by assertions**). @post The JSON value remains unchanged. @throw invalid_iterator.214 when called on `null` value @liveexample{The following code shows an example for `front()`.,front} @sa @ref back() -- access the last element @since version 1.0.0 */ reference front() { return *begin(); } /*! @copydoc basic_json::front() */ const_reference front() const { return *cbegin(); } /*! @brief access the last element Returns a reference to the last element in the container. For a JSON container `c`, the expression `c.back()` is equivalent to @code {.cpp} auto tmp = c.end(); --tmp; return *tmp; @endcode @return In case of a structured type (array or object), a reference to the last element is returned. In case of number, string, boolean, or binary values, a reference to the value is returned. @complexity Constant. @pre The JSON value must not be `null` (would throw `std::out_of_range`) or an empty array or object (undefined behavior, **guarded by assertions**). @post The JSON value remains unchanged. @throw invalid_iterator.214 when called on a `null` value. See example below. @liveexample{The following code shows an example for `back()`.,back} @sa @ref front() -- access the first element @since version 1.0.0 */ reference back() { auto tmp = end(); --tmp; return *tmp; } /*! @copydoc basic_json::back() */ const_reference back() const { auto tmp = cend(); --tmp; return *tmp; } /*! @brief remove element given an iterator Removes the element specified by iterator @a pos. The iterator @a pos must be valid and dereferenceable. Thus the `end()` iterator (which is valid, but is not dereferenceable) cannot be used as a value for @a pos. If called on a primitive type other than `null`, the resulting JSON value will be `null`. @param[in] pos iterator to the element to remove @return Iterator following the last removed element. If the iterator @a pos refers to the last element, the `end()` iterator is returned. @tparam IteratorType an @ref iterator or @ref const_iterator @post Invalidates iterators and references at or after the point of the erase, including the `end()` iterator. @throw type_error.307 if called on a `null` value; example: `"cannot use erase() with null"` @throw invalid_iterator.202 if called on an iterator which does not belong to the current JSON value; example: `"iterator does not fit current value"` @throw invalid_iterator.205 if called on a primitive type with invalid iterator (i.e., any iterator which is not `begin()`); example: `"iterator out of range"` @complexity The complexity depends on the type: - objects: amortized constant - arrays: linear in distance between @a pos and the end of the container - strings and binary: linear in the length of the member - other types: constant @liveexample{The example shows the result of `erase()` for different JSON types.,erase__IteratorType} @sa @ref erase(IteratorType, IteratorType) -- removes the elements in the given range @sa @ref erase(const typename object_t::key_type&) -- removes the element from an object at the given key @sa @ref erase(const size_type) -- removes the element from an array at the given index @since version 1.0.0 */ template < class IteratorType, typename std::enable_if < std::is_same::value || std::is_same::value, int >::type = 0 > IteratorType erase(IteratorType pos) { // make sure iterator fits the current value if (JSON_HEDLEY_UNLIKELY(this != pos.m_object)) { JSON_THROW(invalid_iterator::create(202, "iterator does not fit current value")); } IteratorType result = end(); switch (m_type) { case value_t::boolean: case value_t::number_float: case value_t::number_integer: case value_t::number_unsigned: case value_t::string: case value_t::binary: { if (JSON_HEDLEY_UNLIKELY(!pos.m_it.primitive_iterator.is_begin())) { JSON_THROW(invalid_iterator::create(205, "iterator out of range")); } if (is_string()) { AllocatorType alloc; std::allocator_traits::destroy(alloc, m_value.string); std::allocator_traits::deallocate(alloc, m_value.string, 1); m_value.string = nullptr; } else if (is_binary()) { AllocatorType alloc; std::allocator_traits::destroy(alloc, m_value.binary); std::allocator_traits::deallocate(alloc, m_value.binary, 1); m_value.binary = nullptr; } m_type = value_t::null; assert_invariant(); break; } case value_t::object: { result.m_it.object_iterator = m_value.object->erase(pos.m_it.object_iterator); break; } case value_t::array: { result.m_it.array_iterator = m_value.array->erase(pos.m_it.array_iterator); break; } default: JSON_THROW(type_error::create(307, "cannot use erase() with " + std::string(type_name()))); } return result; } /*! @brief remove elements given an iterator range Removes the element specified by the range `[first; last)`. The iterator @a first does not need to be dereferenceable if `first == last`: erasing an empty range is a no-op. If called on a primitive type other than `null`, the resulting JSON value will be `null`. @param[in] first iterator to the beginning of the range to remove @param[in] last iterator past the end of the range to remove @return Iterator following the last removed element. If the iterator @a second refers to the last element, the `end()` iterator is returned. @tparam IteratorType an @ref iterator or @ref const_iterator @post Invalidates iterators and references at or after the point of the erase, including the `end()` iterator. @throw type_error.307 if called on a `null` value; example: `"cannot use erase() with null"` @throw invalid_iterator.203 if called on iterators which does not belong to the current JSON value; example: `"iterators do not fit current value"` @throw invalid_iterator.204 if called on a primitive type with invalid iterators (i.e., if `first != begin()` and `last != end()`); example: `"iterators out of range"` @complexity The complexity depends on the type: - objects: `log(size()) + std::distance(first, last)` - arrays: linear in the distance between @a first and @a last, plus linear in the distance between @a last and end of the container - strings and binary: linear in the length of the member - other types: constant @liveexample{The example shows the result of `erase()` for different JSON types.,erase__IteratorType_IteratorType} @sa @ref erase(IteratorType) -- removes the element at a given position @sa @ref erase(const typename object_t::key_type&) -- removes the element from an object at the given key @sa @ref erase(const size_type) -- removes the element from an array at the given index @since version 1.0.0 */ template < class IteratorType, typename std::enable_if < std::is_same::value || std::is_same::value, int >::type = 0 > IteratorType erase(IteratorType first, IteratorType last) { // make sure iterator fits the current value if (JSON_HEDLEY_UNLIKELY(this != first.m_object || this != last.m_object)) { JSON_THROW(invalid_iterator::create(203, "iterators do not fit current value")); } IteratorType result = end(); switch (m_type) { case value_t::boolean: case value_t::number_float: case value_t::number_integer: case value_t::number_unsigned: case value_t::string: case value_t::binary: { if (JSON_HEDLEY_LIKELY(!first.m_it.primitive_iterator.is_begin() || !last.m_it.primitive_iterator.is_end())) { JSON_THROW(invalid_iterator::create(204, "iterators out of range")); } if (is_string()) { AllocatorType alloc; std::allocator_traits::destroy(alloc, m_value.string); std::allocator_traits::deallocate(alloc, m_value.string, 1); m_value.string = nullptr; } else if (is_binary()) { AllocatorType alloc; std::allocator_traits::destroy(alloc, m_value.binary); std::allocator_traits::deallocate(alloc, m_value.binary, 1); m_value.binary = nullptr; } m_type = value_t::null; assert_invariant(); break; } case value_t::object: { result.m_it.object_iterator = m_value.object->erase(first.m_it.object_iterator, last.m_it.object_iterator); break; } case value_t::array: { result.m_it.array_iterator = m_value.array->erase(first.m_it.array_iterator, last.m_it.array_iterator); break; } default: JSON_THROW(type_error::create(307, "cannot use erase() with " + std::string(type_name()))); } return result; } /*! @brief remove element from a JSON object given a key Removes elements from a JSON object with the key value @a key. @param[in] key value of the elements to remove @return Number of elements removed. If @a ObjectType is the default `std::map` type, the return value will always be `0` (@a key was not found) or `1` (@a key was found). @post References and iterators to the erased elements are invalidated. Other references and iterators are not affected. @throw type_error.307 when called on a type other than JSON object; example: `"cannot use erase() with null"` @complexity `log(size()) + count(key)` @liveexample{The example shows the effect of `erase()`.,erase__key_type} @sa @ref erase(IteratorType) -- removes the element at a given position @sa @ref erase(IteratorType, IteratorType) -- removes the elements in the given range @sa @ref erase(const size_type) -- removes the element from an array at the given index @since version 1.0.0 */ size_type erase(const typename object_t::key_type& key) { // this erase only works for objects if (JSON_HEDLEY_LIKELY(is_object())) { return m_value.object->erase(key); } JSON_THROW(type_error::create(307, "cannot use erase() with " + std::string(type_name()))); } /*! @brief remove element from a JSON array given an index Removes element from a JSON array at the index @a idx. @param[in] idx index of the element to remove @throw type_error.307 when called on a type other than JSON object; example: `"cannot use erase() with null"` @throw out_of_range.401 when `idx >= size()`; example: `"array index 17 is out of range"` @complexity Linear in distance between @a idx and the end of the container. @liveexample{The example shows the effect of `erase()`.,erase__size_type} @sa @ref erase(IteratorType) -- removes the element at a given position @sa @ref erase(IteratorType, IteratorType) -- removes the elements in the given range @sa @ref erase(const typename object_t::key_type&) -- removes the element from an object at the given key @since version 1.0.0 */ void erase(const size_type idx) { // this erase only works for arrays if (JSON_HEDLEY_LIKELY(is_array())) { if (JSON_HEDLEY_UNLIKELY(idx >= size())) { JSON_THROW(out_of_range::create(401, "array index " + std::to_string(idx) + " is out of range")); } m_value.array->erase(m_value.array->begin() + static_cast(idx)); } else { JSON_THROW(type_error::create(307, "cannot use erase() with " + std::string(type_name()))); } } /// @} //////////// // lookup // //////////// /// @name lookup /// @{ /*! @brief find an element in a JSON object Finds an element in a JSON object with key equivalent to @a key. If the element is not found or the JSON value is not an object, end() is returned. @note This method always returns @ref end() when executed on a JSON type that is not an object. @param[in] key key value of the element to search for. @return Iterator to an element with key equivalent to @a key. If no such element is found or the JSON value is not an object, past-the-end (see @ref end()) iterator is returned. @complexity Logarithmic in the size of the JSON object. @liveexample{The example shows how `find()` is used.,find__key_type} @sa @ref contains(KeyT&&) const -- checks whether a key exists @since version 1.0.0 */ template iterator find(KeyT&& key) { auto result = end(); if (is_object()) { result.m_it.object_iterator = m_value.object->find(std::forward(key)); } return result; } /*! @brief find an element in a JSON object @copydoc find(KeyT&&) */ template const_iterator find(KeyT&& key) const { auto result = cend(); if (is_object()) { result.m_it.object_iterator = m_value.object->find(std::forward(key)); } return result; } /*! @brief returns the number of occurrences of a key in a JSON object Returns the number of elements with key @a key. If ObjectType is the default `std::map` type, the return value will always be `0` (@a key was not found) or `1` (@a key was found). @note This method always returns `0` when executed on a JSON type that is not an object. @param[in] key key value of the element to count @return Number of elements with key @a key. If the JSON value is not an object, the return value will be `0`. @complexity Logarithmic in the size of the JSON object. @liveexample{The example shows how `count()` is used.,count} @since version 1.0.0 */ template size_type count(KeyT&& key) const { // return 0 for all nonobject types return is_object() ? m_value.object->count(std::forward(key)) : 0; } /*! @brief check the existence of an element in a JSON object Check whether an element exists in a JSON object with key equivalent to @a key. If the element is not found or the JSON value is not an object, false is returned. @note This method always returns false when executed on a JSON type that is not an object. @param[in] key key value to check its existence. @return true if an element with specified @a key exists. If no such element with such key is found or the JSON value is not an object, false is returned. @complexity Logarithmic in the size of the JSON object. @liveexample{The following code shows an example for `contains()`.,contains} @sa @ref find(KeyT&&) -- returns an iterator to an object element @sa @ref contains(const json_pointer&) const -- checks the existence for a JSON pointer @since version 3.6.0 */ template < typename KeyT, typename std::enable_if < !std::is_same::type, json_pointer>::value, int >::type = 0 > bool contains(KeyT && key) const { return is_object() && m_value.object->find(std::forward(key)) != m_value.object->end(); } /*! @brief check the existence of an element in a JSON object given a JSON pointer Check whether the given JSON pointer @a ptr can be resolved in the current JSON value. @note This method can be executed on any JSON value type. @param[in] ptr JSON pointer to check its existence. @return true if the JSON pointer can be resolved to a stored value, false otherwise. @post If `j.contains(ptr)` returns true, it is safe to call `j[ptr]`. @throw parse_error.106 if an array index begins with '0' @throw parse_error.109 if an array index was not a number @complexity Logarithmic in the size of the JSON object. @liveexample{The following code shows an example for `contains()`.,contains_json_pointer} @sa @ref contains(KeyT &&) const -- checks the existence of a key @since version 3.7.0 */ bool contains(const json_pointer& ptr) const { return ptr.contains(this); } /// @} /////////////// // iterators // /////////////// /// @name iterators /// @{ /*! @brief returns an iterator to the first element Returns an iterator to the first element. @image html range-begin-end.svg "Illustration from cppreference.com" @return iterator to the first element @complexity Constant. @requirement This function helps `basic_json` satisfying the [Container](https://en.cppreference.com/w/cpp/named_req/Container) requirements: - The complexity is constant. @liveexample{The following code shows an example for `begin()`.,begin} @sa @ref cbegin() -- returns a const iterator to the beginning @sa @ref end() -- returns an iterator to the end @sa @ref cend() -- returns a const iterator to the end @since version 1.0.0 */ iterator begin() noexcept { iterator result(this); result.set_begin(); return result; } /*! @copydoc basic_json::cbegin() */ const_iterator begin() const noexcept { return cbegin(); } /*! @brief returns a const iterator to the first element Returns a const iterator to the first element. @image html range-begin-end.svg "Illustration from cppreference.com" @return const iterator to the first element @complexity Constant. @requirement This function helps `basic_json` satisfying the [Container](https://en.cppreference.com/w/cpp/named_req/Container) requirements: - The complexity is constant. - Has the semantics of `const_cast(*this).begin()`. @liveexample{The following code shows an example for `cbegin()`.,cbegin} @sa @ref begin() -- returns an iterator to the beginning @sa @ref end() -- returns an iterator to the end @sa @ref cend() -- returns a const iterator to the end @since version 1.0.0 */ const_iterator cbegin() const noexcept { const_iterator result(this); result.set_begin(); return result; } /*! @brief returns an iterator to one past the last element Returns an iterator to one past the last element. @image html range-begin-end.svg "Illustration from cppreference.com" @return iterator one past the last element @complexity Constant. @requirement This function helps `basic_json` satisfying the [Container](https://en.cppreference.com/w/cpp/named_req/Container) requirements: - The complexity is constant. @liveexample{The following code shows an example for `end()`.,end} @sa @ref cend() -- returns a const iterator to the end @sa @ref begin() -- returns an iterator to the beginning @sa @ref cbegin() -- returns a const iterator to the beginning @since version 1.0.0 */ iterator end() noexcept { iterator result(this); result.set_end(); return result; } /*! @copydoc basic_json::cend() */ const_iterator end() const noexcept { return cend(); } /*! @brief returns a const iterator to one past the last element Returns a const iterator to one past the last element. @image html range-begin-end.svg "Illustration from cppreference.com" @return const iterator one past the last element @complexity Constant. @requirement This function helps `basic_json` satisfying the [Container](https://en.cppreference.com/w/cpp/named_req/Container) requirements: - The complexity is constant. - Has the semantics of `const_cast(*this).end()`. @liveexample{The following code shows an example for `cend()`.,cend} @sa @ref end() -- returns an iterator to the end @sa @ref begin() -- returns an iterator to the beginning @sa @ref cbegin() -- returns a const iterator to the beginning @since version 1.0.0 */ const_iterator cend() const noexcept { const_iterator result(this); result.set_end(); return result; } /*! @brief returns an iterator to the reverse-beginning Returns an iterator to the reverse-beginning; that is, the last element. @image html range-rbegin-rend.svg "Illustration from cppreference.com" @complexity Constant. @requirement This function helps `basic_json` satisfying the [ReversibleContainer](https://en.cppreference.com/w/cpp/named_req/ReversibleContainer) requirements: - The complexity is constant. - Has the semantics of `reverse_iterator(end())`. @liveexample{The following code shows an example for `rbegin()`.,rbegin} @sa @ref crbegin() -- returns a const reverse iterator to the beginning @sa @ref rend() -- returns a reverse iterator to the end @sa @ref crend() -- returns a const reverse iterator to the end @since version 1.0.0 */ reverse_iterator rbegin() noexcept { return reverse_iterator(end()); } /*! @copydoc basic_json::crbegin() */ const_reverse_iterator rbegin() const noexcept { return crbegin(); } /*! @brief returns an iterator to the reverse-end Returns an iterator to the reverse-end; that is, one before the first element. @image html range-rbegin-rend.svg "Illustration from cppreference.com" @complexity Constant. @requirement This function helps `basic_json` satisfying the [ReversibleContainer](https://en.cppreference.com/w/cpp/named_req/ReversibleContainer) requirements: - The complexity is constant. - Has the semantics of `reverse_iterator(begin())`. @liveexample{The following code shows an example for `rend()`.,rend} @sa @ref crend() -- returns a const reverse iterator to the end @sa @ref rbegin() -- returns a reverse iterator to the beginning @sa @ref crbegin() -- returns a const reverse iterator to the beginning @since version 1.0.0 */ reverse_iterator rend() noexcept { return reverse_iterator(begin()); } /*! @copydoc basic_json::crend() */ const_reverse_iterator rend() const noexcept { return crend(); } /*! @brief returns a const reverse iterator to the last element Returns a const iterator to the reverse-beginning; that is, the last element. @image html range-rbegin-rend.svg "Illustration from cppreference.com" @complexity Constant. @requirement This function helps `basic_json` satisfying the [ReversibleContainer](https://en.cppreference.com/w/cpp/named_req/ReversibleContainer) requirements: - The complexity is constant. - Has the semantics of `const_cast(*this).rbegin()`. @liveexample{The following code shows an example for `crbegin()`.,crbegin} @sa @ref rbegin() -- returns a reverse iterator to the beginning @sa @ref rend() -- returns a reverse iterator to the end @sa @ref crend() -- returns a const reverse iterator to the end @since version 1.0.0 */ const_reverse_iterator crbegin() const noexcept { return const_reverse_iterator(cend()); } /*! @brief returns a const reverse iterator to one before the first Returns a const reverse iterator to the reverse-end; that is, one before the first element. @image html range-rbegin-rend.svg "Illustration from cppreference.com" @complexity Constant. @requirement This function helps `basic_json` satisfying the [ReversibleContainer](https://en.cppreference.com/w/cpp/named_req/ReversibleContainer) requirements: - The complexity is constant. - Has the semantics of `const_cast(*this).rend()`. @liveexample{The following code shows an example for `crend()`.,crend} @sa @ref rend() -- returns a reverse iterator to the end @sa @ref rbegin() -- returns a reverse iterator to the beginning @sa @ref crbegin() -- returns a const reverse iterator to the beginning @since version 1.0.0 */ const_reverse_iterator crend() const noexcept { return const_reverse_iterator(cbegin()); } public: /*! @brief wrapper to access iterator member functions in range-based for This function allows to access @ref iterator::key() and @ref iterator::value() during range-based for loops. In these loops, a reference to the JSON values is returned, so there is no access to the underlying iterator. For loop without iterator_wrapper: @code{cpp} for (auto it = j_object.begin(); it != j_object.end(); ++it) { std::cout << "key: " << it.key() << ", value:" << it.value() << '\n'; } @endcode Range-based for loop without iterator proxy: @code{cpp} for (auto it : j_object) { // "it" is of type json::reference and has no key() member std::cout << "value: " << it << '\n'; } @endcode Range-based for loop with iterator proxy: @code{cpp} for (auto it : json::iterator_wrapper(j_object)) { std::cout << "key: " << it.key() << ", value:" << it.value() << '\n'; } @endcode @note When iterating over an array, `key()` will return the index of the element as string (see example). @param[in] ref reference to a JSON value @return iteration proxy object wrapping @a ref with an interface to use in range-based for loops @liveexample{The following code shows how the wrapper is used,iterator_wrapper} @exceptionsafety Strong guarantee: if an exception is thrown, there are no changes in the JSON value. @complexity Constant. @note The name of this function is not yet final and may change in the future. @deprecated This stream operator is deprecated and will be removed in future 4.0.0 of the library. Please use @ref items() instead; that is, replace `json::iterator_wrapper(j)` with `j.items()`. */ JSON_HEDLEY_DEPRECATED_FOR(3.1.0, items()) static iteration_proxy iterator_wrapper(reference ref) noexcept { return ref.items(); } /*! @copydoc iterator_wrapper(reference) */ JSON_HEDLEY_DEPRECATED_FOR(3.1.0, items()) static iteration_proxy iterator_wrapper(const_reference ref) noexcept { return ref.items(); } /*! @brief helper to access iterator member functions in range-based for This function allows to access @ref iterator::key() and @ref iterator::value() during range-based for loops. In these loops, a reference to the JSON values is returned, so there is no access to the underlying iterator. For loop without `items()` function: @code{cpp} for (auto it = j_object.begin(); it != j_object.end(); ++it) { std::cout << "key: " << it.key() << ", value:" << it.value() << '\n'; } @endcode Range-based for loop without `items()` function: @code{cpp} for (auto it : j_object) { // "it" is of type json::reference and has no key() member std::cout << "value: " << it << '\n'; } @endcode Range-based for loop with `items()` function: @code{cpp} for (auto& el : j_object.items()) { std::cout << "key: " << el.key() << ", value:" << el.value() << '\n'; } @endcode The `items()` function also allows to use [structured bindings](https://en.cppreference.com/w/cpp/language/structured_binding) (C++17): @code{cpp} for (auto& [key, val] : j_object.items()) { std::cout << "key: " << key << ", value:" << val << '\n'; } @endcode @note When iterating over an array, `key()` will return the index of the element as string (see example). For primitive types (e.g., numbers), `key()` returns an empty string. @warning Using `items()` on temporary objects is dangerous. Make sure the object's lifetime exeeds the iteration. See for more information. @return iteration proxy object wrapping @a ref with an interface to use in range-based for loops @liveexample{The following code shows how the function is used.,items} @exceptionsafety Strong guarantee: if an exception is thrown, there are no changes in the JSON value. @complexity Constant. @since version 3.1.0, structured bindings support since 3.5.0. */ iteration_proxy items() noexcept { return iteration_proxy(*this); } /*! @copydoc items() */ iteration_proxy items() const noexcept { return iteration_proxy(*this); } /// @} ////////////// // capacity // ////////////// /// @name capacity /// @{ /*! @brief checks whether the container is empty. Checks if a JSON value has no elements (i.e. whether its @ref size is `0`). @return The return value depends on the different types and is defined as follows: Value type | return value ----------- | ------------- null | `true` boolean | `false` string | `false` number | `false` binary | `false` object | result of function `object_t::empty()` array | result of function `array_t::empty()` @liveexample{The following code uses `empty()` to check if a JSON object contains any elements.,empty} @complexity Constant, as long as @ref array_t and @ref object_t satisfy the Container concept; that is, their `empty()` functions have constant complexity. @iterators No changes. @exceptionsafety No-throw guarantee: this function never throws exceptions. @note This function does not return whether a string stored as JSON value is empty - it returns whether the JSON container itself is empty which is false in the case of a string. @requirement This function helps `basic_json` satisfying the [Container](https://en.cppreference.com/w/cpp/named_req/Container) requirements: - The complexity is constant. - Has the semantics of `begin() == end()`. @sa @ref size() -- returns the number of elements @since version 1.0.0 */ bool empty() const noexcept { switch (m_type) { case value_t::null: { // null values are empty return true; } case value_t::array: { // delegate call to array_t::empty() return m_value.array->empty(); } case value_t::object: { // delegate call to object_t::empty() return m_value.object->empty(); } default: { // all other types are nonempty return false; } } } /*! @brief returns the number of elements Returns the number of elements in a JSON value. @return The return value depends on the different types and is defined as follows: Value type | return value ----------- | ------------- null | `0` boolean | `1` string | `1` number | `1` binary | `1` object | result of function object_t::size() array | result of function array_t::size() @liveexample{The following code calls `size()` on the different value types.,size} @complexity Constant, as long as @ref array_t and @ref object_t satisfy the Container concept; that is, their size() functions have constant complexity. @iterators No changes. @exceptionsafety No-throw guarantee: this function never throws exceptions. @note This function does not return the length of a string stored as JSON value - it returns the number of elements in the JSON value which is 1 in the case of a string. @requirement This function helps `basic_json` satisfying the [Container](https://en.cppreference.com/w/cpp/named_req/Container) requirements: - The complexity is constant. - Has the semantics of `std::distance(begin(), end())`. @sa @ref empty() -- checks whether the container is empty @sa @ref max_size() -- returns the maximal number of elements @since version 1.0.0 */ size_type size() const noexcept { switch (m_type) { case value_t::null: { // null values are empty return 0; } case value_t::array: { // delegate call to array_t::size() return m_value.array->size(); } case value_t::object: { // delegate call to object_t::size() return m_value.object->size(); } default: { // all other types have size 1 return 1; } } } /*! @brief returns the maximum possible number of elements Returns the maximum number of elements a JSON value is able to hold due to system or library implementation limitations, i.e. `std::distance(begin(), end())` for the JSON value. @return The return value depends on the different types and is defined as follows: Value type | return value ----------- | ------------- null | `0` (same as `size()`) boolean | `1` (same as `size()`) string | `1` (same as `size()`) number | `1` (same as `size()`) binary | `1` (same as `size()`) object | result of function `object_t::max_size()` array | result of function `array_t::max_size()` @liveexample{The following code calls `max_size()` on the different value types. Note the output is implementation specific.,max_size} @complexity Constant, as long as @ref array_t and @ref object_t satisfy the Container concept; that is, their `max_size()` functions have constant complexity. @iterators No changes. @exceptionsafety No-throw guarantee: this function never throws exceptions. @requirement This function helps `basic_json` satisfying the [Container](https://en.cppreference.com/w/cpp/named_req/Container) requirements: - The complexity is constant. - Has the semantics of returning `b.size()` where `b` is the largest possible JSON value. @sa @ref size() -- returns the number of elements @since version 1.0.0 */ size_type max_size() const noexcept { switch (m_type) { case value_t::array: { // delegate call to array_t::max_size() return m_value.array->max_size(); } case value_t::object: { // delegate call to object_t::max_size() return m_value.object->max_size(); } default: { // all other types have max_size() == size() return size(); } } } /// @} /////////////// // modifiers // /////////////// /// @name modifiers /// @{ /*! @brief clears the contents Clears the content of a JSON value and resets it to the default value as if @ref basic_json(value_t) would have been called with the current value type from @ref type(): Value type | initial value ----------- | ------------- null | `null` boolean | `false` string | `""` number | `0` binary | An empty byte vector object | `{}` array | `[]` @post Has the same effect as calling @code {.cpp} *this = basic_json(type()); @endcode @liveexample{The example below shows the effect of `clear()` to different JSON types.,clear} @complexity Linear in the size of the JSON value. @iterators All iterators, pointers and references related to this container are invalidated. @exceptionsafety No-throw guarantee: this function never throws exceptions. @sa @ref basic_json(value_t) -- constructor that creates an object with the same value than calling `clear()` @since version 1.0.0 */ void clear() noexcept { switch (m_type) { case value_t::number_integer: { m_value.number_integer = 0; break; } case value_t::number_unsigned: { m_value.number_unsigned = 0; break; } case value_t::number_float: { m_value.number_float = 0.0; break; } case value_t::boolean: { m_value.boolean = false; break; } case value_t::string: { m_value.string->clear(); break; } case value_t::binary: { m_value.binary->clear(); break; } case value_t::array: { m_value.array->clear(); break; } case value_t::object: { m_value.object->clear(); break; } default: break; } } /*! @brief add an object to an array Appends the given element @a val to the end of the JSON value. If the function is called on a JSON null value, an empty array is created before appending @a val. @param[in] val the value to add to the JSON array @throw type_error.308 when called on a type other than JSON array or null; example: `"cannot use push_back() with number"` @complexity Amortized constant. @liveexample{The example shows how `push_back()` and `+=` can be used to add elements to a JSON array. Note how the `null` value was silently converted to a JSON array.,push_back} @since version 1.0.0 */ void push_back(basic_json&& val) { // push_back only works for null objects or arrays if (JSON_HEDLEY_UNLIKELY(!(is_null() || is_array()))) { JSON_THROW(type_error::create(308, "cannot use push_back() with " + std::string(type_name()))); } // transform null object into an array if (is_null()) { m_type = value_t::array; m_value = value_t::array; assert_invariant(); } // add element to array (move semantics) m_value.array->push_back(std::move(val)); // if val is moved from, basic_json move constructor marks it null so we do not call the destructor } /*! @brief add an object to an array @copydoc push_back(basic_json&&) */ reference operator+=(basic_json&& val) { push_back(std::move(val)); return *this; } /*! @brief add an object to an array @copydoc push_back(basic_json&&) */ void push_back(const basic_json& val) { // push_back only works for null objects or arrays if (JSON_HEDLEY_UNLIKELY(!(is_null() || is_array()))) { JSON_THROW(type_error::create(308, "cannot use push_back() with " + std::string(type_name()))); } // transform null object into an array if (is_null()) { m_type = value_t::array; m_value = value_t::array; assert_invariant(); } // add element to array m_value.array->push_back(val); } /*! @brief add an object to an array @copydoc push_back(basic_json&&) */ reference operator+=(const basic_json& val) { push_back(val); return *this; } /*! @brief add an object to an object Inserts the given element @a val to the JSON object. If the function is called on a JSON null value, an empty object is created before inserting @a val. @param[in] val the value to add to the JSON object @throw type_error.308 when called on a type other than JSON object or null; example: `"cannot use push_back() with number"` @complexity Logarithmic in the size of the container, O(log(`size()`)). @liveexample{The example shows how `push_back()` and `+=` can be used to add elements to a JSON object. Note how the `null` value was silently converted to a JSON object.,push_back__object_t__value} @since version 1.0.0 */ void push_back(const typename object_t::value_type& val) { // push_back only works for null objects or objects if (JSON_HEDLEY_UNLIKELY(!(is_null() || is_object()))) { JSON_THROW(type_error::create(308, "cannot use push_back() with " + std::string(type_name()))); } // transform null object into an object if (is_null()) { m_type = value_t::object; m_value = value_t::object; assert_invariant(); } // add element to array m_value.object->insert(val); } /*! @brief add an object to an object @copydoc push_back(const typename object_t::value_type&) */ reference operator+=(const typename object_t::value_type& val) { push_back(val); return *this; } /*! @brief add an object to an object This function allows to use `push_back` with an initializer list. In case 1. the current value is an object, 2. the initializer list @a init contains only two elements, and 3. the first element of @a init is a string, @a init is converted into an object element and added using @ref push_back(const typename object_t::value_type&). Otherwise, @a init is converted to a JSON value and added using @ref push_back(basic_json&&). @param[in] init an initializer list @complexity Linear in the size of the initializer list @a init. @note This function is required to resolve an ambiguous overload error, because pairs like `{"key", "value"}` can be both interpreted as `object_t::value_type` or `std::initializer_list`, see https://github.com/nlohmann/json/issues/235 for more information. @liveexample{The example shows how initializer lists are treated as objects when possible.,push_back__initializer_list} */ void push_back(initializer_list_t init) { if (is_object() && init.size() == 2 && (*init.begin())->is_string()) { basic_json&& key = init.begin()->moved_or_copied(); push_back(typename object_t::value_type( std::move(key.get_ref()), (init.begin() + 1)->moved_or_copied())); } else { push_back(basic_json(init)); } } /*! @brief add an object to an object @copydoc push_back(initializer_list_t) */ reference operator+=(initializer_list_t init) { push_back(init); return *this; } /*! @brief add an object to an array Creates a JSON value from the passed parameters @a args to the end of the JSON value. If the function is called on a JSON null value, an empty array is created before appending the value created from @a args. @param[in] args arguments to forward to a constructor of @ref basic_json @tparam Args compatible types to create a @ref basic_json object @return reference to the inserted element @throw type_error.311 when called on a type other than JSON array or null; example: `"cannot use emplace_back() with number"` @complexity Amortized constant. @liveexample{The example shows how `push_back()` can be used to add elements to a JSON array. Note how the `null` value was silently converted to a JSON array.,emplace_back} @since version 2.0.8, returns reference since 3.7.0 */ template reference emplace_back(Args&& ... args) { // emplace_back only works for null objects or arrays if (JSON_HEDLEY_UNLIKELY(!(is_null() || is_array()))) { JSON_THROW(type_error::create(311, "cannot use emplace_back() with " + std::string(type_name()))); } // transform null object into an array if (is_null()) { m_type = value_t::array; m_value = value_t::array; assert_invariant(); } // add element to array (perfect forwarding) #ifdef JSON_HAS_CPP_17 return m_value.array->emplace_back(std::forward(args)...); #else m_value.array->emplace_back(std::forward(args)...); return m_value.array->back(); #endif } /*! @brief add an object to an object if key does not exist Inserts a new element into a JSON object constructed in-place with the given @a args if there is no element with the key in the container. If the function is called on a JSON null value, an empty object is created before appending the value created from @a args. @param[in] args arguments to forward to a constructor of @ref basic_json @tparam Args compatible types to create a @ref basic_json object @return a pair consisting of an iterator to the inserted element, or the already-existing element if no insertion happened, and a bool denoting whether the insertion took place. @throw type_error.311 when called on a type other than JSON object or null; example: `"cannot use emplace() with number"` @complexity Logarithmic in the size of the container, O(log(`size()`)). @liveexample{The example shows how `emplace()` can be used to add elements to a JSON object. Note how the `null` value was silently converted to a JSON object. Further note how no value is added if there was already one value stored with the same key.,emplace} @since version 2.0.8 */ template std::pair emplace(Args&& ... args) { // emplace only works for null objects or arrays if (JSON_HEDLEY_UNLIKELY(!(is_null() || is_object()))) { JSON_THROW(type_error::create(311, "cannot use emplace() with " + std::string(type_name()))); } // transform null object into an object if (is_null()) { m_type = value_t::object; m_value = value_t::object; assert_invariant(); } // add element to array (perfect forwarding) auto res = m_value.object->emplace(std::forward(args)...); // create result iterator and set iterator to the result of emplace auto it = begin(); it.m_it.object_iterator = res.first; // return pair of iterator and boolean return {it, res.second}; } /// Helper for insertion of an iterator /// @note: This uses std::distance to support GCC 4.8, /// see https://github.com/nlohmann/json/pull/1257 template iterator insert_iterator(const_iterator pos, Args&& ... args) { iterator result(this); JSON_ASSERT(m_value.array != nullptr); auto insert_pos = std::distance(m_value.array->begin(), pos.m_it.array_iterator); m_value.array->insert(pos.m_it.array_iterator, std::forward(args)...); result.m_it.array_iterator = m_value.array->begin() + insert_pos; // This could have been written as: // result.m_it.array_iterator = m_value.array->insert(pos.m_it.array_iterator, cnt, val); // but the return value of insert is missing in GCC 4.8, so it is written this way instead. return result; } /*! @brief inserts element Inserts element @a val before iterator @a pos. @param[in] pos iterator before which the content will be inserted; may be the end() iterator @param[in] val element to insert @return iterator pointing to the inserted @a val. @throw type_error.309 if called on JSON values other than arrays; example: `"cannot use insert() with string"` @throw invalid_iterator.202 if @a pos is not an iterator of *this; example: `"iterator does not fit current value"` @complexity Constant plus linear in the distance between @a pos and end of the container. @liveexample{The example shows how `insert()` is used.,insert} @since version 1.0.0 */ iterator insert(const_iterator pos, const basic_json& val) { // insert only works for arrays if (JSON_HEDLEY_LIKELY(is_array())) { // check if iterator pos fits to this JSON value if (JSON_HEDLEY_UNLIKELY(pos.m_object != this)) { JSON_THROW(invalid_iterator::create(202, "iterator does not fit current value")); } // insert to array and return iterator return insert_iterator(pos, val); } JSON_THROW(type_error::create(309, "cannot use insert() with " + std::string(type_name()))); } /*! @brief inserts element @copydoc insert(const_iterator, const basic_json&) */ iterator insert(const_iterator pos, basic_json&& val) { return insert(pos, val); } /*! @brief inserts elements Inserts @a cnt copies of @a val before iterator @a pos. @param[in] pos iterator before which the content will be inserted; may be the end() iterator @param[in] cnt number of copies of @a val to insert @param[in] val element to insert @return iterator pointing to the first element inserted, or @a pos if `cnt==0` @throw type_error.309 if called on JSON values other than arrays; example: `"cannot use insert() with string"` @throw invalid_iterator.202 if @a pos is not an iterator of *this; example: `"iterator does not fit current value"` @complexity Linear in @a cnt plus linear in the distance between @a pos and end of the container. @liveexample{The example shows how `insert()` is used.,insert__count} @since version 1.0.0 */ iterator insert(const_iterator pos, size_type cnt, const basic_json& val) { // insert only works for arrays if (JSON_HEDLEY_LIKELY(is_array())) { // check if iterator pos fits to this JSON value if (JSON_HEDLEY_UNLIKELY(pos.m_object != this)) { JSON_THROW(invalid_iterator::create(202, "iterator does not fit current value")); } // insert to array and return iterator return insert_iterator(pos, cnt, val); } JSON_THROW(type_error::create(309, "cannot use insert() with " + std::string(type_name()))); } /*! @brief inserts elements Inserts elements from range `[first, last)` before iterator @a pos. @param[in] pos iterator before which the content will be inserted; may be the end() iterator @param[in] first begin of the range of elements to insert @param[in] last end of the range of elements to insert @throw type_error.309 if called on JSON values other than arrays; example: `"cannot use insert() with string"` @throw invalid_iterator.202 if @a pos is not an iterator of *this; example: `"iterator does not fit current value"` @throw invalid_iterator.210 if @a first and @a last do not belong to the same JSON value; example: `"iterators do not fit"` @throw invalid_iterator.211 if @a first or @a last are iterators into container for which insert is called; example: `"passed iterators may not belong to container"` @return iterator pointing to the first element inserted, or @a pos if `first==last` @complexity Linear in `std::distance(first, last)` plus linear in the distance between @a pos and end of the container. @liveexample{The example shows how `insert()` is used.,insert__range} @since version 1.0.0 */ iterator insert(const_iterator pos, const_iterator first, const_iterator last) { // insert only works for arrays if (JSON_HEDLEY_UNLIKELY(!is_array())) { JSON_THROW(type_error::create(309, "cannot use insert() with " + std::string(type_name()))); } // check if iterator pos fits to this JSON value if (JSON_HEDLEY_UNLIKELY(pos.m_object != this)) { JSON_THROW(invalid_iterator::create(202, "iterator does not fit current value")); } // check if range iterators belong to the same JSON object if (JSON_HEDLEY_UNLIKELY(first.m_object != last.m_object)) { JSON_THROW(invalid_iterator::create(210, "iterators do not fit")); } if (JSON_HEDLEY_UNLIKELY(first.m_object == this)) { JSON_THROW(invalid_iterator::create(211, "passed iterators may not belong to container")); } // insert to array and return iterator return insert_iterator(pos, first.m_it.array_iterator, last.m_it.array_iterator); } /*! @brief inserts elements Inserts elements from initializer list @a ilist before iterator @a pos. @param[in] pos iterator before which the content will be inserted; may be the end() iterator @param[in] ilist initializer list to insert the values from @throw type_error.309 if called on JSON values other than arrays; example: `"cannot use insert() with string"` @throw invalid_iterator.202 if @a pos is not an iterator of *this; example: `"iterator does not fit current value"` @return iterator pointing to the first element inserted, or @a pos if `ilist` is empty @complexity Linear in `ilist.size()` plus linear in the distance between @a pos and end of the container. @liveexample{The example shows how `insert()` is used.,insert__ilist} @since version 1.0.0 */ iterator insert(const_iterator pos, initializer_list_t ilist) { // insert only works for arrays if (JSON_HEDLEY_UNLIKELY(!is_array())) { JSON_THROW(type_error::create(309, "cannot use insert() with " + std::string(type_name()))); } // check if iterator pos fits to this JSON value if (JSON_HEDLEY_UNLIKELY(pos.m_object != this)) { JSON_THROW(invalid_iterator::create(202, "iterator does not fit current value")); } // insert to array and return iterator return insert_iterator(pos, ilist.begin(), ilist.end()); } /*! @brief inserts elements Inserts elements from range `[first, last)`. @param[in] first begin of the range of elements to insert @param[in] last end of the range of elements to insert @throw type_error.309 if called on JSON values other than objects; example: `"cannot use insert() with string"` @throw invalid_iterator.202 if iterator @a first or @a last does does not point to an object; example: `"iterators first and last must point to objects"` @throw invalid_iterator.210 if @a first and @a last do not belong to the same JSON value; example: `"iterators do not fit"` @complexity Logarithmic: `O(N*log(size() + N))`, where `N` is the number of elements to insert. @liveexample{The example shows how `insert()` is used.,insert__range_object} @since version 3.0.0 */ void insert(const_iterator first, const_iterator last) { // insert only works for objects if (JSON_HEDLEY_UNLIKELY(!is_object())) { JSON_THROW(type_error::create(309, "cannot use insert() with " + std::string(type_name()))); } // check if range iterators belong to the same JSON object if (JSON_HEDLEY_UNLIKELY(first.m_object != last.m_object)) { JSON_THROW(invalid_iterator::create(210, "iterators do not fit")); } // passed iterators must belong to objects if (JSON_HEDLEY_UNLIKELY(!first.m_object->is_object())) { JSON_THROW(invalid_iterator::create(202, "iterators first and last must point to objects")); } m_value.object->insert(first.m_it.object_iterator, last.m_it.object_iterator); } /*! @brief updates a JSON object from another object, overwriting existing keys Inserts all values from JSON object @a j and overwrites existing keys. @param[in] j JSON object to read values from @throw type_error.312 if called on JSON values other than objects; example: `"cannot use update() with string"` @complexity O(N*log(size() + N)), where N is the number of elements to insert. @liveexample{The example shows how `update()` is used.,update} @sa https://docs.python.org/3.6/library/stdtypes.html#dict.update @since version 3.0.0 */ void update(const_reference j) { // implicitly convert null value to an empty object if (is_null()) { m_type = value_t::object; m_value.object = create(); assert_invariant(); } if (JSON_HEDLEY_UNLIKELY(!is_object())) { JSON_THROW(type_error::create(312, "cannot use update() with " + std::string(type_name()))); } if (JSON_HEDLEY_UNLIKELY(!j.is_object())) { JSON_THROW(type_error::create(312, "cannot use update() with " + std::string(j.type_name()))); } for (auto it = j.cbegin(); it != j.cend(); ++it) { m_value.object->operator[](it.key()) = it.value(); } } /*! @brief updates a JSON object from another object, overwriting existing keys Inserts all values from from range `[first, last)` and overwrites existing keys. @param[in] first begin of the range of elements to insert @param[in] last end of the range of elements to insert @throw type_error.312 if called on JSON values other than objects; example: `"cannot use update() with string"` @throw invalid_iterator.202 if iterator @a first or @a last does does not point to an object; example: `"iterators first and last must point to objects"` @throw invalid_iterator.210 if @a first and @a last do not belong to the same JSON value; example: `"iterators do not fit"` @complexity O(N*log(size() + N)), where N is the number of elements to insert. @liveexample{The example shows how `update()` is used__range.,update} @sa https://docs.python.org/3.6/library/stdtypes.html#dict.update @since version 3.0.0 */ void update(const_iterator first, const_iterator last) { // implicitly convert null value to an empty object if (is_null()) { m_type = value_t::object; m_value.object = create(); assert_invariant(); } if (JSON_HEDLEY_UNLIKELY(!is_object())) { JSON_THROW(type_error::create(312, "cannot use update() with " + std::string(type_name()))); } // check if range iterators belong to the same JSON object if (JSON_HEDLEY_UNLIKELY(first.m_object != last.m_object)) { JSON_THROW(invalid_iterator::create(210, "iterators do not fit")); } // passed iterators must belong to objects if (JSON_HEDLEY_UNLIKELY(!first.m_object->is_object() || !last.m_object->is_object())) { JSON_THROW(invalid_iterator::create(202, "iterators first and last must point to objects")); } for (auto it = first; it != last; ++it) { m_value.object->operator[](it.key()) = it.value(); } } /*! @brief exchanges the values Exchanges the contents of the JSON value with those of @a other. Does not invoke any move, copy, or swap operations on individual elements. All iterators and references remain valid. The past-the-end iterator is invalidated. @param[in,out] other JSON value to exchange the contents with @complexity Constant. @liveexample{The example below shows how JSON values can be swapped with `swap()`.,swap__reference} @since version 1.0.0 */ void swap(reference other) noexcept ( std::is_nothrow_move_constructible::value&& std::is_nothrow_move_assignable::value&& std::is_nothrow_move_constructible::value&& std::is_nothrow_move_assignable::value ) { std::swap(m_type, other.m_type); std::swap(m_value, other.m_value); assert_invariant(); } /*! @brief exchanges the values Exchanges the contents of the JSON value from @a left with those of @a right. Does not invoke any move, copy, or swap operations on individual elements. All iterators and references remain valid. The past-the-end iterator is invalidated. implemented as a friend function callable via ADL. @param[in,out] left JSON value to exchange the contents with @param[in,out] right JSON value to exchange the contents with @complexity Constant. @liveexample{The example below shows how JSON values can be swapped with `swap()`.,swap__reference} @since version 1.0.0 */ friend void swap(reference left, reference right) noexcept ( std::is_nothrow_move_constructible::value&& std::is_nothrow_move_assignable::value&& std::is_nothrow_move_constructible::value&& std::is_nothrow_move_assignable::value ) { left.swap(right); } /*! @brief exchanges the values Exchanges the contents of a JSON array with those of @a other. Does not invoke any move, copy, or swap operations on individual elements. All iterators and references remain valid. The past-the-end iterator is invalidated. @param[in,out] other array to exchange the contents with @throw type_error.310 when JSON value is not an array; example: `"cannot use swap() with string"` @complexity Constant. @liveexample{The example below shows how arrays can be swapped with `swap()`.,swap__array_t} @since version 1.0.0 */ void swap(array_t& other) { // swap only works for arrays if (JSON_HEDLEY_LIKELY(is_array())) { std::swap(*(m_value.array), other); } else { JSON_THROW(type_error::create(310, "cannot use swap() with " + std::string(type_name()))); } } /*! @brief exchanges the values Exchanges the contents of a JSON object with those of @a other. Does not invoke any move, copy, or swap operations on individual elements. All iterators and references remain valid. The past-the-end iterator is invalidated. @param[in,out] other object to exchange the contents with @throw type_error.310 when JSON value is not an object; example: `"cannot use swap() with string"` @complexity Constant. @liveexample{The example below shows how objects can be swapped with `swap()`.,swap__object_t} @since version 1.0.0 */ void swap(object_t& other) { // swap only works for objects if (JSON_HEDLEY_LIKELY(is_object())) { std::swap(*(m_value.object), other); } else { JSON_THROW(type_error::create(310, "cannot use swap() with " + std::string(type_name()))); } } /*! @brief exchanges the values Exchanges the contents of a JSON string with those of @a other. Does not invoke any move, copy, or swap operations on individual elements. All iterators and references remain valid. The past-the-end iterator is invalidated. @param[in,out] other string to exchange the contents with @throw type_error.310 when JSON value is not a string; example: `"cannot use swap() with boolean"` @complexity Constant. @liveexample{The example below shows how strings can be swapped with `swap()`.,swap__string_t} @since version 1.0.0 */ void swap(string_t& other) { // swap only works for strings if (JSON_HEDLEY_LIKELY(is_string())) { std::swap(*(m_value.string), other); } else { JSON_THROW(type_error::create(310, "cannot use swap() with " + std::string(type_name()))); } } /*! @brief exchanges the values Exchanges the contents of a JSON string with those of @a other. Does not invoke any move, copy, or swap operations on individual elements. All iterators and references remain valid. The past-the-end iterator is invalidated. @param[in,out] other binary to exchange the contents with @throw type_error.310 when JSON value is not a string; example: `"cannot use swap() with boolean"` @complexity Constant. @liveexample{The example below shows how strings can be swapped with `swap()`.,swap__binary_t} @since version 3.8.0 */ void swap(binary_t& other) { // swap only works for strings if (JSON_HEDLEY_LIKELY(is_binary())) { std::swap(*(m_value.binary), other); } else { JSON_THROW(type_error::create(310, "cannot use swap() with " + std::string(type_name()))); } } /// @copydoc swap(binary_t) void swap(typename binary_t::container_type& other) { // swap only works for strings if (JSON_HEDLEY_LIKELY(is_binary())) { std::swap(*(m_value.binary), other); } else { JSON_THROW(type_error::create(310, "cannot use swap() with " + std::string(type_name()))); } } /// @} public: ////////////////////////////////////////// // lexicographical comparison operators // ////////////////////////////////////////// /// @name lexicographical comparison operators /// @{ /*! @brief comparison: equal Compares two JSON values for equality according to the following rules: - Two JSON values are equal if (1) they are from the same type and (2) their stored values are the same according to their respective `operator==`. - Integer and floating-point numbers are automatically converted before comparison. Note that two NaN values are always treated as unequal. - Two JSON null values are equal. @note Floating-point inside JSON values numbers are compared with `json::number_float_t::operator==` which is `double::operator==` by default. To compare floating-point while respecting an epsilon, an alternative [comparison function](https://github.com/mariokonrad/marnav/blob/master/include/marnav/math/floatingpoint.hpp#L34-#L39) could be used, for instance @code {.cpp} template::value, T>::type> inline bool is_same(T a, T b, T epsilon = std::numeric_limits::epsilon()) noexcept { return std::abs(a - b) <= epsilon; } @endcode Or you can self-defined operator equal function like this: @code {.cpp} bool my_equal(const_reference lhs, const_reference rhs) { const auto lhs_type lhs.type(); const auto rhs_type rhs.type(); if (lhs_type == rhs_type) { switch(lhs_type) // self_defined case case value_t::number_float: return std::abs(lhs - rhs) <= std::numeric_limits::epsilon(); // other cases remain the same with the original ... } ... } @endcode @note NaN values never compare equal to themselves or to other NaN values. @param[in] lhs first JSON value to consider @param[in] rhs second JSON value to consider @return whether the values @a lhs and @a rhs are equal @exceptionsafety No-throw guarantee: this function never throws exceptions. @complexity Linear. @liveexample{The example demonstrates comparing several JSON types.,operator__equal} @since version 1.0.0 */ friend bool operator==(const_reference lhs, const_reference rhs) noexcept { const auto lhs_type = lhs.type(); const auto rhs_type = rhs.type(); if (lhs_type == rhs_type) { switch (lhs_type) { case value_t::array: return *lhs.m_value.array == *rhs.m_value.array; case value_t::object: return *lhs.m_value.object == *rhs.m_value.object; case value_t::null: return true; case value_t::string: return *lhs.m_value.string == *rhs.m_value.string; case value_t::boolean: return lhs.m_value.boolean == rhs.m_value.boolean; case value_t::number_integer: return lhs.m_value.number_integer == rhs.m_value.number_integer; case value_t::number_unsigned: return lhs.m_value.number_unsigned == rhs.m_value.number_unsigned; case value_t::number_float: return lhs.m_value.number_float == rhs.m_value.number_float; case value_t::binary: return *lhs.m_value.binary == *rhs.m_value.binary; default: return false; } } else if (lhs_type == value_t::number_integer && rhs_type == value_t::number_float) { return static_cast(lhs.m_value.number_integer) == rhs.m_value.number_float; } else if (lhs_type == value_t::number_float && rhs_type == value_t::number_integer) { return lhs.m_value.number_float == static_cast(rhs.m_value.number_integer); } else if (lhs_type == value_t::number_unsigned && rhs_type == value_t::number_float) { return static_cast(lhs.m_value.number_unsigned) == rhs.m_value.number_float; } else if (lhs_type == value_t::number_float && rhs_type == value_t::number_unsigned) { return lhs.m_value.number_float == static_cast(rhs.m_value.number_unsigned); } else if (lhs_type == value_t::number_unsigned && rhs_type == value_t::number_integer) { return static_cast(lhs.m_value.number_unsigned) == rhs.m_value.number_integer; } else if (lhs_type == value_t::number_integer && rhs_type == value_t::number_unsigned) { return lhs.m_value.number_integer == static_cast(rhs.m_value.number_unsigned); } return false; } /*! @brief comparison: equal @copydoc operator==(const_reference, const_reference) */ template::value, int>::type = 0> friend bool operator==(const_reference lhs, const ScalarType rhs) noexcept { return lhs == basic_json(rhs); } /*! @brief comparison: equal @copydoc operator==(const_reference, const_reference) */ template::value, int>::type = 0> friend bool operator==(const ScalarType lhs, const_reference rhs) noexcept { return basic_json(lhs) == rhs; } /*! @brief comparison: not equal Compares two JSON values for inequality by calculating `not (lhs == rhs)`. @param[in] lhs first JSON value to consider @param[in] rhs second JSON value to consider @return whether the values @a lhs and @a rhs are not equal @complexity Linear. @exceptionsafety No-throw guarantee: this function never throws exceptions. @liveexample{The example demonstrates comparing several JSON types.,operator__notequal} @since version 1.0.0 */ friend bool operator!=(const_reference lhs, const_reference rhs) noexcept { return !(lhs == rhs); } /*! @brief comparison: not equal @copydoc operator!=(const_reference, const_reference) */ template::value, int>::type = 0> friend bool operator!=(const_reference lhs, const ScalarType rhs) noexcept { return lhs != basic_json(rhs); } /*! @brief comparison: not equal @copydoc operator!=(const_reference, const_reference) */ template::value, int>::type = 0> friend bool operator!=(const ScalarType lhs, const_reference rhs) noexcept { return basic_json(lhs) != rhs; } /*! @brief comparison: less than Compares whether one JSON value @a lhs is less than another JSON value @a rhs according to the following rules: - If @a lhs and @a rhs have the same type, the values are compared using the default `<` operator. - Integer and floating-point numbers are automatically converted before comparison - In case @a lhs and @a rhs have different types, the values are ignored and the order of the types is considered, see @ref operator<(const value_t, const value_t). @param[in] lhs first JSON value to consider @param[in] rhs second JSON value to consider @return whether @a lhs is less than @a rhs @complexity Linear. @exceptionsafety No-throw guarantee: this function never throws exceptions. @liveexample{The example demonstrates comparing several JSON types.,operator__less} @since version 1.0.0 */ friend bool operator<(const_reference lhs, const_reference rhs) noexcept { const auto lhs_type = lhs.type(); const auto rhs_type = rhs.type(); if (lhs_type == rhs_type) { switch (lhs_type) { case value_t::array: // note parentheses are necessary, see // https://github.com/nlohmann/json/issues/1530 return (*lhs.m_value.array) < (*rhs.m_value.array); case value_t::object: return (*lhs.m_value.object) < (*rhs.m_value.object); case value_t::null: return false; case value_t::string: return (*lhs.m_value.string) < (*rhs.m_value.string); case value_t::boolean: return (lhs.m_value.boolean) < (rhs.m_value.boolean); case value_t::number_integer: return (lhs.m_value.number_integer) < (rhs.m_value.number_integer); case value_t::number_unsigned: return (lhs.m_value.number_unsigned) < (rhs.m_value.number_unsigned); case value_t::number_float: return (lhs.m_value.number_float) < (rhs.m_value.number_float); case value_t::binary: return (*lhs.m_value.binary) < (*rhs.m_value.binary); default: return false; } } else if (lhs_type == value_t::number_integer && rhs_type == value_t::number_float) { return static_cast(lhs.m_value.number_integer) < rhs.m_value.number_float; } else if (lhs_type == value_t::number_float && rhs_type == value_t::number_integer) { return lhs.m_value.number_float < static_cast(rhs.m_value.number_integer); } else if (lhs_type == value_t::number_unsigned && rhs_type == value_t::number_float) { return static_cast(lhs.m_value.number_unsigned) < rhs.m_value.number_float; } else if (lhs_type == value_t::number_float && rhs_type == value_t::number_unsigned) { return lhs.m_value.number_float < static_cast(rhs.m_value.number_unsigned); } else if (lhs_type == value_t::number_integer && rhs_type == value_t::number_unsigned) { return lhs.m_value.number_integer < static_cast(rhs.m_value.number_unsigned); } else if (lhs_type == value_t::number_unsigned && rhs_type == value_t::number_integer) { return static_cast(lhs.m_value.number_unsigned) < rhs.m_value.number_integer; } // We only reach this line if we cannot compare values. In that case, // we compare types. Note we have to call the operator explicitly, // because MSVC has problems otherwise. return operator<(lhs_type, rhs_type); } /*! @brief comparison: less than @copydoc operator<(const_reference, const_reference) */ template::value, int>::type = 0> friend bool operator<(const_reference lhs, const ScalarType rhs) noexcept { return lhs < basic_json(rhs); } /*! @brief comparison: less than @copydoc operator<(const_reference, const_reference) */ template::value, int>::type = 0> friend bool operator<(const ScalarType lhs, const_reference rhs) noexcept { return basic_json(lhs) < rhs; } /*! @brief comparison: less than or equal Compares whether one JSON value @a lhs is less than or equal to another JSON value by calculating `not (rhs < lhs)`. @param[in] lhs first JSON value to consider @param[in] rhs second JSON value to consider @return whether @a lhs is less than or equal to @a rhs @complexity Linear. @exceptionsafety No-throw guarantee: this function never throws exceptions. @liveexample{The example demonstrates comparing several JSON types.,operator__greater} @since version 1.0.0 */ friend bool operator<=(const_reference lhs, const_reference rhs) noexcept { return !(rhs < lhs); } /*! @brief comparison: less than or equal @copydoc operator<=(const_reference, const_reference) */ template::value, int>::type = 0> friend bool operator<=(const_reference lhs, const ScalarType rhs) noexcept { return lhs <= basic_json(rhs); } /*! @brief comparison: less than or equal @copydoc operator<=(const_reference, const_reference) */ template::value, int>::type = 0> friend bool operator<=(const ScalarType lhs, const_reference rhs) noexcept { return basic_json(lhs) <= rhs; } /*! @brief comparison: greater than Compares whether one JSON value @a lhs is greater than another JSON value by calculating `not (lhs <= rhs)`. @param[in] lhs first JSON value to consider @param[in] rhs second JSON value to consider @return whether @a lhs is greater than to @a rhs @complexity Linear. @exceptionsafety No-throw guarantee: this function never throws exceptions. @liveexample{The example demonstrates comparing several JSON types.,operator__lessequal} @since version 1.0.0 */ friend bool operator>(const_reference lhs, const_reference rhs) noexcept { return !(lhs <= rhs); } /*! @brief comparison: greater than @copydoc operator>(const_reference, const_reference) */ template::value, int>::type = 0> friend bool operator>(const_reference lhs, const ScalarType rhs) noexcept { return lhs > basic_json(rhs); } /*! @brief comparison: greater than @copydoc operator>(const_reference, const_reference) */ template::value, int>::type = 0> friend bool operator>(const ScalarType lhs, const_reference rhs) noexcept { return basic_json(lhs) > rhs; } /*! @brief comparison: greater than or equal Compares whether one JSON value @a lhs is greater than or equal to another JSON value by calculating `not (lhs < rhs)`. @param[in] lhs first JSON value to consider @param[in] rhs second JSON value to consider @return whether @a lhs is greater than or equal to @a rhs @complexity Linear. @exceptionsafety No-throw guarantee: this function never throws exceptions. @liveexample{The example demonstrates comparing several JSON types.,operator__greaterequal} @since version 1.0.0 */ friend bool operator>=(const_reference lhs, const_reference rhs) noexcept { return !(lhs < rhs); } /*! @brief comparison: greater than or equal @copydoc operator>=(const_reference, const_reference) */ template::value, int>::type = 0> friend bool operator>=(const_reference lhs, const ScalarType rhs) noexcept { return lhs >= basic_json(rhs); } /*! @brief comparison: greater than or equal @copydoc operator>=(const_reference, const_reference) */ template::value, int>::type = 0> friend bool operator>=(const ScalarType lhs, const_reference rhs) noexcept { return basic_json(lhs) >= rhs; } /// @} /////////////////// // serialization // /////////////////// /// @name serialization /// @{ /*! @brief serialize to stream Serialize the given JSON value @a j to the output stream @a o. The JSON value will be serialized using the @ref dump member function. - The indentation of the output can be controlled with the member variable `width` of the output stream @a o. For instance, using the manipulator `std::setw(4)` on @a o sets the indentation level to `4` and the serialization result is the same as calling `dump(4)`. - The indentation character can be controlled with the member variable `fill` of the output stream @a o. For instance, the manipulator `std::setfill('\\t')` sets indentation to use a tab character rather than the default space character. @param[in,out] o stream to serialize to @param[in] j JSON value to serialize @return the stream @a o @throw type_error.316 if a string stored inside the JSON value is not UTF-8 encoded @complexity Linear. @liveexample{The example below shows the serialization with different parameters to `width` to adjust the indentation level.,operator_serialize} @since version 1.0.0; indentation character added in version 3.0.0 */ friend std::ostream& operator<<(std::ostream& o, const basic_json& j) { // read width member and use it as indentation parameter if nonzero const bool pretty_print = o.width() > 0; const auto indentation = pretty_print ? o.width() : 0; // reset width to 0 for subsequent calls to this stream o.width(0); // do the actual serialization serializer s(detail::output_adapter(o), o.fill()); s.dump(j, pretty_print, false, static_cast(indentation)); return o; } /*! @brief serialize to stream @deprecated This stream operator is deprecated and will be removed in future 4.0.0 of the library. Please use @ref operator<<(std::ostream&, const basic_json&) instead; that is, replace calls like `j >> o;` with `o << j;`. @since version 1.0.0; deprecated since version 3.0.0 */ JSON_HEDLEY_DEPRECATED_FOR(3.0.0, operator<<(std::ostream&, const basic_json&)) friend std::ostream& operator>>(const basic_json& j, std::ostream& o) { return o << j; } /// @} ///////////////////// // deserialization // ///////////////////// /// @name deserialization /// @{ /*! @brief deserialize from a compatible input @tparam InputType A compatible input, for instance - an std::istream object - a FILE pointer - a C-style array of characters - a pointer to a null-terminated string of single byte characters - an object obj for which begin(obj) and end(obj) produces a valid pair of iterators. @param[in] i input to read from @param[in] cb a parser callback function of type @ref parser_callback_t which is used to control the deserialization by filtering unwanted values (optional) @param[in] allow_exceptions whether to throw exceptions in case of a parse error (optional, true by default) @param[in] ignore_comments whether comments should be ignored and treated like whitespace (true) or yield a parse error (true); (optional, false by default) @return deserialized JSON value; in case of a parse error and @a allow_exceptions set to `false`, the return value will be value_t::discarded. @throw parse_error.101 if a parse error occurs; example: `""unexpected end of input; expected string literal""` @throw parse_error.102 if to_unicode fails or surrogate error @throw parse_error.103 if to_unicode fails @complexity Linear in the length of the input. The parser is a predictive LL(1) parser. The complexity can be higher if the parser callback function @a cb or reading from the input @a i has a super-linear complexity. @note A UTF-8 byte order mark is silently ignored. @liveexample{The example below demonstrates the `parse()` function reading from an array.,parse__array__parser_callback_t} @liveexample{The example below demonstrates the `parse()` function with and without callback function.,parse__string__parser_callback_t} @liveexample{The example below demonstrates the `parse()` function with and without callback function.,parse__istream__parser_callback_t} @liveexample{The example below demonstrates the `parse()` function reading from a contiguous container.,parse__contiguouscontainer__parser_callback_t} @since version 2.0.3 (contiguous containers); version 3.9.0 allowed to ignore comments. */ template JSON_HEDLEY_WARN_UNUSED_RESULT static basic_json parse(InputType&& i, const parser_callback_t cb = nullptr, const bool allow_exceptions = true, const bool ignore_comments = false) { basic_json result; parser(detail::input_adapter(std::forward(i)), cb, allow_exceptions, ignore_comments).parse(true, result); return result; } /*! @brief deserialize from a pair of character iterators The value_type of the iterator must be a integral type with size of 1, 2 or 4 bytes, which will be interpreted respectively as UTF-8, UTF-16 and UTF-32. @param[in] first iterator to start of character range @param[in] last iterator to end of character range @param[in] cb a parser callback function of type @ref parser_callback_t which is used to control the deserialization by filtering unwanted values (optional) @param[in] allow_exceptions whether to throw exceptions in case of a parse error (optional, true by default) @param[in] ignore_comments whether comments should be ignored and treated like whitespace (true) or yield a parse error (true); (optional, false by default) @return deserialized JSON value; in case of a parse error and @a allow_exceptions set to `false`, the return value will be value_t::discarded. @throw parse_error.101 if a parse error occurs; example: `""unexpected end of input; expected string literal""` @throw parse_error.102 if to_unicode fails or surrogate error @throw parse_error.103 if to_unicode fails */ template JSON_HEDLEY_WARN_UNUSED_RESULT static basic_json parse(IteratorType first, IteratorType last, const parser_callback_t cb = nullptr, const bool allow_exceptions = true, const bool ignore_comments = false) { basic_json result; parser(detail::input_adapter(std::move(first), std::move(last)), cb, allow_exceptions, ignore_comments).parse(true, result); return result; } JSON_HEDLEY_WARN_UNUSED_RESULT JSON_HEDLEY_DEPRECATED_FOR(3.8.0, parse(ptr, ptr + len)) static basic_json parse(detail::span_input_adapter&& i, const parser_callback_t cb = nullptr, const bool allow_exceptions = true, const bool ignore_comments = false) { basic_json result; parser(i.get(), cb, allow_exceptions, ignore_comments).parse(true, result); return result; } /*! @brief check if the input is valid JSON Unlike the @ref parse(InputType&&, const parser_callback_t,const bool) function, this function neither throws an exception in case of invalid JSON input (i.e., a parse error) nor creates diagnostic information. @tparam InputType A compatible input, for instance - an std::istream object - a FILE pointer - a C-style array of characters - a pointer to a null-terminated string of single byte characters - an object obj for which begin(obj) and end(obj) produces a valid pair of iterators. @param[in] i input to read from @param[in] ignore_comments whether comments should be ignored and treated like whitespace (true) or yield a parse error (true); (optional, false by default) @return Whether the input read from @a i is valid JSON. @complexity Linear in the length of the input. The parser is a predictive LL(1) parser. @note A UTF-8 byte order mark is silently ignored. @liveexample{The example below demonstrates the `accept()` function reading from a string.,accept__string} */ template static bool accept(InputType&& i, const bool ignore_comments = false) { return parser(detail::input_adapter(std::forward(i)), nullptr, false, ignore_comments).accept(true); } template static bool accept(IteratorType first, IteratorType last, const bool ignore_comments = false) { return parser(detail::input_adapter(std::move(first), std::move(last)), nullptr, false, ignore_comments).accept(true); } JSON_HEDLEY_WARN_UNUSED_RESULT JSON_HEDLEY_DEPRECATED_FOR(3.8.0, accept(ptr, ptr + len)) static bool accept(detail::span_input_adapter&& i, const bool ignore_comments = false) { return parser(i.get(), nullptr, false, ignore_comments).accept(true); } /*! @brief generate SAX events The SAX event lister must follow the interface of @ref json_sax. This function reads from a compatible input. Examples are: - an std::istream object - a FILE pointer - a C-style array of characters - a pointer to a null-terminated string of single byte characters - an object obj for which begin(obj) and end(obj) produces a valid pair of iterators. @param[in] i input to read from @param[in,out] sax SAX event listener @param[in] format the format to parse (JSON, CBOR, MessagePack, or UBJSON) @param[in] strict whether the input has to be consumed completely @param[in] ignore_comments whether comments should be ignored and treated like whitespace (true) or yield a parse error (true); (optional, false by default); only applies to the JSON file format. @return return value of the last processed SAX event @throw parse_error.101 if a parse error occurs; example: `""unexpected end of input; expected string literal""` @throw parse_error.102 if to_unicode fails or surrogate error @throw parse_error.103 if to_unicode fails @complexity Linear in the length of the input. The parser is a predictive LL(1) parser. The complexity can be higher if the SAX consumer @a sax has a super-linear complexity. @note A UTF-8 byte order mark is silently ignored. @liveexample{The example below demonstrates the `sax_parse()` function reading from string and processing the events with a user-defined SAX event consumer.,sax_parse} @since version 3.2.0 */ template JSON_HEDLEY_NON_NULL(2) static bool sax_parse(InputType&& i, SAX* sax, input_format_t format = input_format_t::json, const bool strict = true, const bool ignore_comments = false) { auto ia = detail::input_adapter(std::forward(i)); return format == input_format_t::json ? parser(std::move(ia), nullptr, true, ignore_comments).sax_parse(sax, strict) : detail::binary_reader(std::move(ia)).sax_parse(format, sax, strict); } template JSON_HEDLEY_NON_NULL(3) static bool sax_parse(IteratorType first, IteratorType last, SAX* sax, input_format_t format = input_format_t::json, const bool strict = true, const bool ignore_comments = false) { auto ia = detail::input_adapter(std::move(first), std::move(last)); return format == input_format_t::json ? parser(std::move(ia), nullptr, true, ignore_comments).sax_parse(sax, strict) : detail::binary_reader(std::move(ia)).sax_parse(format, sax, strict); } template JSON_HEDLEY_DEPRECATED_FOR(3.8.0, sax_parse(ptr, ptr + len, ...)) JSON_HEDLEY_NON_NULL(2) static bool sax_parse(detail::span_input_adapter&& i, SAX* sax, input_format_t format = input_format_t::json, const bool strict = true, const bool ignore_comments = false) { auto ia = i.get(); return format == input_format_t::json ? parser(std::move(ia), nullptr, true, ignore_comments).sax_parse(sax, strict) : detail::binary_reader(std::move(ia)).sax_parse(format, sax, strict); } /*! @brief deserialize from stream @deprecated This stream operator is deprecated and will be removed in version 4.0.0 of the library. Please use @ref operator>>(std::istream&, basic_json&) instead; that is, replace calls like `j << i;` with `i >> j;`. @since version 1.0.0; deprecated since version 3.0.0 */ JSON_HEDLEY_DEPRECATED_FOR(3.0.0, operator>>(std::istream&, basic_json&)) friend std::istream& operator<<(basic_json& j, std::istream& i) { return operator>>(i, j); } /*! @brief deserialize from stream Deserializes an input stream to a JSON value. @param[in,out] i input stream to read a serialized JSON value from @param[in,out] j JSON value to write the deserialized input to @throw parse_error.101 in case of an unexpected token @throw parse_error.102 if to_unicode fails or surrogate error @throw parse_error.103 if to_unicode fails @complexity Linear in the length of the input. The parser is a predictive LL(1) parser. @note A UTF-8 byte order mark is silently ignored. @liveexample{The example below shows how a JSON value is constructed by reading a serialization from a stream.,operator_deserialize} @sa parse(std::istream&, const parser_callback_t) for a variant with a parser callback function to filter values while parsing @since version 1.0.0 */ friend std::istream& operator>>(std::istream& i, basic_json& j) { parser(detail::input_adapter(i)).parse(false, j); return i; } /// @} /////////////////////////// // convenience functions // /////////////////////////// /*! @brief return the type as string Returns the type name as string to be used in error messages - usually to indicate that a function was called on a wrong JSON type. @return a string representation of a the @a m_type member: Value type | return value ----------- | ------------- null | `"null"` boolean | `"boolean"` string | `"string"` number | `"number"` (for all number types) object | `"object"` array | `"array"` binary | `"binary"` discarded | `"discarded"` @exceptionsafety No-throw guarantee: this function never throws exceptions. @complexity Constant. @liveexample{The following code exemplifies `type_name()` for all JSON types.,type_name} @sa @ref type() -- return the type of the JSON value @sa @ref operator value_t() -- return the type of the JSON value (implicit) @since version 1.0.0, public since 2.1.0, `const char*` and `noexcept` since 3.0.0 */ JSON_HEDLEY_RETURNS_NON_NULL const char* type_name() const noexcept { { switch (m_type) { case value_t::null: return "null"; case value_t::object: return "object"; case value_t::array: return "array"; case value_t::string: return "string"; case value_t::boolean: return "boolean"; case value_t::binary: return "binary"; case value_t::discarded: return "discarded"; default: return "number"; } } } private: ////////////////////// // member variables // ////////////////////// /// the type of the current element value_t m_type = value_t::null; /// the value of the current element json_value m_value = {}; ////////////////////////////////////////// // binary serialization/deserialization // ////////////////////////////////////////// /// @name binary serialization/deserialization support /// @{ public: /*! @brief create a CBOR serialization of a given JSON value Serializes a given JSON value @a j to a byte vector using the CBOR (Concise Binary Object Representation) serialization format. CBOR is a binary serialization format which aims to be more compact than JSON itself, yet more efficient to parse. The library uses the following mapping from JSON values types to CBOR types according to the CBOR specification (RFC 7049): JSON value type | value/range | CBOR type | first byte --------------- | ------------------------------------------ | ---------------------------------- | --------------- null | `null` | Null | 0xF6 boolean | `true` | True | 0xF5 boolean | `false` | False | 0xF4 number_integer | -9223372036854775808..-2147483649 | Negative integer (8 bytes follow) | 0x3B number_integer | -2147483648..-32769 | Negative integer (4 bytes follow) | 0x3A number_integer | -32768..-129 | Negative integer (2 bytes follow) | 0x39 number_integer | -128..-25 | Negative integer (1 byte follow) | 0x38 number_integer | -24..-1 | Negative integer | 0x20..0x37 number_integer | 0..23 | Integer | 0x00..0x17 number_integer | 24..255 | Unsigned integer (1 byte follow) | 0x18 number_integer | 256..65535 | Unsigned integer (2 bytes follow) | 0x19 number_integer | 65536..4294967295 | Unsigned integer (4 bytes follow) | 0x1A number_integer | 4294967296..18446744073709551615 | Unsigned integer (8 bytes follow) | 0x1B number_unsigned | 0..23 | Integer | 0x00..0x17 number_unsigned | 24..255 | Unsigned integer (1 byte follow) | 0x18 number_unsigned | 256..65535 | Unsigned integer (2 bytes follow) | 0x19 number_unsigned | 65536..4294967295 | Unsigned integer (4 bytes follow) | 0x1A number_unsigned | 4294967296..18446744073709551615 | Unsigned integer (8 bytes follow) | 0x1B number_float | *any value representable by a float* | Single-Precision Float | 0xFA number_float | *any value NOT representable by a float* | Double-Precision Float | 0xFB string | *length*: 0..23 | UTF-8 string | 0x60..0x77 string | *length*: 23..255 | UTF-8 string (1 byte follow) | 0x78 string | *length*: 256..65535 | UTF-8 string (2 bytes follow) | 0x79 string | *length*: 65536..4294967295 | UTF-8 string (4 bytes follow) | 0x7A string | *length*: 4294967296..18446744073709551615 | UTF-8 string (8 bytes follow) | 0x7B array | *size*: 0..23 | array | 0x80..0x97 array | *size*: 23..255 | array (1 byte follow) | 0x98 array | *size*: 256..65535 | array (2 bytes follow) | 0x99 array | *size*: 65536..4294967295 | array (4 bytes follow) | 0x9A array | *size*: 4294967296..18446744073709551615 | array (8 bytes follow) | 0x9B object | *size*: 0..23 | map | 0xA0..0xB7 object | *size*: 23..255 | map (1 byte follow) | 0xB8 object | *size*: 256..65535 | map (2 bytes follow) | 0xB9 object | *size*: 65536..4294967295 | map (4 bytes follow) | 0xBA object | *size*: 4294967296..18446744073709551615 | map (8 bytes follow) | 0xBB binary | *size*: 0..23 | byte string | 0x40..0x57 binary | *size*: 23..255 | byte string (1 byte follow) | 0x58 binary | *size*: 256..65535 | byte string (2 bytes follow) | 0x59 binary | *size*: 65536..4294967295 | byte string (4 bytes follow) | 0x5A binary | *size*: 4294967296..18446744073709551615 | byte string (8 bytes follow) | 0x5B @note The mapping is **complete** in the sense that any JSON value type can be converted to a CBOR value. @note If NaN or Infinity are stored inside a JSON number, they are serialized properly. This behavior differs from the @ref dump() function which serializes NaN or Infinity to `null`. @note The following CBOR types are not used in the conversion: - UTF-8 strings terminated by "break" (0x7F) - arrays terminated by "break" (0x9F) - maps terminated by "break" (0xBF) - byte strings terminated by "break" (0x5F) - date/time (0xC0..0xC1) - bignum (0xC2..0xC3) - decimal fraction (0xC4) - bigfloat (0xC5) - expected conversions (0xD5..0xD7) - simple values (0xE0..0xF3, 0xF8) - undefined (0xF7) - half-precision floats (0xF9) - break (0xFF) @param[in] j JSON value to serialize @return CBOR serialization as byte vector @complexity Linear in the size of the JSON value @a j. @liveexample{The example shows the serialization of a JSON value to a byte vector in CBOR format.,to_cbor} @sa http://cbor.io @sa @ref from_cbor(detail::input_adapter&&, const bool, const bool, const cbor_tag_handler_t) for the analogous deserialization @sa @ref to_msgpack(const basic_json&) for the related MessagePack format @sa @ref to_ubjson(const basic_json&, const bool, const bool) for the related UBJSON format @since version 2.0.9; compact representation of floating-point numbers since version 3.8.0 */ static std::vector to_cbor(const basic_json& j) { std::vector result; to_cbor(j, result); return result; } static void to_cbor(const basic_json& j, detail::output_adapter o) { binary_writer(o).write_cbor(j); } static void to_cbor(const basic_json& j, detail::output_adapter o) { binary_writer(o).write_cbor(j); } /*! @brief create a MessagePack serialization of a given JSON value Serializes a given JSON value @a j to a byte vector using the MessagePack serialization format. MessagePack is a binary serialization format which aims to be more compact than JSON itself, yet more efficient to parse. The library uses the following mapping from JSON values types to MessagePack types according to the MessagePack specification: JSON value type | value/range | MessagePack type | first byte --------------- | --------------------------------- | ---------------- | ---------- null | `null` | nil | 0xC0 boolean | `true` | true | 0xC3 boolean | `false` | false | 0xC2 number_integer | -9223372036854775808..-2147483649 | int64 | 0xD3 number_integer | -2147483648..-32769 | int32 | 0xD2 number_integer | -32768..-129 | int16 | 0xD1 number_integer | -128..-33 | int8 | 0xD0 number_integer | -32..-1 | negative fixint | 0xE0..0xFF number_integer | 0..127 | positive fixint | 0x00..0x7F number_integer | 128..255 | uint 8 | 0xCC number_integer | 256..65535 | uint 16 | 0xCD number_integer | 65536..4294967295 | uint 32 | 0xCE number_integer | 4294967296..18446744073709551615 | uint 64 | 0xCF number_unsigned | 0..127 | positive fixint | 0x00..0x7F number_unsigned | 128..255 | uint 8 | 0xCC number_unsigned | 256..65535 | uint 16 | 0xCD number_unsigned | 65536..4294967295 | uint 32 | 0xCE number_unsigned | 4294967296..18446744073709551615 | uint 64 | 0xCF number_float | *any value representable by a float* | float 32 | 0xCA number_float | *any value NOT representable by a float* | float 64 | 0xCB string | *length*: 0..31 | fixstr | 0xA0..0xBF string | *length*: 32..255 | str 8 | 0xD9 string | *length*: 256..65535 | str 16 | 0xDA string | *length*: 65536..4294967295 | str 32 | 0xDB array | *size*: 0..15 | fixarray | 0x90..0x9F array | *size*: 16..65535 | array 16 | 0xDC array | *size*: 65536..4294967295 | array 32 | 0xDD object | *size*: 0..15 | fix map | 0x80..0x8F object | *size*: 16..65535 | map 16 | 0xDE object | *size*: 65536..4294967295 | map 32 | 0xDF binary | *size*: 0..255 | bin 8 | 0xC4 binary | *size*: 256..65535 | bin 16 | 0xC5 binary | *size*: 65536..4294967295 | bin 32 | 0xC6 @note The mapping is **complete** in the sense that any JSON value type can be converted to a MessagePack value. @note The following values can **not** be converted to a MessagePack value: - strings with more than 4294967295 bytes - byte strings with more than 4294967295 bytes - arrays with more than 4294967295 elements - objects with more than 4294967295 elements @note Any MessagePack output created @ref to_msgpack can be successfully parsed by @ref from_msgpack. @note If NaN or Infinity are stored inside a JSON number, they are serialized properly. This behavior differs from the @ref dump() function which serializes NaN or Infinity to `null`. @param[in] j JSON value to serialize @return MessagePack serialization as byte vector @complexity Linear in the size of the JSON value @a j. @liveexample{The example shows the serialization of a JSON value to a byte vector in MessagePack format.,to_msgpack} @sa http://msgpack.org @sa @ref from_msgpack for the analogous deserialization @sa @ref to_cbor(const basic_json& for the related CBOR format @sa @ref to_ubjson(const basic_json&, const bool, const bool) for the related UBJSON format @since version 2.0.9 */ static std::vector to_msgpack(const basic_json& j) { std::vector result; to_msgpack(j, result); return result; } static void to_msgpack(const basic_json& j, detail::output_adapter o) { binary_writer(o).write_msgpack(j); } static void to_msgpack(const basic_json& j, detail::output_adapter o) { binary_writer(o).write_msgpack(j); } /*! @brief create a UBJSON serialization of a given JSON value Serializes a given JSON value @a j to a byte vector using the UBJSON (Universal Binary JSON) serialization format. UBJSON aims to be more compact than JSON itself, yet more efficient to parse. The library uses the following mapping from JSON values types to UBJSON types according to the UBJSON specification: JSON value type | value/range | UBJSON type | marker --------------- | --------------------------------- | ----------- | ------ null | `null` | null | `Z` boolean | `true` | true | `T` boolean | `false` | false | `F` number_integer | -9223372036854775808..-2147483649 | int64 | `L` number_integer | -2147483648..-32769 | int32 | `l` number_integer | -32768..-129 | int16 | `I` number_integer | -128..127 | int8 | `i` number_integer | 128..255 | uint8 | `U` number_integer | 256..32767 | int16 | `I` number_integer | 32768..2147483647 | int32 | `l` number_integer | 2147483648..9223372036854775807 | int64 | `L` number_unsigned | 0..127 | int8 | `i` number_unsigned | 128..255 | uint8 | `U` number_unsigned | 256..32767 | int16 | `I` number_unsigned | 32768..2147483647 | int32 | `l` number_unsigned | 2147483648..9223372036854775807 | int64 | `L` number_unsigned | 2147483649..18446744073709551615 | high-precision | `H` number_float | *any value* | float64 | `D` string | *with shortest length indicator* | string | `S` array | *see notes on optimized format* | array | `[` object | *see notes on optimized format* | map | `{` @note The mapping is **complete** in the sense that any JSON value type can be converted to a UBJSON value. @note The following values can **not** be converted to a UBJSON value: - strings with more than 9223372036854775807 bytes (theoretical) @note The following markers are not used in the conversion: - `Z`: no-op values are not created. - `C`: single-byte strings are serialized with `S` markers. @note Any UBJSON output created @ref to_ubjson can be successfully parsed by @ref from_ubjson. @note If NaN or Infinity are stored inside a JSON number, they are serialized properly. This behavior differs from the @ref dump() function which serializes NaN or Infinity to `null`. @note The optimized formats for containers are supported: Parameter @a use_size adds size information to the beginning of a container and removes the closing marker. Parameter @a use_type further checks whether all elements of a container have the same type and adds the type marker to the beginning of the container. The @a use_type parameter must only be used together with @a use_size = true. Note that @a use_size = true alone may result in larger representations - the benefit of this parameter is that the receiving side is immediately informed on the number of elements of the container. @note If the JSON data contains the binary type, the value stored is a list of integers, as suggested by the UBJSON documentation. In particular, this means that serialization and the deserialization of a JSON containing binary values into UBJSON and back will result in a different JSON object. @param[in] j JSON value to serialize @param[in] use_size whether to add size annotations to container types @param[in] use_type whether to add type annotations to container types (must be combined with @a use_size = true) @return UBJSON serialization as byte vector @complexity Linear in the size of the JSON value @a j. @liveexample{The example shows the serialization of a JSON value to a byte vector in UBJSON format.,to_ubjson} @sa http://ubjson.org @sa @ref from_ubjson(detail::input_adapter&&, const bool, const bool) for the analogous deserialization @sa @ref to_cbor(const basic_json& for the related CBOR format @sa @ref to_msgpack(const basic_json&) for the related MessagePack format @since version 3.1.0 */ static std::vector to_ubjson(const basic_json& j, const bool use_size = false, const bool use_type = false) { std::vector result; to_ubjson(j, result, use_size, use_type); return result; } static void to_ubjson(const basic_json& j, detail::output_adapter o, const bool use_size = false, const bool use_type = false) { binary_writer(o).write_ubjson(j, use_size, use_type); } static void to_ubjson(const basic_json& j, detail::output_adapter o, const bool use_size = false, const bool use_type = false) { binary_writer(o).write_ubjson(j, use_size, use_type); } /*! @brief Serializes the given JSON object `j` to BSON and returns a vector containing the corresponding BSON-representation. BSON (Binary JSON) is a binary format in which zero or more ordered key/value pairs are stored as a single entity (a so-called document). The library uses the following mapping from JSON values types to BSON types: JSON value type | value/range | BSON type | marker --------------- | --------------------------------- | ----------- | ------ null | `null` | null | 0x0A boolean | `true`, `false` | boolean | 0x08 number_integer | -9223372036854775808..-2147483649 | int64 | 0x12 number_integer | -2147483648..2147483647 | int32 | 0x10 number_integer | 2147483648..9223372036854775807 | int64 | 0x12 number_unsigned | 0..2147483647 | int32 | 0x10 number_unsigned | 2147483648..9223372036854775807 | int64 | 0x12 number_unsigned | 9223372036854775808..18446744073709551615| -- | -- number_float | *any value* | double | 0x01 string | *any value* | string | 0x02 array | *any value* | document | 0x04 object | *any value* | document | 0x03 binary | *any value* | binary | 0x05 @warning The mapping is **incomplete**, since only JSON-objects (and things contained therein) can be serialized to BSON. Also, integers larger than 9223372036854775807 cannot be serialized to BSON, and the keys may not contain U+0000, since they are serialized a zero-terminated c-strings. @throw out_of_range.407 if `j.is_number_unsigned() && j.get() > 9223372036854775807` @throw out_of_range.409 if a key in `j` contains a NULL (U+0000) @throw type_error.317 if `!j.is_object()` @pre The input `j` is required to be an object: `j.is_object() == true`. @note Any BSON output created via @ref to_bson can be successfully parsed by @ref from_bson. @param[in] j JSON value to serialize @return BSON serialization as byte vector @complexity Linear in the size of the JSON value @a j. @liveexample{The example shows the serialization of a JSON value to a byte vector in BSON format.,to_bson} @sa http://bsonspec.org/spec.html @sa @ref from_bson(detail::input_adapter&&, const bool strict) for the analogous deserialization @sa @ref to_ubjson(const basic_json&, const bool, const bool) for the related UBJSON format @sa @ref to_cbor(const basic_json&) for the related CBOR format @sa @ref to_msgpack(const basic_json&) for the related MessagePack format */ static std::vector to_bson(const basic_json& j) { std::vector result; to_bson(j, result); return result; } /*! @brief Serializes the given JSON object `j` to BSON and forwards the corresponding BSON-representation to the given output_adapter `o`. @param j The JSON object to convert to BSON. @param o The output adapter that receives the binary BSON representation. @pre The input `j` shall be an object: `j.is_object() == true` @sa @ref to_bson(const basic_json&) */ static void to_bson(const basic_json& j, detail::output_adapter o) { binary_writer(o).write_bson(j); } /*! @copydoc to_bson(const basic_json&, detail::output_adapter) */ static void to_bson(const basic_json& j, detail::output_adapter o) { binary_writer(o).write_bson(j); } /*! @brief create a JSON value from an input in CBOR format Deserializes a given input @a i to a JSON value using the CBOR (Concise Binary Object Representation) serialization format. The library maps CBOR types to JSON value types as follows: CBOR type | JSON value type | first byte ---------------------- | --------------- | ---------- Integer | number_unsigned | 0x00..0x17 Unsigned integer | number_unsigned | 0x18 Unsigned integer | number_unsigned | 0x19 Unsigned integer | number_unsigned | 0x1A Unsigned integer | number_unsigned | 0x1B Negative integer | number_integer | 0x20..0x37 Negative integer | number_integer | 0x38 Negative integer | number_integer | 0x39 Negative integer | number_integer | 0x3A Negative integer | number_integer | 0x3B Byte string | binary | 0x40..0x57 Byte string | binary | 0x58 Byte string | binary | 0x59 Byte string | binary | 0x5A Byte string | binary | 0x5B UTF-8 string | string | 0x60..0x77 UTF-8 string | string | 0x78 UTF-8 string | string | 0x79 UTF-8 string | string | 0x7A UTF-8 string | string | 0x7B UTF-8 string | string | 0x7F array | array | 0x80..0x97 array | array | 0x98 array | array | 0x99 array | array | 0x9A array | array | 0x9B array | array | 0x9F map | object | 0xA0..0xB7 map | object | 0xB8 map | object | 0xB9 map | object | 0xBA map | object | 0xBB map | object | 0xBF False | `false` | 0xF4 True | `true` | 0xF5 Null | `null` | 0xF6 Half-Precision Float | number_float | 0xF9 Single-Precision Float | number_float | 0xFA Double-Precision Float | number_float | 0xFB @warning The mapping is **incomplete** in the sense that not all CBOR types can be converted to a JSON value. The following CBOR types are not supported and will yield parse errors (parse_error.112): - date/time (0xC0..0xC1) - bignum (0xC2..0xC3) - decimal fraction (0xC4) - bigfloat (0xC5) - expected conversions (0xD5..0xD7) - simple values (0xE0..0xF3, 0xF8) - undefined (0xF7) @warning CBOR allows map keys of any type, whereas JSON only allows strings as keys in object values. Therefore, CBOR maps with keys other than UTF-8 strings are rejected (parse_error.113). @note Any CBOR output created @ref to_cbor can be successfully parsed by @ref from_cbor. @param[in] i an input in CBOR format convertible to an input adapter @param[in] strict whether to expect the input to be consumed until EOF (true by default) @param[in] allow_exceptions whether to throw exceptions in case of a parse error (optional, true by default) @param[in] tag_handler how to treat CBOR tags (optional, error by default) @return deserialized JSON value; in case of a parse error and @a allow_exceptions set to `false`, the return value will be value_t::discarded. @throw parse_error.110 if the given input ends prematurely or the end of file was not reached when @a strict was set to true @throw parse_error.112 if unsupported features from CBOR were used in the given input @a v or if the input is not valid CBOR @throw parse_error.113 if a string was expected as map key, but not found @complexity Linear in the size of the input @a i. @liveexample{The example shows the deserialization of a byte vector in CBOR format to a JSON value.,from_cbor} @sa http://cbor.io @sa @ref to_cbor(const basic_json&) for the analogous serialization @sa @ref from_msgpack(detail::input_adapter&&, const bool, const bool) for the related MessagePack format @sa @ref from_ubjson(detail::input_adapter&&, const bool, const bool) for the related UBJSON format @since version 2.0.9; parameter @a start_index since 2.1.1; changed to consume input adapters, removed start_index parameter, and added @a strict parameter since 3.0.0; added @a allow_exceptions parameter since 3.2.0; added @a tag_handler parameter since 3.9.0. */ template JSON_HEDLEY_WARN_UNUSED_RESULT static basic_json from_cbor(InputType&& i, const bool strict = true, const bool allow_exceptions = true, const cbor_tag_handler_t tag_handler = cbor_tag_handler_t::error) { basic_json result; detail::json_sax_dom_parser sdp(result, allow_exceptions); auto ia = detail::input_adapter(std::forward(i)); const bool res = binary_reader(std::move(ia)).sax_parse(input_format_t::cbor, &sdp, strict, tag_handler); return res ? result : basic_json(value_t::discarded); } /*! @copydoc from_cbor(detail::input_adapter&&, const bool, const bool, const cbor_tag_handler_t) */ template JSON_HEDLEY_WARN_UNUSED_RESULT static basic_json from_cbor(IteratorType first, IteratorType last, const bool strict = true, const bool allow_exceptions = true, const cbor_tag_handler_t tag_handler = cbor_tag_handler_t::error) { basic_json result; detail::json_sax_dom_parser sdp(result, allow_exceptions); auto ia = detail::input_adapter(std::move(first), std::move(last)); const bool res = binary_reader(std::move(ia)).sax_parse(input_format_t::cbor, &sdp, strict, tag_handler); return res ? result : basic_json(value_t::discarded); } template JSON_HEDLEY_WARN_UNUSED_RESULT JSON_HEDLEY_DEPRECATED_FOR(3.8.0, from_cbor(ptr, ptr + len)) static basic_json from_cbor(const T* ptr, std::size_t len, const bool strict = true, const bool allow_exceptions = true, const cbor_tag_handler_t tag_handler = cbor_tag_handler_t::error) { return from_cbor(ptr, ptr + len, strict, allow_exceptions, tag_handler); } JSON_HEDLEY_WARN_UNUSED_RESULT JSON_HEDLEY_DEPRECATED_FOR(3.8.0, from_cbor(ptr, ptr + len)) static basic_json from_cbor(detail::span_input_adapter&& i, const bool strict = true, const bool allow_exceptions = true, const cbor_tag_handler_t tag_handler = cbor_tag_handler_t::error) { basic_json result; detail::json_sax_dom_parser sdp(result, allow_exceptions); auto ia = i.get(); const bool res = binary_reader(std::move(ia)).sax_parse(input_format_t::cbor, &sdp, strict, tag_handler); return res ? result : basic_json(value_t::discarded); } /*! @brief create a JSON value from an input in MessagePack format Deserializes a given input @a i to a JSON value using the MessagePack serialization format. The library maps MessagePack types to JSON value types as follows: MessagePack type | JSON value type | first byte ---------------- | --------------- | ---------- positive fixint | number_unsigned | 0x00..0x7F fixmap | object | 0x80..0x8F fixarray | array | 0x90..0x9F fixstr | string | 0xA0..0xBF nil | `null` | 0xC0 false | `false` | 0xC2 true | `true` | 0xC3 float 32 | number_float | 0xCA float 64 | number_float | 0xCB uint 8 | number_unsigned | 0xCC uint 16 | number_unsigned | 0xCD uint 32 | number_unsigned | 0xCE uint 64 | number_unsigned | 0xCF int 8 | number_integer | 0xD0 int 16 | number_integer | 0xD1 int 32 | number_integer | 0xD2 int 64 | number_integer | 0xD3 str 8 | string | 0xD9 str 16 | string | 0xDA str 32 | string | 0xDB array 16 | array | 0xDC array 32 | array | 0xDD map 16 | object | 0xDE map 32 | object | 0xDF bin 8 | binary | 0xC4 bin 16 | binary | 0xC5 bin 32 | binary | 0xC6 ext 8 | binary | 0xC7 ext 16 | binary | 0xC8 ext 32 | binary | 0xC9 fixext 1 | binary | 0xD4 fixext 2 | binary | 0xD5 fixext 4 | binary | 0xD6 fixext 8 | binary | 0xD7 fixext 16 | binary | 0xD8 negative fixint | number_integer | 0xE0-0xFF @note Any MessagePack output created @ref to_msgpack can be successfully parsed by @ref from_msgpack. @param[in] i an input in MessagePack format convertible to an input adapter @param[in] strict whether to expect the input to be consumed until EOF (true by default) @param[in] allow_exceptions whether to throw exceptions in case of a parse error (optional, true by default) @return deserialized JSON value; in case of a parse error and @a allow_exceptions set to `false`, the return value will be value_t::discarded. @throw parse_error.110 if the given input ends prematurely or the end of file was not reached when @a strict was set to true @throw parse_error.112 if unsupported features from MessagePack were used in the given input @a i or if the input is not valid MessagePack @throw parse_error.113 if a string was expected as map key, but not found @complexity Linear in the size of the input @a i. @liveexample{The example shows the deserialization of a byte vector in MessagePack format to a JSON value.,from_msgpack} @sa http://msgpack.org @sa @ref to_msgpack(const basic_json&) for the analogous serialization @sa @ref from_cbor(detail::input_adapter&&, const bool, const bool, const cbor_tag_handler_t) for the related CBOR format @sa @ref from_ubjson(detail::input_adapter&&, const bool, const bool) for the related UBJSON format @sa @ref from_bson(detail::input_adapter&&, const bool, const bool) for the related BSON format @since version 2.0.9; parameter @a start_index since 2.1.1; changed to consume input adapters, removed start_index parameter, and added @a strict parameter since 3.0.0; added @a allow_exceptions parameter since 3.2.0 */ template JSON_HEDLEY_WARN_UNUSED_RESULT static basic_json from_msgpack(InputType&& i, const bool strict = true, const bool allow_exceptions = true) { basic_json result; detail::json_sax_dom_parser sdp(result, allow_exceptions); auto ia = detail::input_adapter(std::forward(i)); const bool res = binary_reader(std::move(ia)).sax_parse(input_format_t::msgpack, &sdp, strict); return res ? result : basic_json(value_t::discarded); } /*! @copydoc from_msgpack(detail::input_adapter&&, const bool, const bool) */ template JSON_HEDLEY_WARN_UNUSED_RESULT static basic_json from_msgpack(IteratorType first, IteratorType last, const bool strict = true, const bool allow_exceptions = true) { basic_json result; detail::json_sax_dom_parser sdp(result, allow_exceptions); auto ia = detail::input_adapter(std::move(first), std::move(last)); const bool res = binary_reader(std::move(ia)).sax_parse(input_format_t::msgpack, &sdp, strict); return res ? result : basic_json(value_t::discarded); } template JSON_HEDLEY_WARN_UNUSED_RESULT JSON_HEDLEY_DEPRECATED_FOR(3.8.0, from_msgpack(ptr, ptr + len)) static basic_json from_msgpack(const T* ptr, std::size_t len, const bool strict = true, const bool allow_exceptions = true) { return from_msgpack(ptr, ptr + len, strict, allow_exceptions); } JSON_HEDLEY_WARN_UNUSED_RESULT JSON_HEDLEY_DEPRECATED_FOR(3.8.0, from_msgpack(ptr, ptr + len)) static basic_json from_msgpack(detail::span_input_adapter&& i, const bool strict = true, const bool allow_exceptions = true) { basic_json result; detail::json_sax_dom_parser sdp(result, allow_exceptions); auto ia = i.get(); const bool res = binary_reader(std::move(ia)).sax_parse(input_format_t::msgpack, &sdp, strict); return res ? result : basic_json(value_t::discarded); } /*! @brief create a JSON value from an input in UBJSON format Deserializes a given input @a i to a JSON value using the UBJSON (Universal Binary JSON) serialization format. The library maps UBJSON types to JSON value types as follows: UBJSON type | JSON value type | marker ----------- | --------------------------------------- | ------ no-op | *no value, next value is read* | `N` null | `null` | `Z` false | `false` | `F` true | `true` | `T` float32 | number_float | `d` float64 | number_float | `D` uint8 | number_unsigned | `U` int8 | number_integer | `i` int16 | number_integer | `I` int32 | number_integer | `l` int64 | number_integer | `L` high-precision number | number_integer, number_unsigned, or number_float - depends on number string | 'H' string | string | `S` char | string | `C` array | array (optimized values are supported) | `[` object | object (optimized values are supported) | `{` @note The mapping is **complete** in the sense that any UBJSON value can be converted to a JSON value. @param[in] i an input in UBJSON format convertible to an input adapter @param[in] strict whether to expect the input to be consumed until EOF (true by default) @param[in] allow_exceptions whether to throw exceptions in case of a parse error (optional, true by default) @return deserialized JSON value; in case of a parse error and @a allow_exceptions set to `false`, the return value will be value_t::discarded. @throw parse_error.110 if the given input ends prematurely or the end of file was not reached when @a strict was set to true @throw parse_error.112 if a parse error occurs @throw parse_error.113 if a string could not be parsed successfully @complexity Linear in the size of the input @a i. @liveexample{The example shows the deserialization of a byte vector in UBJSON format to a JSON value.,from_ubjson} @sa http://ubjson.org @sa @ref to_ubjson(const basic_json&, const bool, const bool) for the analogous serialization @sa @ref from_cbor(detail::input_adapter&&, const bool, const bool, const cbor_tag_handler_t) for the related CBOR format @sa @ref from_msgpack(detail::input_adapter&&, const bool, const bool) for the related MessagePack format @sa @ref from_bson(detail::input_adapter&&, const bool, const bool) for the related BSON format @since version 3.1.0; added @a allow_exceptions parameter since 3.2.0 */ template JSON_HEDLEY_WARN_UNUSED_RESULT static basic_json from_ubjson(InputType&& i, const bool strict = true, const bool allow_exceptions = true) { basic_json result; detail::json_sax_dom_parser sdp(result, allow_exceptions); auto ia = detail::input_adapter(std::forward(i)); const bool res = binary_reader(std::move(ia)).sax_parse(input_format_t::ubjson, &sdp, strict); return res ? result : basic_json(value_t::discarded); } /*! @copydoc from_ubjson(detail::input_adapter&&, const bool, const bool) */ template JSON_HEDLEY_WARN_UNUSED_RESULT static basic_json from_ubjson(IteratorType first, IteratorType last, const bool strict = true, const bool allow_exceptions = true) { basic_json result; detail::json_sax_dom_parser sdp(result, allow_exceptions); auto ia = detail::input_adapter(std::move(first), std::move(last)); const bool res = binary_reader(std::move(ia)).sax_parse(input_format_t::ubjson, &sdp, strict); return res ? result : basic_json(value_t::discarded); } template JSON_HEDLEY_WARN_UNUSED_RESULT JSON_HEDLEY_DEPRECATED_FOR(3.8.0, from_ubjson(ptr, ptr + len)) static basic_json from_ubjson(const T* ptr, std::size_t len, const bool strict = true, const bool allow_exceptions = true) { return from_ubjson(ptr, ptr + len, strict, allow_exceptions); } JSON_HEDLEY_WARN_UNUSED_RESULT JSON_HEDLEY_DEPRECATED_FOR(3.8.0, from_ubjson(ptr, ptr + len)) static basic_json from_ubjson(detail::span_input_adapter&& i, const bool strict = true, const bool allow_exceptions = true) { basic_json result; detail::json_sax_dom_parser sdp(result, allow_exceptions); auto ia = i.get(); const bool res = binary_reader(std::move(ia)).sax_parse(input_format_t::ubjson, &sdp, strict); return res ? result : basic_json(value_t::discarded); } /*! @brief Create a JSON value from an input in BSON format Deserializes a given input @a i to a JSON value using the BSON (Binary JSON) serialization format. The library maps BSON record types to JSON value types as follows: BSON type | BSON marker byte | JSON value type --------------- | ---------------- | --------------------------- double | 0x01 | number_float string | 0x02 | string document | 0x03 | object array | 0x04 | array binary | 0x05 | still unsupported undefined | 0x06 | still unsupported ObjectId | 0x07 | still unsupported boolean | 0x08 | boolean UTC Date-Time | 0x09 | still unsupported null | 0x0A | null Regular Expr. | 0x0B | still unsupported DB Pointer | 0x0C | still unsupported JavaScript Code | 0x0D | still unsupported Symbol | 0x0E | still unsupported JavaScript Code | 0x0F | still unsupported int32 | 0x10 | number_integer Timestamp | 0x11 | still unsupported 128-bit decimal float | 0x13 | still unsupported Max Key | 0x7F | still unsupported Min Key | 0xFF | still unsupported @warning The mapping is **incomplete**. The unsupported mappings are indicated in the table above. @param[in] i an input in BSON format convertible to an input adapter @param[in] strict whether to expect the input to be consumed until EOF (true by default) @param[in] allow_exceptions whether to throw exceptions in case of a parse error (optional, true by default) @return deserialized JSON value; in case of a parse error and @a allow_exceptions set to `false`, the return value will be value_t::discarded. @throw parse_error.114 if an unsupported BSON record type is encountered @complexity Linear in the size of the input @a i. @liveexample{The example shows the deserialization of a byte vector in BSON format to a JSON value.,from_bson} @sa http://bsonspec.org/spec.html @sa @ref to_bson(const basic_json&) for the analogous serialization @sa @ref from_cbor(detail::input_adapter&&, const bool, const bool, const cbor_tag_handler_t) for the related CBOR format @sa @ref from_msgpack(detail::input_adapter&&, const bool, const bool) for the related MessagePack format @sa @ref from_ubjson(detail::input_adapter&&, const bool, const bool) for the related UBJSON format */ template JSON_HEDLEY_WARN_UNUSED_RESULT static basic_json from_bson(InputType&& i, const bool strict = true, const bool allow_exceptions = true) { basic_json result; detail::json_sax_dom_parser sdp(result, allow_exceptions); auto ia = detail::input_adapter(std::forward(i)); const bool res = binary_reader(std::move(ia)).sax_parse(input_format_t::bson, &sdp, strict); return res ? result : basic_json(value_t::discarded); } /*! @copydoc from_bson(detail::input_adapter&&, const bool, const bool) */ template JSON_HEDLEY_WARN_UNUSED_RESULT static basic_json from_bson(IteratorType first, IteratorType last, const bool strict = true, const bool allow_exceptions = true) { basic_json result; detail::json_sax_dom_parser sdp(result, allow_exceptions); auto ia = detail::input_adapter(std::move(first), std::move(last)); const bool res = binary_reader(std::move(ia)).sax_parse(input_format_t::bson, &sdp, strict); return res ? result : basic_json(value_t::discarded); } template JSON_HEDLEY_WARN_UNUSED_RESULT JSON_HEDLEY_DEPRECATED_FOR(3.8.0, from_bson(ptr, ptr + len)) static basic_json from_bson(const T* ptr, std::size_t len, const bool strict = true, const bool allow_exceptions = true) { return from_bson(ptr, ptr + len, strict, allow_exceptions); } JSON_HEDLEY_WARN_UNUSED_RESULT JSON_HEDLEY_DEPRECATED_FOR(3.8.0, from_bson(ptr, ptr + len)) static basic_json from_bson(detail::span_input_adapter&& i, const bool strict = true, const bool allow_exceptions = true) { basic_json result; detail::json_sax_dom_parser sdp(result, allow_exceptions); auto ia = i.get(); const bool res = binary_reader(std::move(ia)).sax_parse(input_format_t::bson, &sdp, strict); return res ? result : basic_json(value_t::discarded); } /// @} ////////////////////////// // JSON Pointer support // ////////////////////////// /// @name JSON Pointer functions /// @{ /*! @brief access specified element via JSON Pointer Uses a JSON pointer to retrieve a reference to the respective JSON value. No bound checking is performed. Similar to @ref operator[](const typename object_t::key_type&), `null` values are created in arrays and objects if necessary. In particular: - If the JSON pointer points to an object key that does not exist, it is created an filled with a `null` value before a reference to it is returned. - If the JSON pointer points to an array index that does not exist, it is created an filled with a `null` value before a reference to it is returned. All indices between the current maximum and the given index are also filled with `null`. - The special value `-` is treated as a synonym for the index past the end. @param[in] ptr a JSON pointer @return reference to the element pointed to by @a ptr @complexity Constant. @throw parse_error.106 if an array index begins with '0' @throw parse_error.109 if an array index was not a number @throw out_of_range.404 if the JSON pointer can not be resolved @liveexample{The behavior is shown in the example.,operatorjson_pointer} @since version 2.0.0 */ reference operator[](const json_pointer& ptr) { return ptr.get_unchecked(this); } /*! @brief access specified element via JSON Pointer Uses a JSON pointer to retrieve a reference to the respective JSON value. No bound checking is performed. The function does not change the JSON value; no `null` values are created. In particular, the special value `-` yields an exception. @param[in] ptr JSON pointer to the desired element @return const reference to the element pointed to by @a ptr @complexity Constant. @throw parse_error.106 if an array index begins with '0' @throw parse_error.109 if an array index was not a number @throw out_of_range.402 if the array index '-' is used @throw out_of_range.404 if the JSON pointer can not be resolved @liveexample{The behavior is shown in the example.,operatorjson_pointer_const} @since version 2.0.0 */ const_reference operator[](const json_pointer& ptr) const { return ptr.get_unchecked(this); } /*! @brief access specified element via JSON Pointer Returns a reference to the element at with specified JSON pointer @a ptr, with bounds checking. @param[in] ptr JSON pointer to the desired element @return reference to the element pointed to by @a ptr @throw parse_error.106 if an array index in the passed JSON pointer @a ptr begins with '0'. See example below. @throw parse_error.109 if an array index in the passed JSON pointer @a ptr is not a number. See example below. @throw out_of_range.401 if an array index in the passed JSON pointer @a ptr is out of range. See example below. @throw out_of_range.402 if the array index '-' is used in the passed JSON pointer @a ptr. As `at` provides checked access (and no elements are implicitly inserted), the index '-' is always invalid. See example below. @throw out_of_range.403 if the JSON pointer describes a key of an object which cannot be found. See example below. @throw out_of_range.404 if the JSON pointer @a ptr can not be resolved. See example below. @exceptionsafety Strong guarantee: if an exception is thrown, there are no changes in the JSON value. @complexity Constant. @since version 2.0.0 @liveexample{The behavior is shown in the example.,at_json_pointer} */ reference at(const json_pointer& ptr) { return ptr.get_checked(this); } /*! @brief access specified element via JSON Pointer Returns a const reference to the element at with specified JSON pointer @a ptr, with bounds checking. @param[in] ptr JSON pointer to the desired element @return reference to the element pointed to by @a ptr @throw parse_error.106 if an array index in the passed JSON pointer @a ptr begins with '0'. See example below. @throw parse_error.109 if an array index in the passed JSON pointer @a ptr is not a number. See example below. @throw out_of_range.401 if an array index in the passed JSON pointer @a ptr is out of range. See example below. @throw out_of_range.402 if the array index '-' is used in the passed JSON pointer @a ptr. As `at` provides checked access (and no elements are implicitly inserted), the index '-' is always invalid. See example below. @throw out_of_range.403 if the JSON pointer describes a key of an object which cannot be found. See example below. @throw out_of_range.404 if the JSON pointer @a ptr can not be resolved. See example below. @exceptionsafety Strong guarantee: if an exception is thrown, there are no changes in the JSON value. @complexity Constant. @since version 2.0.0 @liveexample{The behavior is shown in the example.,at_json_pointer_const} */ const_reference at(const json_pointer& ptr) const { return ptr.get_checked(this); } /*! @brief return flattened JSON value The function creates a JSON object whose keys are JSON pointers (see [RFC 6901](https://tools.ietf.org/html/rfc6901)) and whose values are all primitive. The original JSON value can be restored using the @ref unflatten() function. @return an object that maps JSON pointers to primitive values @note Empty objects and arrays are flattened to `null` and will not be reconstructed correctly by the @ref unflatten() function. @complexity Linear in the size the JSON value. @liveexample{The following code shows how a JSON object is flattened to an object whose keys consist of JSON pointers.,flatten} @sa @ref unflatten() for the reverse function @since version 2.0.0 */ basic_json flatten() const { basic_json result(value_t::object); json_pointer::flatten("", *this, result); return result; } /*! @brief unflatten a previously flattened JSON value The function restores the arbitrary nesting of a JSON value that has been flattened before using the @ref flatten() function. The JSON value must meet certain constraints: 1. The value must be an object. 2. The keys must be JSON pointers (see [RFC 6901](https://tools.ietf.org/html/rfc6901)) 3. The mapped values must be primitive JSON types. @return the original JSON from a flattened version @note Empty objects and arrays are flattened by @ref flatten() to `null` values and can not unflattened to their original type. Apart from this example, for a JSON value `j`, the following is always true: `j == j.flatten().unflatten()`. @complexity Linear in the size the JSON value. @throw type_error.314 if value is not an object @throw type_error.315 if object values are not primitive @liveexample{The following code shows how a flattened JSON object is unflattened into the original nested JSON object.,unflatten} @sa @ref flatten() for the reverse function @since version 2.0.0 */ basic_json unflatten() const { return json_pointer::unflatten(*this); } /// @} ////////////////////////// // JSON Patch functions // ////////////////////////// /// @name JSON Patch functions /// @{ /*! @brief applies a JSON patch [JSON Patch](http://jsonpatch.com) defines a JSON document structure for expressing a sequence of operations to apply to a JSON) document. With this function, a JSON Patch is applied to the current JSON value by executing all operations from the patch. @param[in] json_patch JSON patch document @return patched document @note The application of a patch is atomic: Either all operations succeed and the patched document is returned or an exception is thrown. In any case, the original value is not changed: the patch is applied to a copy of the value. @throw parse_error.104 if the JSON patch does not consist of an array of objects @throw parse_error.105 if the JSON patch is malformed (e.g., mandatory attributes are missing); example: `"operation add must have member path"` @throw out_of_range.401 if an array index is out of range. @throw out_of_range.403 if a JSON pointer inside the patch could not be resolved successfully in the current JSON value; example: `"key baz not found"` @throw out_of_range.405 if JSON pointer has no parent ("add", "remove", "move") @throw other_error.501 if "test" operation was unsuccessful @complexity Linear in the size of the JSON value and the length of the JSON patch. As usually only a fraction of the JSON value is affected by the patch, the complexity can usually be neglected. @liveexample{The following code shows how a JSON patch is applied to a value.,patch} @sa @ref diff -- create a JSON patch by comparing two JSON values @sa [RFC 6902 (JSON Patch)](https://tools.ietf.org/html/rfc6902) @sa [RFC 6901 (JSON Pointer)](https://tools.ietf.org/html/rfc6901) @since version 2.0.0 */ basic_json patch(const basic_json& json_patch) const { // make a working copy to apply the patch to basic_json result = *this; // the valid JSON Patch operations enum class patch_operations {add, remove, replace, move, copy, test, invalid}; const auto get_op = [](const std::string & op) { if (op == "add") { return patch_operations::add; } if (op == "remove") { return patch_operations::remove; } if (op == "replace") { return patch_operations::replace; } if (op == "move") { return patch_operations::move; } if (op == "copy") { return patch_operations::copy; } if (op == "test") { return patch_operations::test; } return patch_operations::invalid; }; // wrapper for "add" operation; add value at ptr const auto operation_add = [&result](json_pointer & ptr, basic_json val) { // adding to the root of the target document means replacing it if (ptr.empty()) { result = val; return; } // make sure the top element of the pointer exists json_pointer top_pointer = ptr.top(); if (top_pointer != ptr) { result.at(top_pointer); } // get reference to parent of JSON pointer ptr const auto last_path = ptr.back(); ptr.pop_back(); basic_json& parent = result[ptr]; switch (parent.m_type) { case value_t::null: case value_t::object: { // use operator[] to add value parent[last_path] = val; break; } case value_t::array: { if (last_path == "-") { // special case: append to back parent.push_back(val); } else { const auto idx = json_pointer::array_index(last_path); if (JSON_HEDLEY_UNLIKELY(idx > parent.size())) { // avoid undefined behavior JSON_THROW(out_of_range::create(401, "array index " + std::to_string(idx) + " is out of range")); } // default case: insert add offset parent.insert(parent.begin() + static_cast(idx), val); } break; } // if there exists a parent it cannot be primitive default: // LCOV_EXCL_LINE JSON_ASSERT(false); // LCOV_EXCL_LINE } }; // wrapper for "remove" operation; remove value at ptr const auto operation_remove = [&result](json_pointer & ptr) { // get reference to parent of JSON pointer ptr const auto last_path = ptr.back(); ptr.pop_back(); basic_json& parent = result.at(ptr); // remove child if (parent.is_object()) { // perform range check auto it = parent.find(last_path); if (JSON_HEDLEY_LIKELY(it != parent.end())) { parent.erase(it); } else { JSON_THROW(out_of_range::create(403, "key '" + last_path + "' not found")); } } else if (parent.is_array()) { // note erase performs range check parent.erase(json_pointer::array_index(last_path)); } }; // type check: top level value must be an array if (JSON_HEDLEY_UNLIKELY(!json_patch.is_array())) { JSON_THROW(parse_error::create(104, 0, "JSON patch must be an array of objects")); } // iterate and apply the operations for (const auto& val : json_patch) { // wrapper to get a value for an operation const auto get_value = [&val](const std::string & op, const std::string & member, bool string_type) -> basic_json & { // find value auto it = val.m_value.object->find(member); // context-sensitive error message const auto error_msg = (op == "op") ? "operation" : "operation '" + op + "'"; // check if desired value is present if (JSON_HEDLEY_UNLIKELY(it == val.m_value.object->end())) { JSON_THROW(parse_error::create(105, 0, error_msg + " must have member '" + member + "'")); } // check if result is of type string if (JSON_HEDLEY_UNLIKELY(string_type && !it->second.is_string())) { JSON_THROW(parse_error::create(105, 0, error_msg + " must have string member '" + member + "'")); } // no error: return value return it->second; }; // type check: every element of the array must be an object if (JSON_HEDLEY_UNLIKELY(!val.is_object())) { JSON_THROW(parse_error::create(104, 0, "JSON patch must be an array of objects")); } // collect mandatory members const auto op = get_value("op", "op", true).template get(); const auto path = get_value(op, "path", true).template get(); json_pointer ptr(path); switch (get_op(op)) { case patch_operations::add: { operation_add(ptr, get_value("add", "value", false)); break; } case patch_operations::remove: { operation_remove(ptr); break; } case patch_operations::replace: { // the "path" location must exist - use at() result.at(ptr) = get_value("replace", "value", false); break; } case patch_operations::move: { const auto from_path = get_value("move", "from", true).template get(); json_pointer from_ptr(from_path); // the "from" location must exist - use at() basic_json v = result.at(from_ptr); // The move operation is functionally identical to a // "remove" operation on the "from" location, followed // immediately by an "add" operation at the target // location with the value that was just removed. operation_remove(from_ptr); operation_add(ptr, v); break; } case patch_operations::copy: { const auto from_path = get_value("copy", "from", true).template get(); const json_pointer from_ptr(from_path); // the "from" location must exist - use at() basic_json v = result.at(from_ptr); // The copy is functionally identical to an "add" // operation at the target location using the value // specified in the "from" member. operation_add(ptr, v); break; } case patch_operations::test: { bool success = false; JSON_TRY { // check if "value" matches the one at "path" // the "path" location must exist - use at() success = (result.at(ptr) == get_value("test", "value", false)); } JSON_INTERNAL_CATCH (out_of_range&) { // ignore out of range errors: success remains false } // throw an exception if test fails if (JSON_HEDLEY_UNLIKELY(!success)) { JSON_THROW(other_error::create(501, "unsuccessful: " + val.dump())); } break; } default: { // op must be "add", "remove", "replace", "move", "copy", or // "test" JSON_THROW(parse_error::create(105, 0, "operation value '" + op + "' is invalid")); } } } return result; } /*! @brief creates a diff as a JSON patch Creates a [JSON Patch](http://jsonpatch.com) so that value @a source can be changed into the value @a target by calling @ref patch function. @invariant For two JSON values @a source and @a target, the following code yields always `true`: @code {.cpp} source.patch(diff(source, target)) == target; @endcode @note Currently, only `remove`, `add`, and `replace` operations are generated. @param[in] source JSON value to compare from @param[in] target JSON value to compare against @param[in] path helper value to create JSON pointers @return a JSON patch to convert the @a source to @a target @complexity Linear in the lengths of @a source and @a target. @liveexample{The following code shows how a JSON patch is created as a diff for two JSON values.,diff} @sa @ref patch -- apply a JSON patch @sa @ref merge_patch -- apply a JSON Merge Patch @sa [RFC 6902 (JSON Patch)](https://tools.ietf.org/html/rfc6902) @since version 2.0.0 */ JSON_HEDLEY_WARN_UNUSED_RESULT static basic_json diff(const basic_json& source, const basic_json& target, const std::string& path = "") { // the patch basic_json result(value_t::array); // if the values are the same, return empty patch if (source == target) { return result; } if (source.type() != target.type()) { // different types: replace value result.push_back( { {"op", "replace"}, {"path", path}, {"value", target} }); return result; } switch (source.type()) { case value_t::array: { // first pass: traverse common elements std::size_t i = 0; while (i < source.size() && i < target.size()) { // recursive call to compare array values at index i auto temp_diff = diff(source[i], target[i], path + "/" + std::to_string(i)); result.insert(result.end(), temp_diff.begin(), temp_diff.end()); ++i; } // i now reached the end of at least one array // in a second pass, traverse the remaining elements // remove my remaining elements const auto end_index = static_cast(result.size()); while (i < source.size()) { // add operations in reverse order to avoid invalid // indices result.insert(result.begin() + end_index, object( { {"op", "remove"}, {"path", path + "/" + std::to_string(i)} })); ++i; } // add other remaining elements while (i < target.size()) { result.push_back( { {"op", "add"}, {"path", path + "/-"}, {"value", target[i]} }); ++i; } break; } case value_t::object: { // first pass: traverse this object's elements for (auto it = source.cbegin(); it != source.cend(); ++it) { // escape the key name to be used in a JSON patch const auto key = json_pointer::escape(it.key()); if (target.find(it.key()) != target.end()) { // recursive call to compare object values at key it auto temp_diff = diff(it.value(), target[it.key()], path + "/" + key); result.insert(result.end(), temp_diff.begin(), temp_diff.end()); } else { // found a key that is not in o -> remove it result.push_back(object( { {"op", "remove"}, {"path", path + "/" + key} })); } } // second pass: traverse other object's elements for (auto it = target.cbegin(); it != target.cend(); ++it) { if (source.find(it.key()) == source.end()) { // found a key that is not in this -> add it const auto key = json_pointer::escape(it.key()); result.push_back( { {"op", "add"}, {"path", path + "/" + key}, {"value", it.value()} }); } } break; } default: { // both primitive type: replace value result.push_back( { {"op", "replace"}, {"path", path}, {"value", target} }); break; } } return result; } /// @} //////////////////////////////// // JSON Merge Patch functions // //////////////////////////////// /// @name JSON Merge Patch functions /// @{ /*! @brief applies a JSON Merge Patch The merge patch format is primarily intended for use with the HTTP PATCH method as a means of describing a set of modifications to a target resource's content. This function applies a merge patch to the current JSON value. The function implements the following algorithm from Section 2 of [RFC 7396 (JSON Merge Patch)](https://tools.ietf.org/html/rfc7396): ``` define MergePatch(Target, Patch): if Patch is an Object: if Target is not an Object: Target = {} // Ignore the contents and set it to an empty Object for each Name/Value pair in Patch: if Value is null: if Name exists in Target: remove the Name/Value pair from Target else: Target[Name] = MergePatch(Target[Name], Value) return Target else: return Patch ``` Thereby, `Target` is the current object; that is, the patch is applied to the current value. @param[in] apply_patch the patch to apply @complexity Linear in the lengths of @a patch. @liveexample{The following code shows how a JSON Merge Patch is applied to a JSON document.,merge_patch} @sa @ref patch -- apply a JSON patch @sa [RFC 7396 (JSON Merge Patch)](https://tools.ietf.org/html/rfc7396) @since version 3.0.0 */ void merge_patch(const basic_json& apply_patch) { if (apply_patch.is_object()) { if (!is_object()) { *this = object(); } for (auto it = apply_patch.begin(); it != apply_patch.end(); ++it) { if (it.value().is_null()) { erase(it.key()); } else { operator[](it.key()).merge_patch(it.value()); } } } else { *this = apply_patch; } } /// @} }; /*! @brief user-defined to_string function for JSON values This function implements a user-defined to_string for JSON objects. @param[in] j a JSON object @return a std::string object */ NLOHMANN_BASIC_JSON_TPL_DECLARATION std::string to_string(const NLOHMANN_BASIC_JSON_TPL& j) { return j.dump(); } } // namespace nlohmann /////////////////////// // nonmember support // /////////////////////// // specialization of std::swap, and std::hash namespace std { /// hash value for JSON objects template<> struct hash { /*! @brief return a hash value for a JSON object @since version 1.0.0 */ std::size_t operator()(const nlohmann::json& j) const { return nlohmann::detail::hash(j); } }; /// specialization for std::less /// @note: do not remove the space after '<', /// see https://github.com/nlohmann/json/pull/679 template<> struct less<::nlohmann::detail::value_t> { /*! @brief compare two value_t enum values @since version 3.0.0 */ bool operator()(nlohmann::detail::value_t lhs, nlohmann::detail::value_t rhs) const noexcept { return nlohmann::detail::operator<(lhs, rhs); } }; // C++20 prohibit function specialization in the std namespace. #ifndef JSON_HAS_CPP_20 /*! @brief exchanges the values of two JSON objects @since version 1.0.0 */ template<> inline void swap(nlohmann::json& j1, nlohmann::json& j2) noexcept( is_nothrow_move_constructible::value&& is_nothrow_move_assignable::value ) { j1.swap(j2); } #endif } // namespace std /*! @brief user-defined string literal for JSON values This operator implements a user-defined string literal for JSON objects. It can be used by adding `"_json"` to a string literal and returns a JSON object if no parse error occurred. @param[in] s a string representation of a JSON object @param[in] n the length of string @a s @return a JSON object @since version 1.0.0 */ JSON_HEDLEY_NON_NULL(1) inline nlohmann::json operator "" _json(const char* s, std::size_t n) { return nlohmann::json::parse(s, s + n); } /*! @brief user-defined string literal for JSON pointer This operator implements a user-defined string literal for JSON Pointers. It can be used by adding `"_json_pointer"` to a string literal and returns a JSON pointer object if no parse error occurred. @param[in] s a string representation of a JSON Pointer @param[in] n the length of string @a s @return a JSON pointer object @since version 2.0.0 */ JSON_HEDLEY_NON_NULL(1) inline nlohmann::json::json_pointer operator "" _json_pointer(const char* s, std::size_t n) { return nlohmann::json::json_pointer(std::string(s, n)); } // #include // restore GCC/clang diagnostic settings #if defined(__clang__) || defined(__GNUC__) || defined(__GNUG__) #pragma GCC diagnostic pop #endif #if defined(__clang__) #pragma GCC diagnostic pop #endif // clean up #undef JSON_ASSERT #undef JSON_INTERNAL_CATCH #undef JSON_CATCH #undef JSON_THROW #undef JSON_TRY #undef JSON_HAS_CPP_14 #undef JSON_HAS_CPP_17 #undef NLOHMANN_BASIC_JSON_TPL_DECLARATION #undef NLOHMANN_BASIC_JSON_TPL #undef JSON_EXPLICIT // #include #undef JSON_HEDLEY_ALWAYS_INLINE #undef JSON_HEDLEY_ARM_VERSION #undef JSON_HEDLEY_ARM_VERSION_CHECK #undef JSON_HEDLEY_ARRAY_PARAM #undef JSON_HEDLEY_ASSUME #undef JSON_HEDLEY_BEGIN_C_DECLS #undef JSON_HEDLEY_CLANG_HAS_ATTRIBUTE #undef JSON_HEDLEY_CLANG_HAS_BUILTIN #undef JSON_HEDLEY_CLANG_HAS_CPP_ATTRIBUTE #undef JSON_HEDLEY_CLANG_HAS_DECLSPEC_DECLSPEC_ATTRIBUTE #undef JSON_HEDLEY_CLANG_HAS_EXTENSION #undef JSON_HEDLEY_CLANG_HAS_FEATURE #undef JSON_HEDLEY_CLANG_HAS_WARNING #undef JSON_HEDLEY_COMPCERT_VERSION #undef JSON_HEDLEY_COMPCERT_VERSION_CHECK #undef JSON_HEDLEY_CONCAT #undef JSON_HEDLEY_CONCAT3 #undef JSON_HEDLEY_CONCAT3_EX #undef JSON_HEDLEY_CONCAT_EX #undef JSON_HEDLEY_CONST #undef JSON_HEDLEY_CONSTEXPR #undef JSON_HEDLEY_CONST_CAST #undef JSON_HEDLEY_CPP_CAST #undef JSON_HEDLEY_CRAY_VERSION #undef JSON_HEDLEY_CRAY_VERSION_CHECK #undef JSON_HEDLEY_C_DECL #undef JSON_HEDLEY_DEPRECATED #undef JSON_HEDLEY_DEPRECATED_FOR #undef JSON_HEDLEY_DIAGNOSTIC_DISABLE_CAST_QUAL #undef JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_ #undef JSON_HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED #undef JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_CPP_ATTRIBUTES #undef JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_PRAGMAS #undef JSON_HEDLEY_DIAGNOSTIC_POP #undef JSON_HEDLEY_DIAGNOSTIC_PUSH #undef JSON_HEDLEY_DMC_VERSION #undef JSON_HEDLEY_DMC_VERSION_CHECK #undef JSON_HEDLEY_EMPTY_BASES #undef JSON_HEDLEY_EMSCRIPTEN_VERSION #undef JSON_HEDLEY_EMSCRIPTEN_VERSION_CHECK #undef JSON_HEDLEY_END_C_DECLS #undef JSON_HEDLEY_FLAGS #undef JSON_HEDLEY_FLAGS_CAST #undef JSON_HEDLEY_GCC_HAS_ATTRIBUTE #undef JSON_HEDLEY_GCC_HAS_BUILTIN #undef JSON_HEDLEY_GCC_HAS_CPP_ATTRIBUTE #undef JSON_HEDLEY_GCC_HAS_DECLSPEC_ATTRIBUTE #undef JSON_HEDLEY_GCC_HAS_EXTENSION #undef JSON_HEDLEY_GCC_HAS_FEATURE #undef JSON_HEDLEY_GCC_HAS_WARNING #undef JSON_HEDLEY_GCC_NOT_CLANG_VERSION_CHECK #undef JSON_HEDLEY_GCC_VERSION #undef JSON_HEDLEY_GCC_VERSION_CHECK #undef JSON_HEDLEY_GNUC_HAS_ATTRIBUTE #undef JSON_HEDLEY_GNUC_HAS_BUILTIN #undef JSON_HEDLEY_GNUC_HAS_CPP_ATTRIBUTE #undef JSON_HEDLEY_GNUC_HAS_DECLSPEC_ATTRIBUTE #undef JSON_HEDLEY_GNUC_HAS_EXTENSION #undef JSON_HEDLEY_GNUC_HAS_FEATURE #undef JSON_HEDLEY_GNUC_HAS_WARNING #undef JSON_HEDLEY_GNUC_VERSION #undef JSON_HEDLEY_GNUC_VERSION_CHECK #undef JSON_HEDLEY_HAS_ATTRIBUTE #undef JSON_HEDLEY_HAS_BUILTIN #undef JSON_HEDLEY_HAS_CPP_ATTRIBUTE #undef JSON_HEDLEY_HAS_CPP_ATTRIBUTE_NS #undef JSON_HEDLEY_HAS_DECLSPEC_ATTRIBUTE #undef JSON_HEDLEY_HAS_EXTENSION #undef JSON_HEDLEY_HAS_FEATURE #undef JSON_HEDLEY_HAS_WARNING #undef JSON_HEDLEY_IAR_VERSION #undef JSON_HEDLEY_IAR_VERSION_CHECK #undef JSON_HEDLEY_IBM_VERSION #undef JSON_HEDLEY_IBM_VERSION_CHECK #undef JSON_HEDLEY_IMPORT #undef JSON_HEDLEY_INLINE #undef JSON_HEDLEY_INTEL_VERSION #undef JSON_HEDLEY_INTEL_VERSION_CHECK #undef JSON_HEDLEY_IS_CONSTANT #undef JSON_HEDLEY_IS_CONSTEXPR_ #undef JSON_HEDLEY_LIKELY #undef JSON_HEDLEY_MALLOC #undef JSON_HEDLEY_MESSAGE #undef JSON_HEDLEY_MSVC_VERSION #undef JSON_HEDLEY_MSVC_VERSION_CHECK #undef JSON_HEDLEY_NEVER_INLINE #undef JSON_HEDLEY_NON_NULL #undef JSON_HEDLEY_NO_ESCAPE #undef JSON_HEDLEY_NO_RETURN #undef JSON_HEDLEY_NO_THROW #undef JSON_HEDLEY_NULL #undef JSON_HEDLEY_PELLES_VERSION #undef JSON_HEDLEY_PELLES_VERSION_CHECK #undef JSON_HEDLEY_PGI_VERSION #undef JSON_HEDLEY_PGI_VERSION_CHECK #undef JSON_HEDLEY_PREDICT #undef JSON_HEDLEY_PRINTF_FORMAT #undef JSON_HEDLEY_PRIVATE #undef JSON_HEDLEY_PUBLIC #undef JSON_HEDLEY_PURE #undef JSON_HEDLEY_REINTERPRET_CAST #undef JSON_HEDLEY_REQUIRE #undef JSON_HEDLEY_REQUIRE_CONSTEXPR #undef JSON_HEDLEY_REQUIRE_MSG #undef JSON_HEDLEY_RESTRICT #undef JSON_HEDLEY_RETURNS_NON_NULL #undef JSON_HEDLEY_SENTINEL #undef JSON_HEDLEY_STATIC_ASSERT #undef JSON_HEDLEY_STATIC_CAST #undef JSON_HEDLEY_STRINGIFY #undef JSON_HEDLEY_STRINGIFY_EX #undef JSON_HEDLEY_SUNPRO_VERSION #undef JSON_HEDLEY_SUNPRO_VERSION_CHECK #undef JSON_HEDLEY_TINYC_VERSION #undef JSON_HEDLEY_TINYC_VERSION_CHECK #undef JSON_HEDLEY_TI_ARMCL_VERSION #undef JSON_HEDLEY_TI_ARMCL_VERSION_CHECK #undef JSON_HEDLEY_TI_CL2000_VERSION #undef JSON_HEDLEY_TI_CL2000_VERSION_CHECK #undef JSON_HEDLEY_TI_CL430_VERSION #undef JSON_HEDLEY_TI_CL430_VERSION_CHECK #undef JSON_HEDLEY_TI_CL6X_VERSION #undef JSON_HEDLEY_TI_CL6X_VERSION_CHECK #undef JSON_HEDLEY_TI_CL7X_VERSION #undef JSON_HEDLEY_TI_CL7X_VERSION_CHECK #undef JSON_HEDLEY_TI_CLPRU_VERSION #undef JSON_HEDLEY_TI_CLPRU_VERSION_CHECK #undef JSON_HEDLEY_TI_VERSION #undef JSON_HEDLEY_TI_VERSION_CHECK #undef JSON_HEDLEY_UNAVAILABLE #undef JSON_HEDLEY_UNLIKELY #undef JSON_HEDLEY_UNPREDICTABLE #undef JSON_HEDLEY_UNREACHABLE #undef JSON_HEDLEY_UNREACHABLE_RETURN #undef JSON_HEDLEY_VERSION #undef JSON_HEDLEY_VERSION_DECODE_MAJOR #undef JSON_HEDLEY_VERSION_DECODE_MINOR #undef JSON_HEDLEY_VERSION_DECODE_REVISION #undef JSON_HEDLEY_VERSION_ENCODE #undef JSON_HEDLEY_WARNING #undef JSON_HEDLEY_WARN_UNUSED_RESULT #undef JSON_HEDLEY_WARN_UNUSED_RESULT_MSG #undef JSON_HEDLEY_FALL_THROUGH #endif // INCLUDE_NLOHMANN_JSON_HPP_ ================================================ FILE: src/windhawk/app/libraries/winhttpwrappers/WinHTTPWrappers.h ================================================ /* Module : WinHTTPWrappers.h Purpose: Defines the interface for a set of C++ class which encapsulate WinHTTP. The classes are based on the MSDN Magazine article by Kenny Kerr at https://docs.microsoft.com/en-us/archive/msdn-magazine/2008/august/windows-with-c-asynchronous-winhttp History: PJN / 30-05-2011 1. All tracing in CWinHTTPHandle::OnCallback has been moved into a new TraceCallback method which client code is free to call. Also all tracing in CWinHTTPHandle:: OnCallbackComplete has been moved into a new TraceCallbackComplete method. Also all tracing in CDownloadFileWinHttpRequest::OnCallbackComplete has been moved into a new TraceCallbackComplete method. 2. Moved cleanup of resources from CDownloadFileWinHttpRequest::OnCallbackComplete to a new public method called ReleaseResources() PJN / 30-07-2011 1. CDownloadFileWinHttpRequest class is now called CAsyncWinHttpDownloader 2. Major rework of the CAsyncWinHttpDownloader to now support HTTP and Proxy authentication, pre-authentication, resumed downloads, file uploads, in-memory arrays and bandwidth throttling. 3. Fixed an issue in TraceCallback where WINHTTP_CALLBACK_STATUS_SECURE_FAILURE would be reported incorrectly by TRACE statements 4. Addition of a new CSyncWinHttpDownloader class which provides for synchronous WinHTTP downloads. 5. Updated the sample app to allow all of the new configuration settings of CAsyncWinHttpDownloader and CSyncWinHttpDownloader classes to be exercised. 6. Fixed a bug in CWinHTTPRequest::WriteData() where buffer parameter was incorrectly set as a LPVOID instead of a LPCVOID. PJN / 30-03-2013 1. Updated copyright details. 2. Updated the sample app to correctly release the file handles when the file is downloaded. Thanks to David Lowndes for reporting this bug. 3. Updated the code to clean compile on VC 2012 4. TimeSinceStartDownload() method has been extended to return a __int64 return value instead of a DWORD. 5. Changed class names to use C*WinHTTP* prefix instead of C*WinHttp*. PJN / 01-12-2013 1. Updated the code to clean compile on VC 2013. PJN / 08-06-2014 1. Updated copyright details. 2. UpdatedCAsyncWinHTTPDownloader::InitializetoallowthedwShareModeparameterof the ATL::CAtlFile::Createcall for the fileinstances tobedownloadanduploadedto becustomized.Thedefaultvalue forthesharemodeisnow0insteadofFILE_SHARE_READ. ThankstoSimonOrdeforprovidingthisniceaddition. 3. All the class methods have had SAL annotations added PJN / 08-03-2015 1. Updated copyright details. 2. Reworked the classes to optionally compile without MFC. By default the classes now use STL classes and idioms but if you define WINHTTPWRAPPERS_MFC_EXTENSTIONS the classes will revert back to the MFC behaviour. 3. Moved all the classes to a WinHTTPWrappers namespace 4. Renamed CWinHTTPHandle class to CHandle 5. Renamed CWinHTTPSession class to CSession 6. Renamed CWinHTTPConnection class to CConnection 7. Renamed CWinHTTPRequest class to CRequest 8. Renamed CAsyncWinHTTPDownloader class to CAsyncDownloader 9. Renamed CSyncWinHTTPDownloader class to CSyncDownloader 10. DeleteDownloadedFile now checks to see if "m_sFileToDownloadInto" is valid before it calls DeleteFile. Thanks to Paul Jackson for reporting this issue. 11. Reworked the CAsyncDownloader::SendRequest, On407Response, On401Response & OnRequestErrorCallback methods to pass a more correct value for the "dwTotalLength" parameter in the call to WinHttpSendRequest. Thanks to Paul Jackson for reporting this issue. PJN / 11-03-2015 1. Optimized allocation of temporary string stack variables in CAsyncDownloader::Initialize & CAsyncDownloader::DeleteDownloadedFile. Thanks to Paul Jackson for reporting this issue. PJN / 14-06-2015 1. Addition of a CAsyncDownloader::GetLastStatusCode method. 2. CAsyncDownloader::OnHeadersAvailableCallback and CSyncDownloader::SendRequestSync now preserves the HTTP status code when the value received is not 200, 206, 401 or 407 and the return value ATL::AtlHresultFromWin32(ERROR_WINHTTP_INVALID_HEADER) is about to be returned. 3. CSyncDownloader::SendRequestSync method has been made virtual. 4. Update the sample app to report the last status code if available when a download request fails. PJN / 07-11-2015 1. Updated SAL annotations in CHandle::SetOption to be consistent with Windows 10 SDK. 2. Fixed an issue in the use of _When_ SAL annotation in CHandle::SetOption 3. Update the code to compile cleanly on VC 2015 PJN / 06-03-2016 1. Updated copyright details. 2. The CAsyncDownloader destructor now resets the status callback function via SetStatusCallback(NULL, WINHTTP_CALLBACK_FLAG_ALL_NOTIFICATIONS). This prevents spurious callbacks occurring after the C++ object is destroyed which depending on how you allocated the C++ object could cause access violations in CHandle::_Callback. 3. Optimized the logic in the sample app when updating the edit box with status information PJN / 16-04-2017 1. Updated copyright details. 2. Added support for WinHttpCreateProxyResolver, WinHttpResetAutoProxy, WinHttpWriteProxySettings, WinHttpReadProxySettings & WinHttpGetProxySettingsVersion from the latest Windows 10 SDK PJN / 18-09-2017 1. Replaced CString::operator LPC*STR() calls throughout the codebase with CString::GetString calls PJN / 23-05-2018 1. Replaced NULL with nullptr throughout the code. 3. Fixed a number of C++ core guidelines compiler warnings. These changes mean that the code will now only compile on VC 2017 or later. PJN / 02-09-2018 1. Fixed a number of compiler warnings when using VS 2017 15.8.2 PJN / 29-09-2018 1. Removed code which supported WINHTTPWRAPPERS_MFC_EXTENSIONS define 2. Added wrappers for WinHttpWebSocketCompleteUpgrade, WinHttpWebSocketSend, WinHttpWebSocketReceive, WinHttpWebSocketShutdown, WinHttpWebSocketClose & WinHttpWebSocketQueryCloseStatus APIs. 3. Added wrappers for WinHttpGetProxyForUrlEx, WinHttpGetProxyForUrlEx2, WinHttpGetProxyResult & WinHttpGetProxyResultEx APIs. 4. Reworked TimeSinceStartDownload to use GetTickCount64 API. PJN / 24-11-2018 1. Fixed some further compiler warnings when using VS 2017 15.9.2 PJN / 19-04-2019 1. Updated copyright details 2. Updated the code to clean compile on VC 2019 PJN / 23-06-2019 1. Updated the code to clean compile when _ATL_NO_AUTOMATIC_NAMESPACE is defined. PJN / 14-08-2019 1. Fixed some further compiler warnings when using VC 2019 Preview v16.3.0 Preview 2.0 2. Added support for new WinHttpAddRequestHeadersEx API available in latest Windows 10 SDK PJN / 16-09-2019 1. Updated code to handle all 2XX response codes. PJN / 03-11-2019 1. Updated initialization of various structs to use C++ 11 list initialization PJN / 18-01-2020 1. Updated copyright details 2. Fixed more Clang-Tidy static code analysis warnings in the code. 3. Replaced BOOL with bool in various places PJN / 01-02-2020 1. Fixed a bug in the sample app when calling the WinHttpCrackUrl. Thanks to Onur Senturk for reporting this issue. 2. Fixed a bug in CSyncDownloader::SendRequestSync where the resources would not be released if the download was successful. Again thanks to to Onur Senturk for reporting this issue. PJN / 12-04-2020 1. Fixed more Clang-Tidy static code analysis warnings in the code. PJN / 13-11-2020 1. Added support for new WinHttpReadDataEx & WinHttpQueryHeadersEx APIs available in latest Windows 10 SDK. PJN / 09-04-2021 1. Updated copyright details 2. Fixed more /analyze static code analysis warnings in the code. PJN / 24-06-2021 1. Added support for new WinHttpQueryConnectionGroup API available in latest Windows 11 SDK. 2. Fixed up return value handling in GetProxySettingsVersion. PJN / 25-07-2021 1. Moved the QueryConnectionGroup method from CHandle into CConnection and CRequest 2. Moved some of the reusable functionality in CAsyncDownloader::OnHeadersAvailableCallback into a new virtual OnCheckStatusCode method. PJN / 22-01-2022 1. Updated copyright details. 2. Fixed more static code analysis warnings in Visual Studio 2022. PJN / 10-02-2022 1. Updated the code to use C++ uniform initialization for all variable declarations 2. Replaced ATL::CHeapPtr variables with std::vector. PJN / 16-05-2022 1. Fixed a static code analysis warning from PVS-Studio in CHandle::_Callback related to an unnecessary ATLASSERT. Thanks to David Lowndes for reporting this issue. 2. Fixed a static code analysis warning from PVS-Studio in the CWebSocket class which had an unnecessary "m_h" member that clashes with the same named member in the base class CHandle. Thanks to David Lowndes for reporting this issue. PJN / 17-08-2022 1. Reset more member variables in CAsyncDownloader::ReleaseResources. 2. CAsyncDownloader::m_dwLastStatusCode is now implemented as a std::optional. 3. CAsyncDownloader::m_nContentLength is now implemented as a std::optional. 4. Made all CAsyncDownloader member variables public to allow easier customization of the class 5. Implemented a version of CAsyncDownloader::SendRequest which allows the headers to be provided as a parameter. 6. Implemented a version of CSyncDownloader ::SendRequestSync which allows the headers to be provided as a parameter. 7. Reworked CAsyncDownloader::On407Response, CAsyncDownloader::On401Response and CAsyncDownloader::OnRequestErrorCallback to use correct value for dwTotalLength when calling WinHttpSendRequest. 8. Reworked CSyncDownloader::SendRequestSync to call OnCheckStatusCode method. PJN / 10-05-2023 1. Updated copyright details. 2. Updated module to indicate that it needs to be compiled using /std:c++17. Thanks to Martin Richter for reporting this issue. Copyright (c) 2011 - 2023 by PJ Naughter (Web: www.naughter.com, Email: pjna@naughter.com) All rights reserved. Copyright / Usage Details: You are allowed to include the source code in any product (commercial, shareware, freeware or otherwise) when your product is released in binary form. You are allowed to modify the source code in any way you want except you cannot modify the copyright details at the top of each module. If you want to distribute source code with your application, then you are only allowed to distribute versions released by the author. This is to maintain a single distribution point for the source code. */ //////////////////// Macros / Defines ///////////////////////////////////////// #pragma once #if _MSVC_LANG < 201703 #error WinHTTPWrappers requires a minimum C++ language standard of /std:c++17 #endif // #if _MSVC_LANG < 201703 #ifndef __WINHTTPWRAPPERS_H__ #define __WINHTTPWRAPPERS_H__ #ifndef CWINHTTPWRAPPERS_EXT_CLASS #define CWINHTTPWRAPPERS_EXT_CLASS #endif // #ifndef CWINHTTPWRAPPERS_EXT_CLASS #pragma comment(lib, "Winhttp.lib") //////////////////// Includes ///////////////////////////////////////////////// #ifndef _WINHTTPX_ #pragma message( \ "To avoid this message, please put WinHttp.h in your pre compiled header (normally stdafx.h)") #include #endif // #ifndef _WINHTTPX_ #ifndef __ATLBASE_H__ #pragma message( \ "To avoid this message, please put atlbase.h in your pre compiled header (normally stdafx.h)") #include #endif // #ifndef __ATLBASE_H__ #ifndef __ATLFILE_H__ #pragma message( \ "To avoid this message, please put atlfile.h in your pre compiled header (normally stdafx.h)") #include #endif // #ifndef __ATLFILE_H__ #ifndef __ATLSTR_H__ #pragma message( \ "To avoid this message, please put atlstr.h in your pre compiled header (normally stdafx.h)") #include #endif // #ifndef __ATLSTR_H__ #ifndef _STRING_ #pragma message( \ "To avoid this message, please put string in your pre compiled header (normally stdafx.h)") #include #endif // #ifndef _STRING_ #ifndef _VECTOR_ #pragma message( \ "To avoid this message, please put vector in your pre compiled header (normally stdafx.h)") #include #endif // #ifndef _VECTOR_ #ifndef _OPTIONAL_ #pragma message( \ "To avoid this message, please put optional in your pre compiled header (normally stdafx.h)") #include #endif // #ifndef _OPTIONAL_ //////////////////// Classes ////////////////////////////////////////////////// namespace WinHTTPWrappers { // Typedefs using String = std::wstring; using ByteArray = std::vector; // Wrapper for a WinHTTP HINTERNET handle class CWINHTTPWRAPPERS_EXT_CLASS CHandle { public: // Constructors / Destructors CHandle() = default; CHandle(_In_ const CHandle&) = delete; CHandle(_In_ CHandle&& handle) noexcept : m_h{handle.m_h} { handle.m_h = nullptr; } explicit CHandle(_In_ HINTERNET h) noexcept : m_h{h} {} virtual ~CHandle() { if (m_h != nullptr) Close(); } // Methods CHandle& operator=(_In_ const CHandle&) = delete; CHandle& operator=(_In_ CHandle&& handle) noexcept { if (m_h != nullptr) Close(); m_h = handle.m_h; handle.m_h = nullptr; return *this; } operator HINTERNET() const noexcept { return m_h; } void Attach(_In_ HINTERNET h) noexcept { #pragma warning(suppress : 26477) ATLASSUME(m_h == nullptr); m_h = h; } HINTERNET Detach() noexcept { HINTERNET h{m_h}; m_h = nullptr; return h; } void Close() noexcept { if (m_h != nullptr) { WinHttpCloseHandle(m_h); m_h = nullptr; } } HRESULT QueryOption(IN DWORD dwOption, _Out_writes_bytes_to_opt_(dwBufferLength, dwBufferLength) __out_data_source(NETWORK) void* pBuffer, IN OUT DWORD& dwBufferLength) noexcept { if (!WinHttpQueryOption(m_h, dwOption, pBuffer, &dwBufferLength)) return ATL::AtlHresultFromLastError(); return S_OK; } HRESULT SetOption( _In_ DWORD dwOption, _When_((dwOption == WINHTTP_OPTION_USERNAME || dwOption == WINHTTP_OPTION_PASSWORD || dwOption == WINHTTP_OPTION_PROXY_USERNAME || dwOption == WINHTTP_OPTION_PROXY_PASSWORD || dwOption == WINHTTP_OPTION_USER_AGENT), _At_((LPCWSTR)lpBuffer, _In_reads_(dwBufferLength))) _When_((dwOption == WINHTTP_OPTION_CLIENT_CERT_CONTEXT), _In_reads_bytes_opt_(dwBufferLength)) _When_((dwOption != WINHTTP_OPTION_USERNAME && dwOption != WINHTTP_OPTION_PASSWORD && dwOption != WINHTTP_OPTION_PROXY_USERNAME && dwOption != WINHTTP_OPTION_PROXY_PASSWORD && dwOption != WINHTTP_OPTION_CLIENT_CERT_CONTEXT && dwOption != WINHTTP_OPTION_USER_AGENT), _In_reads_bytes_(dwBufferLength)) LPVOID lpBuffer, _In_ DWORD dwBufferLength) noexcept { #pragma warning(suppress : 6387) if (!WinHttpSetOption(m_h, dwOption, lpBuffer, dwBufferLength)) return ATL::AtlHresultFromLastError(); return S_OK; } WINHTTP_STATUS_CALLBACK SetStatusCallback( _In_opt_ WINHTTP_STATUS_CALLBACK lpfnInternetCallback, _In_ DWORD dwNotificationFlags = WINHTTP_CALLBACK_FLAG_ALL_NOTIFICATIONS) noexcept { return WinHttpSetStatusCallback(m_h, lpfnInternetCallback, dwNotificationFlags, NULL); } WINHTTP_STATUS_CALLBACK SetStatusCallback( _In_ DWORD dwNotificationFlags = WINHTTP_CALLBACK_FLAG_ALL_NOTIFICATIONS) noexcept { return SetStatusCallback(_Callback, dwNotificationFlags); } // Member variables HINTERNET m_h{nullptr}; protected: // Methods static void CALLBACK _Callback(_In_ HINTERNET hInternet, _In_ DWORD_PTR dwContext, _In_ DWORD dwInternetStatus, _In_opt_ LPVOID lpvStatusInformation, _In_ DWORD dwStatusInformationLength) { // Check to see if we have a context value if (dwContext != 0) { // Convert from the SDK world to the C++ world #pragma warning(suppress : 26429 26490) auto pThis{reinterpret_cast(dwContext)}; // Call the virtual "OnCallback" method #pragma warning(suppress : 26486) const HRESULT hr{pThis->OnCallback(hInternet, dwInternetStatus, lpvStatusInformation, dwStatusInformationLength)}; // If the "Callback" method failed called the "OnCallbackComplete" // method if (FAILED(hr)) #pragma warning(suppress : 26486) pThis->OnCallbackComplete(hr, hInternet, dwInternetStatus, lpvStatusInformation, dwStatusInformationLength); } } #ifdef _DEBUG static void TraceCallback(_In_ HINTERNET hInternet, _In_ DWORD dwInternetStatus, _In_opt_ LPVOID lpvStatusInformation, _In_ DWORD dwStatusInformationLength) { switch (dwInternetStatus) { case WINHTTP_CALLBACK_STATUS_CLOSING_CONNECTION: { ATLTRACE( _T("Closing the connection to the server, Handle:%p\n"), hInternet); break; } case WINHTTP_CALLBACK_STATUS_CONNECTED_TO_SERVER: { ATLTRACE( _T("Successfully connected to the server:%ls, Handle:%p\n"), static_cast(lpvStatusInformation), hInternet); break; } case WINHTTP_CALLBACK_STATUS_CONNECTING_TO_SERVER: { ATLTRACE(_T("Connecting to the server:%ls, Handle:%p\n"), static_cast(lpvStatusInformation), hInternet); break; } case WINHTTP_CALLBACK_STATUS_CONNECTION_CLOSED: { ATLTRACE( _T("Successfully closed the connection to the server, ") _T("Handle:%p\n"), hInternet); break; } case WINHTTP_CALLBACK_STATUS_DATA_AVAILABLE: { #pragma warning(suppress : 26477) ATLASSUME(lpvStatusInformation != nullptr); ATLTRACE( _T("Data is available to be retrieved, Handle:%p, Data ") _T("Available:%u\n"), hInternet, *(static_cast(lpvStatusInformation))); break; } case WINHTTP_CALLBACK_STATUS_HANDLE_CREATED: { #pragma warning(suppress : 26477) ATLASSUME(lpvStatusInformation != nullptr); ATLTRACE(_T("Handle created, Handle:%p\n"), *(static_cast(lpvStatusInformation))); break; } case WINHTTP_CALLBACK_STATUS_HANDLE_CLOSING: { #pragma warning(suppress : 26477) ATLASSUME(lpvStatusInformation != nullptr); ATLTRACE(_T("Handle closing, Handle:%p\n"), *(static_cast(lpvStatusInformation))); break; } case WINHTTP_CALLBACK_STATUS_HEADERS_AVAILABLE: { ATLTRACE( _T("The response header has been received, Handle:%p\n"), hInternet); break; } case WINHTTP_CALLBACK_STATUS_INTERMEDIATE_RESPONSE: { #pragma warning(suppress : 26477) ATLASSUME(lpvStatusInformation != nullptr); ATLTRACE( _T("Received an intermediate (100 level) status code ") _T("message from the server, Handle:%p, Status:%u\n"), hInternet, *(static_cast(lpvStatusInformation))); break; } case WINHTTP_CALLBACK_STATUS_NAME_RESOLVED: { ATLTRACE( _T("Successfully found the IP address of the server:%ls, ") _T("Handle:%p\n"), static_cast(lpvStatusInformation), hInternet); break; } case WINHTTP_CALLBACK_STATUS_READ_COMPLETE: { ATLTRACE( _T("Data was successfully read from the server, Data ") _T("Read:%u, Handle:%p\n"), dwStatusInformationLength, hInternet); break; } case WINHTTP_CALLBACK_STATUS_RECEIVING_RESPONSE: { ATLTRACE( _T("Waiting for the server to respond to a request, ") _T("Handle:%p\n"), hInternet); break; } case WINHTTP_CALLBACK_STATUS_REDIRECT: { ATLTRACE( _T("An HTTP request is about to automatically redirect ") _T("the request to %ls, Handle:%p\n"), static_cast(lpvStatusInformation), hInternet); break; } case WINHTTP_CALLBACK_STATUS_REQUEST_ERROR: { #pragma warning(suppress : 26477) ATLASSUME(lpvStatusInformation != nullptr); auto pResult{static_cast( lpvStatusInformation)}; ATLTRACE( _T("An error occurred while sending an HTTP request, ") _T("Error:%u, Handle:%p\n"), pResult->dwError, hInternet); break; } case WINHTTP_CALLBACK_STATUS_REQUEST_SENT: { #pragma warning(suppress : 26477) ATLASSUME(lpvStatusInformation != nullptr); ATLTRACE( _T("Successfully sent the information request to the ") _T("server, Data Sent:%u, Handle:%p\n"), *(static_cast(lpvStatusInformation)), hInternet); break; } case WINHTTP_CALLBACK_STATUS_RESOLVING_NAME: { ATLTRACE( _T("Looking up the IP address of a server name:%ls, ") _T("Handle:%p\n"), static_cast(lpvStatusInformation), hInternet); break; } case WINHTTP_CALLBACK_STATUS_RESPONSE_RECEIVED: { #pragma warning(suppress : 26477) ATLASSUME(lpvStatusInformation != nullptr); ATLTRACE( _T("Successfully received a response from the server, ") _T("Data Received:%u, Handle:%p\n"), *(static_cast(lpvStatusInformation)), hInternet); break; } case WINHTTP_CALLBACK_STATUS_SECURE_FAILURE: { #pragma warning(suppress : 26477) ATLASSUME(lpvStatusInformation != nullptr); const DWORD dwStatusInformation{ *(static_cast(lpvStatusInformation))}; if (dwStatusInformation & WINHTTP_CALLBACK_STATUS_FLAG_CERT_REV_FAILED) { ATLTRACE( _T("Certification revocation checking has been ") _T("enabled, but the revocation check failed to ") _T("verify whether a certificate has been revoked, ") _T("Handle:%p\n"), hInternet); } if (dwStatusInformation & WINHTTP_CALLBACK_STATUS_FLAG_INVALID_CERT) { ATLTRACE(_T("SSL certificate is invalid, Handle:%p\n"), hInternet); } if (dwStatusInformation & WINHTTP_CALLBACK_STATUS_FLAG_CERT_REVOKED) { ATLTRACE(_T("SSL certificate was revoked, Handle:%p\n"), hInternet); } if (dwStatusInformation & WINHTTP_CALLBACK_STATUS_FLAG_INVALID_CA) { ATLTRACE( _T("The function is unfamiliar with the Certificate ") _T("Authority that generated the server's ") _T("certificate, Handle:%p\n"), hInternet); } if (dwStatusInformation & WINHTTP_CALLBACK_STATUS_FLAG_CERT_CN_INVALID) { ATLTRACE( _T("SSL certificate common name (host name field) is ") _T("incorrect, Handle:%p\n"), hInternet); } if (dwStatusInformation & WINHTTP_CALLBACK_STATUS_FLAG_CERT_DATE_INVALID) { ATLTRACE( _T("SSL certificate date that was received from the ") _T("server is bad. The certificate is expired, ") _T("Handle:%p\n"), hInternet); } if (dwStatusInformation & WINHTTP_CALLBACK_STATUS_FLAG_SECURITY_CHANNEL_ERROR) { ATLTRACE( _T("The application experienced an internal error ") _T("loading the SSL libraries, Handle:%p\n"), hInternet); } break; } case WINHTTP_CALLBACK_STATUS_SENDING_REQUEST: { ATLTRACE( _T("Sending the information request to the server, ") _T("Handle:%p\n"), hInternet); break; } case WINHTTP_CALLBACK_STATUS_SENDREQUEST_COMPLETE: { ATLTRACE(_T("The request completed successfully, Handle:%p\n"), hInternet); break; } case WINHTTP_CALLBACK_STATUS_WRITE_COMPLETE: { #pragma warning(suppress : 26477) ATLASSUME(lpvStatusInformation != nullptr); ATLTRACE( _T("Data was successfully written to the server, Data ") _T("Written:%u, Handle:%p\n"), *(static_cast(lpvStatusInformation)), hInternet); break; } default: { ATLTRACE(_T("Unknown status:%08X, Handle:%p\n"), dwInternetStatus, hInternet); break; } } } #endif // #ifdef _DEBUG #pragma warning(suppress : 26440) virtual HRESULT OnCallback(_In_ HINTERNET hInternet, _In_ DWORD dwInternetStatus, _In_opt_ LPVOID lpvStatusInformation, _In_ DWORD dwStatusInformationLength) { UNREFERENCED_PARAMETER(hInternet); UNREFERENCED_PARAMETER(dwInternetStatus); UNREFERENCED_PARAMETER(lpvStatusInformation); UNREFERENCED_PARAMETER(dwStatusInformationLength); return S_FALSE; // S_FALSE means not handled in our callback } #ifdef _DEBUG static void TraceCallbackComplete(_In_ HRESULT hr, _In_ HINTERNET hInternet, _In_ DWORD dwInternetStatus, _In_opt_ LPVOID lpvStatusInformation, _In_ DWORD dwStatusInformationLength) { UNREFERENCED_PARAMETER(lpvStatusInformation); UNREFERENCED_PARAMETER(dwStatusInformationLength); ATLTRACE( _T("CWinHTTPHandle::TraceCallbackComplete, HRESULT:%08X, ") _T("InternetStatus:%08X, Handle:%p\n"), hr, dwInternetStatus, hInternet); } #endif // #ifdef _DEBUG #pragma warning(suppress : 26440) virtual HRESULT OnCallbackComplete(_In_ HRESULT hr, _In_ HINTERNET hInternet, _In_ DWORD dwInternetStatus, _In_opt_ LPVOID lpvStatusInformation, _In_ DWORD dwStatusInformationLength) { UNREFERENCED_PARAMETER(hr); UNREFERENCED_PARAMETER(hInternet); UNREFERENCED_PARAMETER(dwInternetStatus); UNREFERENCED_PARAMETER(lpvStatusInformation); UNREFERENCED_PARAMETER(dwStatusInformationLength); return E_NOTIMPL; } }; // Wrapper for a WinHTTP resolver HINTERNET handle class CWINHTTPWRAPPERS_EXT_CLASS CResolver : public CHandle { public: // Constructors / Destructors CResolver() = default; CResolver(_In_ const CResolver&) = delete; CResolver(_In_ CResolver&& resolver) noexcept : CHandle{std::move(resolver)} {} explicit CResolver(_In_ HINTERNET h) noexcept : CHandle{h} {} ~CResolver() = default; // NOLINT(modernize-use-override) // Methods CResolver& operator=(_In_ const CResolver&) = delete; #pragma warning(suppress : 26456) CResolver& operator=(_In_ CResolver&& resolver) noexcept { __super::operator=(std::move(resolver)); return *this; } __if_exists(WinHttpGetProxyForUrlEx) { #pragma warning(suppress : 6553) DWORD GetProxyForUrlEx( _In_ PCWSTR pcwszUrl, _In_ WINHTTP_AUTOPROXY_OPTIONS * pAutoProxyOptions, _In_opt_ DWORD_PTR pContext) noexcept { // Validate our parameters #pragma warning(suppress : 26477) ATLASSERT(m_h != nullptr); return WinHttpGetProxyForUrlEx(m_h, pcwszUrl, pAutoProxyOptions, pContext); } } __if_exists(WinHttpGetProxyForUrlEx2) { #pragma warning(suppress : 6553) DWORD GetProxyForUrlEx2( _In_ PCWSTR pcwszUrl, _In_ WINHTTP_AUTOPROXY_OPTIONS * pAutoProxyOptions, _In_ DWORD cbInterfaceSelectionContext, _In_reads_bytes_opt_(cbInterfaceSelectionContext) BYTE * pInterfaceSelectionContext, _In_opt_ DWORD_PTR pContext) noexcept { // Validate our parameters #pragma warning(suppress : 26477) ATLASSERT(m_h != nullptr); return WinHttpGetProxyForUrlEx2( m_h, pcwszUrl, pAutoProxyOptions, cbInterfaceSelectionContext, pInterfaceSelectionContext, pContext); } } __if_exists(WinHttpGetProxyResult) { DWORD GetProxyResult(_Out_ WINHTTP_PROXY_RESULT * pProxyResult) noexcept { // Validate our parameters #pragma warning(suppress : 26477) ATLASSERT(m_h != nullptr); return WinHttpGetProxyResult(m_h, pProxyResult); } } __if_exists(WinHttpGetProxyResultEx) { DWORD GetProxyResultEx(_Out_ _Out_ WINHTTP_PROXY_RESULT_EX * pProxyResultEx) noexcept { // Validate our parameters #pragma warning(suppress : 26477) ATLASSERT(m_h != nullptr); return WinHttpGetProxyResultEx(m_h, pProxyResultEx); } } }; // Wrapper for a WinHttp web socket handle class CWINHTTPWRAPPERS_EXT_CLASS CWebSocket : public CHandle { public: // Constructors / Destructors CWebSocket() = default; CWebSocket(_In_ const CWebSocket&) = delete; #pragma warning(suppress : 26495) CWebSocket(_In_ CWebSocket&& socket) noexcept : CHandle{std::move(socket)} {} #pragma warning(suppress : 26495) explicit CWebSocket(_In_ HINTERNET h) noexcept : CHandle{h} {} ~CWebSocket() = default; // NOLINT(modernize-use-override) // Methods CWebSocket& operator=(_In_ const CWebSocket&) = delete; #pragma warning(suppress : 26456) CWebSocket& operator=(_In_ CWebSocket&& socket) noexcept { __super::operator=(std::move(socket)); return *this; } #pragma warning(suppress : 26812) DWORD Send(_In_ WINHTTP_WEB_SOCKET_BUFFER_TYPE eBufferType, _In_reads_opt_(dwBufferLength) PVOID pvBuffer, _In_ DWORD dwBufferLength) noexcept { // Validate our parameters #pragma warning(suppress : 26477) ATLASSERT(m_h != nullptr); return WinHttpWebSocketSend(m_h, eBufferType, pvBuffer, dwBufferLength); } DWORD Receive(_Out_writes_bytes_to_(dwBufferLength, *pdwBytesRead) PVOID pvBuffer, _In_ DWORD dwBufferLength, _Out_range_(0, dwBufferLength) DWORD* pdwBytesRead, _Out_ WINHTTP_WEB_SOCKET_BUFFER_TYPE* peBufferType) noexcept { // Validate our parameters #pragma warning(suppress : 26477) ATLASSERT(m_h != nullptr); return WinHttpWebSocketReceive(m_h, pvBuffer, dwBufferLength, pdwBytesRead, peBufferType); } DWORD Shutdown(_In_ USHORT usStatus, _In_reads_bytes_opt_(dwReasonLength) PVOID pvReason, _In_range_(0, WINHTTP_WEB_SOCKET_MAX_CLOSE_REASON_LENGTH) DWORD dwReasonLength) noexcept { // Validate our parameters #pragma warning(suppress : 26477) ATLASSERT(m_h != nullptr); return WinHttpWebSocketShutdown(m_h, usStatus, pvReason, dwReasonLength); } DWORD WebSocketClose(_In_ USHORT usStatus, _In_reads_bytes_opt_(dwReasonLength) PVOID pvReason, _In_range_(0, WINHTTP_WEB_SOCKET_MAX_CLOSE_REASON_LENGTH) DWORD dwReasonLength) noexcept { // Validate our parameters #pragma warning(suppress : 26477) ATLASSERT(m_h != nullptr); return WinHttpWebSocketClose(m_h, usStatus, pvReason, dwReasonLength); } DWORD QueryCloseStatus( _Out_ USHORT* pusStatus, _Out_writes_bytes_to_opt_(dwReasonLength, *pdwReasonLengthConsumed) PVOID pvReason, _In_range_(0, WINHTTP_WEB_SOCKET_MAX_CLOSE_REASON_LENGTH) DWORD dwReasonLength, _Out_range_(0, WINHTTP_WEB_SOCKET_MAX_CLOSE_REASON_LENGTH) DWORD* pdwReasonLengthConsumed) noexcept { // Validate our parameters #pragma warning(suppress : 26477) ATLASSERT(m_h != nullptr); return WinHttpWebSocketQueryCloseStatus( m_h, pusStatus, pvReason, dwReasonLength, pdwReasonLengthConsumed); } }; // Wrapper for a WinHTTP Session HINTERNET handle class CWINHTTPWRAPPERS_EXT_CLASS CSession : public CHandle { public: // Constructors / Destructors CSession() = default; CSession(_In_ const CSession&) = delete; CSession(_In_ CSession&& session) noexcept : CHandle{std::move(session)} {} explicit CSession(_In_ HINTERNET h) noexcept : CHandle{h} {} ~CSession() = default; // NOLINT(modernize-use-override) // Methods CSession& operator=(_In_ const CSession&) = delete; #pragma warning(suppress : 26456) CSession& operator=(_In_ CSession&& session) noexcept { __super::operator=(std::move(session)); return *this; } HRESULT Initialize( _In_opt_z_ LPCWSTR pwszUserAgent, _In_ DWORD dwAccessType = WINHTTP_ACCESS_TYPE_DEFAULT_PROXY, _In_opt_z_ LPCWSTR pwszProxyName = WINHTTP_NO_PROXY_NAME, _In_opt_z_ LPCWSTR pwszProxyBypass = WINHTTP_NO_PROXY_BYPASS, _In_ DWORD dwFlags = WINHTTP_FLAG_ASYNC) noexcept { HINTERNET hSession{WinHttpOpen(pwszUserAgent, dwAccessType, pwszProxyName, pwszProxyBypass, dwFlags)}; if (hSession == nullptr) return ATL::AtlHresultFromLastError(); Attach(hSession); return S_OK; } HRESULT GetProxyForUrl(_In_ LPCWSTR lpcwszUrl, _In_ WINHTTP_AUTOPROXY_OPTIONS& AutoProxyOptions, _Out_ DWORD& dwAccessType, _Out_ String& sProxy, _Out_ String& sProxyBypass) { WINHTTP_PROXY_INFO proxyInfo{}; if (!WinHttpGetProxyForUrl(m_h, lpcwszUrl, &AutoProxyOptions, &proxyInfo)) return ATL::AtlHresultFromLastError(); // Update the output parameters dwAccessType = proxyInfo.dwAccessType; #pragma warning(suppress : 6387) sProxy = proxyInfo.lpszProxy; #pragma warning(suppress : 6387) sProxyBypass = proxyInfo.lpszProxyBypass; // Free up the allocated memory if (proxyInfo.lpszProxy != nullptr) GlobalFree(proxyInfo.lpszProxy); if (proxyInfo.lpszProxyBypass != nullptr) GlobalFree(proxyInfo.lpszProxyBypass); return S_OK; } HRESULT SetTimeouts(_In_ int dwResolveTimeout, _In_ int dwConnectTimeout, _In_ int dwSendTimeout, _In_ int dwReceiveTimeout) noexcept { if (!WinHttpSetTimeouts(m_h, dwResolveTimeout, dwConnectTimeout, dwSendTimeout, dwReceiveTimeout)) return ATL::AtlHresultFromLastError(); return S_OK; } __if_exists(WinHttpCreateProxyResolver) { HRESULT CreateProxyResolver(_Inout_ CResolver & resolver) noexcept { // Validate our parameters #pragma warning(suppress : 26477) ATLASSERT(resolver.operator HANDLE() == nullptr); #pragma warning(suppress : 26477) ATLASSERT(m_h != nullptr); return ATL::AtlHresultFromWin32( WinHttpCreateProxyResolver(m_h, &resolver.m_h)); } } __if_exists(WinHttpResetAutoProxy) { HRESULT ResetAutoProxy(_In_ DWORD dwFlags) noexcept { // Validate our parameters #pragma warning(suppress : 26477) ATLASSERT(m_h != nullptr); return ATL::AtlHresultFromWin32( WinHttpResetAutoProxy(m_h, dwFlags)); } } __if_exists(WinHttpWriteProxySettings) { HRESULT WriteProxySettings( _In_ BOOL fForceUpdate, _In_ WINHTTP_PROXY_SETTINGS * pWinHttpProxySettings) noexcept { // Validate our parameters #pragma warning(suppress : 26477) ATLASSERT(m_h != nullptr); return ATL::AtlHresultFromWin32(WinHttpWriteProxySettings( m_h, fForceUpdate, pWinHttpProxySettings)); } } __if_exists(WinHttpReadProxySettings) { HRESULT ReadProxySettings( _In_opt_ PCWSTR pcwszConnectionName, _In_ BOOL fFallBackToDefaultSettings, _In_ BOOL fSetAutoDiscoverForDefaultSettings, _Out_ DWORD * pdwSettingsVersion, _Out_ BOOL * pfDefaultSettingsAreReturned, _Out_ WINHTTP_PROXY_SETTINGS * pWinHttpProxySettings) noexcept { // Validate our parameters #pragma warning(suppress : 26477) ATLASSERT(m_h != nullptr); return ATL::AtlHresultFromWin32(WinHttpReadProxySettings( m_h, pcwszConnectionName, fFallBackToDefaultSettings, fSetAutoDiscoverForDefaultSettings, pdwSettingsVersion, pfDefaultSettingsAreReturned, pWinHttpProxySettings)); } } __if_exists(WinHttpGetProxySettingsVersion) { HRESULT GetProxySettingsVersion(_Out_ DWORD * pdwProxySettingsVersion) noexcept { // Validate our parameters #pragma warning(suppress : 26477) ATLASSERT(m_h != nullptr); return ATL::AtlHresultFromWin32( WinHttpGetProxySettingsVersion(m_h, pdwProxySettingsVersion)); } } }; // Wrapper for a WinHTTP connection HINTERNET handle class CWINHTTPWRAPPERS_EXT_CLASS CConnection : public CHandle { public: // Constructors / Destructors CConnection() = default; CConnection(_In_ const CConnection&) = delete; CConnection(_In_ CConnection&& connection) noexcept : CHandle{std::move(connection)} {} explicit CConnection(_In_ HINTERNET h) noexcept : CHandle{h} {} ~CConnection() = default; // NOLINT(modernize-use-override) // Methods CConnection& operator=(_In_ const CConnection&) = delete; #pragma warning(suppress : 26456) CConnection& operator=(_In_ CConnection&& connection) noexcept { __super::operator=(std::move(connection)); return *this; } HRESULT Initialize( _In_ const CSession& session, _In_z_ LPCWSTR pwszServerName, _In_ INTERNET_PORT nServerPort = INTERNET_DEFAULT_PORT) noexcept { HINTERNET hConnection{ WinHttpConnect(session, pwszServerName, nServerPort, 0)}; if (hConnection == nullptr) return ATL::AtlHresultFromLastError(); Attach(hConnection); return S_OK; } __if_exists(WinHttpQueryConnectionGroup) { HRESULT QueryConnectionGroup( _In_opt_ const GUID* pGuidConnection, _In_ ULONGLONG ullFlags, _Inout_ PWINHTTP_QUERY_CONNECTION_GROUP_RESULT* ppResult) noexcept { // Validate our parameters #pragma warning(suppress : 26477) ATLASSERT(m_h != nullptr); return ATL::AtlHresultFromWin32(WinHttpQueryConnectionGroup( m_h, pGuidConnection, ullFlags, ppResult)); } } }; // Wrapper for a WinHTTP request HINTERNET handle class CWINHTTPWRAPPERS_EXT_CLASS CRequest : public CHandle { public: // Constructors / Destructors CRequest() = default; CRequest(_In_ const CRequest&) = delete; CRequest(_In_ CRequest&& request) noexcept : CHandle{std::move(request)} {} explicit CRequest(_In_ HINTERNET h) noexcept : CHandle{h} {} ~CRequest() = default; // NOLINT(modernize-use-override) // Methods CRequest& operator=(_In_ const CRequest&) = delete; #pragma warning(suppress : 26456) CRequest& operator=(_In_ CRequest&& request) noexcept { __super::operator=(std::move(request)); return *this; } HRESULT Initialize( _In_ const CConnection& connection, _In_z_ LPCWSTR pwszObjectName, _In_opt_z_ LPCWSTR pwszVerb = nullptr, _In_opt_z_ LPCWSTR pwszVersion = nullptr, _In_opt_z_ LPCWSTR pwszReferrer = WINHTTP_NO_REFERER, _In_opt_ LPCWSTR* ppwszAcceptTypes = WINHTTP_DEFAULT_ACCEPT_TYPES, _In_ DWORD dwFlags = 0) noexcept { HINTERNET hRequest{WinHttpOpenRequest( connection, pwszVerb, pwszObjectName, pwszVersion, pwszReferrer, ppwszAcceptTypes, dwFlags)}; if (hRequest == nullptr) return ATL::AtlHresultFromLastError(); Attach(hRequest); return S_OK; } HRESULT AddHeaders( #ifdef _When_ _When_(dwHeadersLength == (DWORD)-1, _In_z_) _When_(dwHeadersLength != (DWORD)-1, _In_reads_(dwHeadersLength)) LPCWSTR pwszHeaders, #else _In_ LPCWSTR pwszHeaders, #endif _In_ DWORD dwHeadersLength, _In_ DWORD dwModifiers) noexcept { // Validate our parameters #pragma warning(suppress : 26477) ATLASSUME(m_h != nullptr); if (!WinHttpAddRequestHeaders(m_h, pwszHeaders, dwHeadersLength, dwModifiers)) return ATL::AtlHresultFromLastError(); return S_OK; } __if_exists(WinHttpAddRequestHeadersEx) { DWORD AddRequestHeaders( _In_ DWORD dwModifiers, _In_ ULONGLONG ullFlags, _In_ ULONGLONG ullExtra, _In_ DWORD cHeaders, _In_reads_(cHeaders) WINHTTP_EXTENDED_HEADER * pHeaders) noexcept { // Validate our parameters #pragma warning(suppress : 26477) ATLASSUME(m_h != nullptr); return WinHttpAddRequestHeadersEx(m_h, dwModifiers, ullFlags, ullExtra, cHeaders, pHeaders); } } HRESULT QueryAuthSchemes(_Out_ DWORD& dwSupportedSchemes, _Out_ DWORD& dwFirstScheme, _Out_ DWORD& dwAuthTarget) noexcept { if (!WinHttpQueryAuthSchemes(m_h, &dwSupportedSchemes, &dwFirstScheme, &dwAuthTarget)) return ATL::AtlHresultFromLastError(); return S_OK; } HRESULT QueryDataAvailable(__out_data_source(NETWORK) DWORD* lpdwNumberOfBytesAvailable) noexcept { if (!WinHttpQueryDataAvailable(m_h, lpdwNumberOfBytesAvailable)) return ATL::AtlHresultFromLastError(); return S_OK; } HRESULT QueryHeaders(IN DWORD dwInfoLevel, IN LPCWSTR pwszName OPTIONAL, _Out_writes_bytes_to_opt_(dwBufferLength, dwBufferLength) __out_data_source(NETWORK) LPVOID lpBuffer, IN OUT DWORD& dwBufferLength, IN OUT DWORD* lpdwIndex OPTIONAL) noexcept { if (!WinHttpQueryHeaders(m_h, dwInfoLevel, pwszName, lpBuffer, &dwBufferLength, lpdwIndex)) return ATL::AtlHresultFromLastError(); return S_OK; } __if_exists(WinHttpQueryHeadersEx) { #pragma warning(suppress : 26476) HRESULT QueryHeadersEx( _In_ DWORD dwInfoLevel, _In_ ULONGLONG ullFlags, _In_ UINT uiCodePage, _Inout_opt_ PDWORD pdwIndex, _In_opt_ PWINHTTP_HEADER_NAME pHeaderName, _Out_writes_bytes_to_opt_(*pdwBufferLength, *pdwBufferLength) PVOID pBuffer, _Inout_ PDWORD pdwBufferLength, _Out_writes_opt_(*pdwHeadersCount) PWINHTTP_EXTENDED_HEADER * ppHeaders, _Out_ PDWORD pdwHeadersCount) noexcept { #pragma warning(suppress : 6001) if (!WinHttpQueryHeadersEx(m_h, dwInfoLevel, ullFlags, uiCodePage, pdwIndex, pHeaderName, pBuffer, pdwBufferLength, ppHeaders, pdwHeadersCount)) return ATL::AtlHresultFromLastError(); return S_OK; } } HRESULT ReadData(_Out_writes_bytes_to_(dwNumberOfBytesToRead, *lpdwNumberOfBytesRead) __out_data_source(NETWORK) LPVOID lpBuffer, IN DWORD dwNumberOfBytesToRead, OUT DWORD* lpdwNumberOfBytesRead) noexcept { if (!WinHttpReadData(m_h, lpBuffer, dwNumberOfBytesToRead, lpdwNumberOfBytesRead)) return ATL::AtlHresultFromLastError(); return S_OK; } __if_exists(WinHttpReadDataEx) { HRESULT ReadDataEx( _Out_writes_bytes_to_(dwNumberOfBytesToRead, *lpdwNumberOfBytesRead) __out_data_source(NETWORK) LPVOID lpBuffer, IN DWORD dwNumberOfBytesToRead, OUT DWORD * lpdwNumberOfBytesRead, IN ULONGLONG ullFlags, IN DWORD cbProperty, _In_reads_bytes_opt_(cbProperty) PVOID pvProperty) noexcept { if (!WinHttpReadDataEx(m_h, lpBuffer, dwNumberOfBytesToRead, lpdwNumberOfBytesRead, ullFlags, cbProperty, pvProperty)) return ATL::AtlHresultFromLastError(); return S_OK; } } HRESULT ReceiveResponse() noexcept { if (!WinHttpReceiveResponse(m_h, nullptr)) return ATL::AtlHresultFromLastError(); return S_OK; } HRESULT SendRequest(_In_reads_opt_(dwHeadersLength) LPCWSTR pwszHeaders = WINHTTP_NO_ADDITIONAL_HEADERS, _In_ DWORD dwHeadersLength = 0, _In_reads_bytes_opt_(dwOptionalLength) LPVOID lpOptional = WINHTTP_NO_REQUEST_DATA, _In_ DWORD dwOptionalLength = 0, _In_ DWORD dwTotalLength = 0, _In_ DWORD_PTR dwContext = 0) noexcept { if (!WinHttpSendRequest(m_h, pwszHeaders, dwHeadersLength, lpOptional, dwOptionalLength, dwTotalLength, dwContext)) return ATL::AtlHresultFromLastError(); return S_OK; } HRESULT SetCredentials(_In_ DWORD AuthTargets, _In_ DWORD AuthScheme, _In_ LPCWSTR pwszUserName, _In_ LPCWSTR pwszPassword) noexcept { if (!WinHttpSetCredentials(m_h, AuthTargets, AuthScheme, pwszUserName, pwszPassword, nullptr)) return ATL::AtlHresultFromLastError(); return S_OK; } HRESULT SetTimeouts(_In_ int dwResolveTimeout, _In_ int dwConnectTimeout, _In_ int dwSendTimeout, _In_ int dwReceiveTimeout) noexcept { if (!WinHttpSetTimeouts(m_h, dwResolveTimeout, dwConnectTimeout, dwSendTimeout, dwReceiveTimeout)) return ATL::AtlHresultFromLastError(); return S_OK; } HRESULT WriteData(_In_reads_bytes_opt_(dwNumberOfBytesToWrite) LPCVOID lpBuffer, _In_ DWORD dwNumberOfBytesToWrite, _Out_opt_ DWORD* lpdwNumberOfBytesWritten) noexcept { if (!WinHttpWriteData(m_h, lpBuffer, dwNumberOfBytesToWrite, lpdwNumberOfBytesWritten)) return ATL::AtlHresultFromLastError(); return S_OK; } #pragma warning(suppress : 6553) [[nodiscard]] CWebSocket WebSocketCompleteUpgrade( _In_opt_ DWORD_PTR pContext) const noexcept { // Validate our parameters #pragma warning(suppress : 26477) ATLASSUME(m_h != nullptr); return CWebSocket{WinHttpWebSocketCompleteUpgrade(m_h, pContext)}; } __if_exists(WinHttpQueryConnectionGroup) { HRESULT QueryConnectionGroup( _In_opt_ const GUID* pGuidConnection, _In_ ULONGLONG ullFlags, _Inout_ PWINHTTP_QUERY_CONNECTION_GROUP_RESULT* ppResult) noexcept { // Validate our parameters #pragma warning(suppress : 26477) ATLASSERT(m_h != nullptr); return ATL::AtlHresultFromWin32(WinHttpQueryConnectionGroup( m_h, pGuidConnection, ullFlags, ppResult)); } } }; // Wrapper for a simple WinHttp async download class CWINHTTPWRAPPERS_EXT_CLASS CAsyncDownloader : public CRequest { public: // Constructors / Destructors CAsyncDownloader() noexcept : m_dwProxyPreauthenticationScheme{WINHTTP_AUTH_SCHEME_NEGOTIATE}, m_dwHTTPPreauthenticationScheme{WINHTTP_AUTH_SCHEME_NEGOTIATE}, m_bProxyPreauthentication{true}, m_bHTTPPreauthentication{true}, m_nDownloadStartPos{0}, m_bNoURLRedirect{false}, m_lpRequest{nullptr}, m_dwRequestSize{0}, m_dbLimit{0}, m_nFileToUploadSize{0}, m_nFileToUploadIndex{0}, m_pOptionalBuffer{nullptr}, m_dwOptionalBufferLength{0}, m_dwProxyAuthScheme{0}, m_nTotalBytesRead{0}, m_dwStartTicksDownload{0}, m_bUsingObjectStatusCallback{false} {} CAsyncDownloader(const CAsyncDownloader&) = delete; CAsyncDownloader(CAsyncDownloader&&) = delete; ~CAsyncDownloader() // NOLINT(modernize-use-override) { if (m_bUsingObjectStatusCallback) { SetStatusCallback(nullptr, WINHTTP_CALLBACK_FLAG_ALL_NOTIFICATIONS); m_bUsingObjectStatusCallback = false; } } // Methods CAsyncDownloader& operator=(const CAsyncDownloader&) = delete; CAsyncDownloader& operator=(CAsyncDownloader&&) = delete; #pragma warning(suppress : 26434 26477 26487) HRESULT Initialize( _In_ const CConnection& connection, _In_z_ LPCWSTR pwszObjectName, _In_opt_z_ LPCWSTR pwszVerb = nullptr, _In_opt_z_ LPCWSTR pwszVersion = nullptr, _In_opt_z_ LPCWSTR pwszReferrer = WINHTTP_NO_REFERER, _In_opt_ LPCWSTR* ppwszAcceptTypes = WINHTTP_DEFAULT_ACCEPT_TYPES, _In_ DWORD dwFlags = 0, _In_ DWORD dwBufferLength = 8096, _In_ DWORD dwShareMode = 0) { // Initialize the critical section HRESULT hr{m_cs.Init()}; if (FAILED(hr)) return hr; // Let the base class do its thing hr = __super::Initialize(connection, pwszObjectName, pwszVerb, pwszVersion, pwszReferrer, ppwszAcceptTypes, dwFlags); if (FAILED(hr)) return hr; // Disable redirects if required if (m_bNoURLRedirect) { DWORD dwOptionValue{WINHTTP_DISABLE_REDIRECTS}; hr = SetOption(WINHTTP_DISABLE_REDIRECTS, &dwOptionValue, sizeof(dwOptionValue)); if (FAILED(hr)) return hr; } // Hook up the callback function if (SetStatusCallback() == WINHTTP_INVALID_STATUS_CALLBACK) return ATL::AtlHresultFromLastError(); // Release our resources if currently in use ReleaseResources(); // Serialize access to our member variables ATL::CCritSecLock sl{m_cs.m_sec, true}; m_bUsingObjectStatusCallback = true; // Allocate the receive buffer m_ReadBuffer.resize(dwBufferLength); // Open up the file for downloading if necessary if (m_sFileToDownloadInto.length()) { #ifdef _UNICODE hr = m_fileToDownloadInto.Create(m_sFileToDownloadInto.c_str(), GENERIC_WRITE, dwShareMode, OPEN_ALWAYS); #else hr = m_fileToDownloadInto.Create( ATL::CW2T(m_sFileToDownloadInto.c_str()), GENERIC_WRITE, dwShareMode, OPEN_ALWAYS); #endif // #ifdef _UNICODE if (FAILED(hr)) return hr; // Seek to the start position of the download hr = m_fileToDownloadInto.Seek(m_nDownloadStartPos, FILE_BEGIN); if (FAILED(hr)) return hr; hr = m_fileToDownloadInto.SetSize(m_nDownloadStartPos); if (FAILED(hr)) return hr; } // Also open up the file to upload if necessary if (m_sFileToUpload.length()) { // Allocate the send buffer m_WriteBuffer.resize(dwBufferLength); // Open up the file for downloading into #ifdef _UNICODE hr = m_fileToUpload.Create(m_sFileToUpload.c_str(), GENERIC_READ, dwShareMode, OPEN_EXISTING); #else hr = m_fileToUpload.Create(ATL::CW2T(m_sFileToUpload.c_str()), GENERIC_READ, dwShareMode, OPEN_EXISTING); #endif // #ifdef _UNICODE if (FAILED(hr)) return hr; // Remember the size of the file to upload hr = m_fileToUpload.GetSize(m_nFileToUploadSize); if (FAILED(hr)) return hr; } return S_OK; } void ReleaseResources() { // Serialize access to our member variables ATL::CCritSecLock sl{m_cs.m_sec, true}; // Ensure the files are closed and the various buffers are deleted m_fileToDownloadInto.Close(); m_ReadBuffer.clear(); m_fileToUpload.Close(); m_WriteBuffer.clear(); m_nFileToUploadSize = 0; m_nFileToUploadIndex = 0; m_pOptionalBuffer = nullptr; m_dwOptionalBufferLength = 0; m_lpRequest = nullptr; m_dwRequestSize = 0; } #pragma warning(suppress : 26165) HRESULT DeleteDownloadedFile() { // What will be the return value from this method (assume the best) HRESULT hr{S_OK}; // Serialize access to our member variables ATL::CCritSecLock sl{m_cs.m_sec, true}; // Delete the partially downloaded file if unsuccessful #ifdef _UNICODE if (m_sFileToDownloadInto.length() && !DeleteFile(m_sFileToDownloadInto.c_str())) #else if (m_sFileToDownloadInto.length() && !DeleteFile(ATL::CW2T(m_sFileToDownloadInto.c_str()))) #endif // #ifdef _UNICODE hr = ATL::AtlHresultFromLastError(); return hr; } [[nodiscard]] ULONGLONG TimeSinceStartDownload() const noexcept { return GetTickCount64() - m_dwStartTicksDownload; } [[nodiscard]] virtual String GetHeaders() { // What will be the return value from this method String sHeaders; // Create the Range header here if required if (m_nDownloadStartPos != 0) // we will build the range request { ATL::CAtlStringW sHeader; sHeader.Format(L"Range: bytes=%I64u-\r\n", m_nDownloadStartPos); sHeaders += sHeader; } // Update the content length if we have a file to upload if (m_fileToUpload.operator HANDLE()) { ATL::CAtlStringW sHeader; sHeader.Format(L"Content-Length: %I64u\r\n", m_nFileToUploadSize); sHeaders += sHeader; } return sHeaders; } [[nodiscard]] virtual DWORD GetContentLength() noexcept { if (m_fileToUpload.operator HANDLE()) { if (m_nFileToUploadSize > UINT_MAX) return WINHTTP_IGNORE_REQUEST_TOTAL_LENGTH; else { #pragma warning(suppress : 26472) return static_cast(m_nFileToUploadSize); } } else return m_dwRequestSize; } [[nodiscard]] DWORD GetLastStatusCode(_Out_ bool& bValid) noexcept { bValid = m_dwStatusCode.has_value(); if (bValid) return m_dwStatusCode.value(); else return 0; } #pragma warning(suppress : 26434 26440) HRESULT SendRequest(_In_reads_bytes_opt_(dwOptionalLength) LPVOID lpOptional, _In_ DWORD dwOptionalLength, _In_ const String& sHeaders) { // Remember the parameters passed in case we need to resend the request m_dwOptionalBufferLength = dwOptionalLength; m_pOptionalBuffer = lpOptional; // Do preauthentication if required m_dwProxyAuthScheme = 0; if (m_bProxyPreauthentication) { const HRESULT hr{DoAuthentication(m_dwProxyPreauthenticationScheme, 0, WINHTTP_AUTH_TARGET_PROXY)}; if (FAILED(hr)) return hr; } if (m_bHTTPPreauthentication) { const HRESULT hr{DoAuthentication(m_dwHTTPPreauthenticationScheme, 0, WINHTTP_AUTH_TARGET_SERVER)}; if (FAILED(hr)) return hr; } // Reset the total bytes read in the response m_nTotalBytesRead = 0; // Reset the response buffer m_Response.clear(); // Remember the time we started the download at m_dwStartTicksDownload = GetTickCount64(); // Reset the last status code m_dwStatusCode.reset(); // Reset the content length header m_nContentLength.reset(); #pragma warning(suppress : 26472) const auto dwHeadersLength{static_cast(sHeaders.length())}; #pragma warning(suppress : 26477 26490) return __super::SendRequest( dwHeadersLength ? sHeaders.c_str() : WINHTTP_NO_ADDITIONAL_HEADERS, dwHeadersLength, lpOptional, dwOptionalLength, dwOptionalLength, reinterpret_cast(this)); } #pragma warning(suppress : 26434 26477) HRESULT SendRequest(_In_reads_bytes_opt_(dwOptionalLength) LPVOID lpOptional = WINHTTP_NO_REQUEST_DATA, _In_ DWORD dwOptionalLength = 0) { // Delegate to the other version of this method return SendRequest(lpOptional, dwOptionalLength, GetHeaders()); } virtual HRESULT DoAuthentication(_In_ DWORD dwAuthenticationScheme, _In_ DWORD /*dwFirstScheme*/, _In_ DWORD dwAuthTarget) noexcept { // What will be the return value from this method HRESULT hr{S_FALSE}; switch (dwAuthTarget) { case WINHTTP_AUTH_TARGET_SERVER: { if (m_sHTTPUserName.length()) hr = SetCredentials(dwAuthTarget, dwAuthenticationScheme, m_sHTTPUserName.c_str(), m_sHTTPPassword.c_str()); break; } case WINHTTP_AUTH_TARGET_PROXY: { if (m_sProxyUserName.length()) hr = SetCredentials(dwAuthTarget, dwAuthenticationScheme, m_sProxyUserName.c_str(), m_sProxyPassword.c_str()); break; } default: { hr = E_UNEXPECTED; break; } } return hr; } [[nodiscard]] virtual DWORD ChooseAuthScheme( _In_ DWORD dwSupportedSchemes, _In_ DWORD /*dwFirstScheme*/, _In_ DWORD /*dwAuthTarget*/) noexcept { // This default implementation will allow any authentication scheme // support and will pick in order of "decreasing strength" if (dwSupportedSchemes & WINHTTP_AUTH_SCHEME_NEGOTIATE) return WINHTTP_AUTH_SCHEME_NEGOTIATE; else if (dwSupportedSchemes & WINHTTP_AUTH_SCHEME_NTLM) return WINHTTP_AUTH_SCHEME_NTLM; else if (dwSupportedSchemes & WINHTTP_AUTH_SCHEME_PASSPORT) return WINHTTP_AUTH_SCHEME_PASSPORT; else if (dwSupportedSchemes & WINHTTP_AUTH_SCHEME_DIGEST) return WINHTTP_AUTH_SCHEME_DIGEST; else if (dwSupportedSchemes & WINHTTP_AUTH_SCHEME_BASIC) return WINHTTP_AUTH_SCHEME_BASIC; else return 0; } // Member variables String m_sHTTPUserName; // The username to use for HTTP authentication String m_sHTTPPassword; // the password to use for HTTP authentication String m_sProxyUserName; // The username to use for Proxy authentication String m_sProxyPassword; // The password to use for Proxy authentication DWORD m_dwProxyPreauthenticationScheme; // The authentication scheme to use // for proxy preauthentication DWORD m_dwHTTPPreauthenticationScheme; // The authentication scheme to use // for HTTP server preauthentication bool m_bProxyPreauthentication; // Should we supply credentials on the // first request for the Proxy rather than // starting out with anonymous credentials // and only authenticating when challenged bool m_bHTTPPreauthentication; // Should we supply credentials on the first // request for the HTTP Server rather than // starting out with anonymous credentials // and only authenticating when challenged ULONGLONG m_nDownloadStartPos; // Offset to resume the download at bool m_bNoURLRedirect; // Set to true if you want to disable URL // redirection following String m_sFileToUpload; // The path of the file to upload String m_sFileToDownloadInto; // The path of the file to download into ByteArray m_Response; // The in memory copy of the HTTP response LPCVOID m_lpRequest; // The in memory data to send in the HTTP request DWORD m_dwRequestSize; // The size in bytes of m_lpRequest double m_dbLimit; // For bandwidth throttling, The value in KB/Second to // limit the connection to std::vector m_ReadBuffer; // The buffer we will read into ATL::CAtlFile m_fileToDownloadInto; // The file we will download into std::vector m_WriteBuffer; // The buffer we will write from ATL::CAtlFile m_fileToUpload; // The file we will upload ULONGLONG m_nFileToUploadSize; // The size of the file to upload ULONGLONG m_nFileToUploadIndex; // The current index of the file upload writing ATL::CComCriticalSection m_cs; // Used to serialize access to our member variables std::optional m_nContentLength; // The content length header value retrieved LPVOID m_pOptionalBuffer; // A pointer to the optional data DWORD m_dwOptionalBufferLength; // The size in bytes of "m_pOptionalBuffer" DWORD m_dwProxyAuthScheme; // The auth scheme used for the last proxy // authentication ULONGLONG m_nTotalBytesRead; // The total bytes read in the current // response ULONGLONG m_dwStartTicksDownload; // The Tick count when we started the download bool m_bUsingObjectStatusCallback; // true if SetStatusCallback has been // called to setup CHandle::_Callback as // the WinHTTP callback std::optional m_dwStatusCode; // The last status code retrieved protected: // Methods HRESULT OnCallback(_In_ HINTERNET hInternet, _In_ DWORD dwInternetStatus, _In_opt_ LPVOID lpvStatusInformation, _In_ DWORD dwStatusInformationLength) override { // What will be the return value from this function (S_FALSE means not // handled in our callback) HRESULT hr{S_FALSE}; switch (dwInternetStatus) { case WINHTTP_CALLBACK_STATUS_WRITE_COMPLETE: // deliberate // fallthrough case WINHTTP_CALLBACK_STATUS_SENDREQUEST_COMPLETE: { hr = OnWriteCallback(hInternet, dwInternetStatus, lpvStatusInformation, dwStatusInformationLength); break; } case WINHTTP_CALLBACK_STATUS_HEADERS_AVAILABLE: { hr = OnHeadersAvailableCallback(hInternet, dwInternetStatus, lpvStatusInformation, dwStatusInformationLength); break; } case WINHTTP_CALLBACK_STATUS_READ_COMPLETE: { hr = OnReadCompleteCallback(hInternet, dwInternetStatus, lpvStatusInformation, dwStatusInformationLength); break; } case WINHTTP_CALLBACK_STATUS_REQUEST_ERROR: { hr = OnRequestErrorCallback(hInternet, dwInternetStatus, lpvStatusInformation, dwStatusInformationLength); break; } default: { break; } } return hr; } virtual HRESULT OnReadCompleteCallback(_In_ HINTERNET hInternet, _In_ DWORD dwInternetStatus, _In_opt_ LPVOID lpvStatusInformation, _In_ DWORD dwStatusInformationLength) { // What will be the return value from the function HRESULT hr{E_UNEXPECTED}; if (dwStatusInformationLength > 0) { // Serialize access to our member variables ATL::CCritSecLock sl{m_cs.m_sec, true}; // Call the virtual OnReadData method if we have received some // response data hr = OnReadData(m_ReadBuffer.data(), dwStatusInformationLength); if (FAILED(hr)) return hr; // Continue to read the HTTP response #pragma warning(suppress : 26472) hr = ReadData(m_ReadBuffer.data(), static_cast(m_ReadBuffer.size()), nullptr); if (FAILED(hr)) return hr; } else { // A dwStatusInformationLength of 0 indicates that the response is // complete, call the OnResponseComplete method to indicate that the // download is complete hr = OnCallbackComplete(S_OK, hInternet, dwInternetStatus, lpvStatusInformation, dwStatusInformationLength); } return hr; } virtual HRESULT OnWriteCallback(_In_ HINTERNET hInternet, _In_ DWORD dwInternetStatus, _In_opt_ LPVOID lpvStatusInformation, _In_ DWORD dwStatusInformationLength) { UNREFERENCED_PARAMETER(hInternet); UNREFERENCED_PARAMETER(dwInternetStatus); UNREFERENCED_PARAMETER(lpvStatusInformation); UNREFERENCED_PARAMETER(dwStatusInformationLength); // Call the virtual OnWriteData method to allow this class a chance to // send additional request data HRESULT hr{OnWriteData()}; if (FAILED(hr)) return hr; // When the request was sent successfully, lets kick off reading the // response if (hr == S_FALSE) hr = ReceiveResponse(); return hr; } virtual HRESULT On407Response() { // Serialize access to our member variables ATL::CCritSecLock sl{m_cs.m_sec, true}; // Check what authentication schemes the server supports DWORD dwSupportedSchemes{0}; DWORD dwFirstScheme{0}; DWORD dwAuthTarget{0}; HRESULT hr{ QueryAuthSchemes(dwSupportedSchemes, dwFirstScheme, dwAuthTarget)}; if (FAILED(hr)) return hr; // Pick an authentication scheme m_dwProxyAuthScheme = ChooseAuthScheme(dwSupportedSchemes, dwFirstScheme, dwAuthTarget); if (m_dwProxyAuthScheme == 0) return ATL::AtlHresultFromWin32(ERROR_WINHTTP_LOGIN_FAILURE); // Do the authentication hr = DoAuthentication(m_dwProxyAuthScheme, dwFirstScheme, dwAuthTarget); if (FAILED(hr)) return hr; // Form the headers we are sending String sHeaders{GetHeaders()}; #pragma warning(suppress : 26472) const auto dwHeadersLength{static_cast(sHeaders.length())}; // Call the base class using the this pointer as the context value #pragma warning(suppress : 26477 26490) return __super::SendRequest( dwHeadersLength ? sHeaders.c_str() : WINHTTP_NO_ADDITIONAL_HEADERS, dwHeadersLength, m_pOptionalBuffer, m_dwOptionalBufferLength, GetContentLength(), reinterpret_cast(this)); } virtual HRESULT On401Response() { // Serialize access to our member variables ATL::CCritSecLock sl{m_cs.m_sec, true}; // Check what authentication schemes the server supports DWORD dwSupportedSchemes{0}; DWORD dwFirstScheme{0}; DWORD dwAuthTarget{0}; HRESULT hr{ QueryAuthSchemes(dwSupportedSchemes, dwFirstScheme, dwAuthTarget)}; if (FAILED(hr)) return hr; // Pick an authentication scheme const DWORD dwAuthenticationScheme{ ChooseAuthScheme(dwSupportedSchemes, dwFirstScheme, dwAuthTarget)}; if (dwAuthenticationScheme == 0) return ATL::AtlHresultFromWin32(ERROR_WINHTTP_LOGIN_FAILURE); // Do the authentication hr = DoAuthentication(dwAuthenticationScheme, dwFirstScheme, dwAuthTarget); if (FAILED(hr)) return hr; // Resend the Proxy authentication details also if used before, // otherwise we could end up in a 407-401-407-401 loop if (m_dwProxyAuthScheme != 0) { // Do the authentication hr = DoAuthentication(m_dwProxyAuthScheme, 0, WINHTTP_AUTH_TARGET_PROXY); if (FAILED(hr)) return hr; } // Form the headers we are sending String sHeaders{GetHeaders()}; #pragma warning(suppress : 26472) const auto dwHeadersLength{static_cast(sHeaders.length())}; // Call the base class using the this pointer as the context value #pragma warning(suppress : 26477 26490) return __super::SendRequest( dwHeadersLength ? sHeaders.c_str() : WINHTTP_NO_ADDITIONAL_HEADERS, dwHeadersLength, m_pOptionalBuffer, m_dwOptionalBufferLength, GetContentLength(), reinterpret_cast(this)); } HRESULT QueryStatusCode(_Out_ DWORD& dwStatusCode) noexcept { dwStatusCode = 0; DWORD dwStatusCodeSize{sizeof(dwStatusCode)}; #pragma warning(suppress : 26477) return QueryHeaders( WINHTTP_QUERY_STATUS_CODE | WINHTTP_QUERY_FLAG_NUMBER, WINHTTP_HEADER_NAME_BY_INDEX, &dwStatusCode, dwStatusCodeSize, WINHTTP_NO_HEADER_INDEX); } HRESULT QueryContentLength(_Out_ LONGLONG& nContentLength) { // First call to get the size of the buffer we need ATL::CAtlStringW sContentLength; DWORD dwBufferSize{32}; #pragma warning(suppress : 26477) const HRESULT hr{QueryHeaders( WINHTTP_QUERY_CONTENT_LENGTH, WINHTTP_HEADER_NAME_BY_INDEX, sContentLength.GetBufferSetLength(dwBufferSize), dwBufferSize, WINHTTP_NO_HEADER_INDEX)}; sContentLength.ReleaseBuffer(); if (FAILED(hr)) return hr; // Update the output parameter nContentLength = _wtoi64(sContentLength); return hr; } virtual HRESULT OnCheckStatusCode() { // Check what status code we have got const DWORD dwStatusCode = m_dwStatusCode.value(); if (dwStatusCode == HTTP_STATUS_PROXY_AUTH_REQ) return On407Response(); else if (dwStatusCode == HTTP_STATUS_DENIED) return On401Response(); else if ((dwStatusCode / 100) != 2) // Any 2XX is Success return ATL::AtlHresultFromWin32(ERROR_WINHTTP_INVALID_HEADER); return S_OK; } virtual HRESULT OnHeadersAvailableCallback( _In_ HINTERNET hInternet, _In_ DWORD dwInternetStatus, _In_opt_ LPVOID lpvStatusInformation, _In_ DWORD dwStatusInformationLength) { UNREFERENCED_PARAMETER(hInternet); UNREFERENCED_PARAMETER(dwInternetStatus); UNREFERENCED_PARAMETER(lpvStatusInformation); UNREFERENCED_PARAMETER(dwStatusInformationLength); // Serialize access to our member variables ATL::CCritSecLock sl{m_cs.m_sec, true}; // Get the HTTP status code DWORD dwStatusCode{0}; HRESULT hr{QueryStatusCode(dwStatusCode)}; if (FAILED(hr)) return hr; m_dwStatusCode = dwStatusCode; hr = OnCheckStatusCode(); // Delegate to the other virtual method to // check the HTTP status code if (FAILED(hr)) return hr; // Cache the content length header also if we can LONGLONG nContentLength{0}; hr = QueryContentLength(nContentLength); if (SUCCEEDED(hr)) m_nContentLength = nContentLength; // Lets begin reading the response #pragma warning(suppress : 26472) return ReadData(m_ReadBuffer.data(), static_cast(m_ReadBuffer.size()), nullptr); } virtual HRESULT OnRequestErrorCallback(_In_ HINTERNET hInternet, _In_ DWORD dwInternetStatus, _In_opt_ LPVOID lpvStatusInformation, _In_ DWORD dwStatusInformationLength) { // Pull out the WINHTTP_ASYNC_RESULT const auto pResult{ static_cast(lpvStatusInformation)}; if (pResult == nullptr) { #pragma warning(suppress : 26477) ATLASSERT(FALSE); return E_UNEXPECTED; } // Resend the request if required if (pResult->dwError == ERROR_WINHTTP_RESEND_REQUEST) { // Form the headers we are sending String sHeaders{GetHeaders()}; #pragma warning(suppress : 26472) const auto dwHeadersLength{static_cast(sHeaders.length())}; // Serialize access to our member variables ATL::CCritSecLock sl{m_cs.m_sec, true}; // Call the base class using the this pointer as the context value #pragma warning(suppress : 26477 26490) return __super::SendRequest( dwHeadersLength ? sHeaders.c_str() : WINHTTP_NO_ADDITIONAL_HEADERS, dwHeadersLength, m_pOptionalBuffer, m_dwOptionalBufferLength, GetContentLength(), reinterpret_cast(this)); } // Call the OnCallbackComplete method with the async HRESULT return OnCallbackComplete( ATL::AtlHresultFromWin32(pResult->dwError), hInternet, dwInternetStatus, lpvStatusInformation, dwStatusInformationLength); } #pragma warning(suppress : 26165) virtual HRESULT OnReadData(_In_reads_bytes_(dwBytesRead) const void* lpvBuffer, _In_ DWORD dwBytesRead) { // What will be the return value from this method (assume the best) HRESULT hr{S_OK}; // Serialize access to our member variables ATL::CCritSecLock sl{m_cs.m_sec, true}; // Increment the total number of bytes read m_nTotalBytesRead += dwBytesRead; // Write out the buffer to the download file if it is open if (m_fileToDownloadInto.operator HANDLE()) hr = m_fileToDownloadInto.Write(lpvBuffer, dwBytesRead); else { // Otherwise build up the response in the in-memory response array // Preallocate the response buffer if nothing has been read yet const auto nResponseSize{m_Response.size()}; if (nResponseSize == 0) { if (m_nContentLength.has_value()) #pragma warning(suppress : 26472) m_Response.reserve( static_cast::size_type>( m_nContentLength.value())); else m_Response.reserve(m_ReadBuffer.size()); } // Add the data read to the response array #pragma warning(suppress : 26444 26481) m_Response.insert( m_Response.end(), static_cast(lpvBuffer), static_cast(lpvBuffer) + dwBytesRead); } // Call the method to handle bandwidth throttling DoBandwidthThrottling(); return hr; } virtual void DoBandwidthThrottling() noexcept { // Do the bandwidth throttling if (m_dbLimit > 0) { const auto fTimeSinceStartDownload{ static_cast(TimeSinceStartDownload())}; if (fTimeSinceStartDownload) { const double q{static_cast(m_nTotalBytesRead) / fTimeSinceStartDownload}; if (q > m_dbLimit) #pragma warning(suppress : 26467) Sleep(static_cast( ((q * fTimeSinceStartDownload) / m_dbLimit) - fTimeSinceStartDownload)); } } } virtual HRESULT OnWriteData() { // Serialize access to our member variables ATL::CCritSecLock sl{m_cs.m_sec, true}; // If we have a file to upload? if (m_fileToUpload.operator HANDLE()) { // Read in the next blob of data to write from the upload file DWORD dwBytesRead{0}; #pragma warning(suppress : 26472) HRESULT hr{m_fileToUpload.Read( m_WriteBuffer.data(), static_cast(m_WriteBuffer.size()), dwBytesRead)}; if (FAILED(hr)) return hr; // Write the data to the server hr = WriteData(m_WriteBuffer.data(), dwBytesRead, nullptr); if (FAILED(hr)) return hr; // Update the current position m_nFileToUploadIndex += dwBytesRead; // Return S_FALSE to conclude the writing if we have reached the end // of the file return (m_nFileToUploadIndex >= m_nFileToUploadSize) ? S_FALSE : S_OK; } else { // Upload the in-memory data if specified if (m_dwRequestSize) { #pragma warning(suppress : 26477) ATLASSERT(m_lpRequest != nullptr); // m_pbyRequest should be provided if // m_dwRequestSize is non-zero const HRESULT hr{ WriteData(m_lpRequest, m_dwRequestSize, nullptr)}; if (FAILED(hr)) return hr; } // There's nothing more to upload so return S_FALSE return S_FALSE; } } #ifdef _DEBUG static void TraceCallbackComplete(_In_ HRESULT hr, _In_ HINTERNET hInternet, _In_ DWORD dwInternetStatus, _In_opt_ LPVOID lpvStatusInformation, _In_ DWORD dwStatusInformationLength) { UNREFERENCED_PARAMETER(hInternet); UNREFERENCED_PARAMETER(dwInternetStatus); UNREFERENCED_PARAMETER(lpvStatusInformation); UNREFERENCED_PARAMETER(dwStatusInformationLength); if (hr == S_OK) ATLTRACE( _T("CAsyncWinHttpDownloader::TraceCallbackComplete, The file ") _T("was successfully downloaded\n")); else ATLTRACE( _T("CAsyncWinHttpDownloader::TraceCallbackComplete, The file ") _T("was not downloaded correctly, Error:%08X\n"), hr); } #endif // #ifdef _DEBUG #pragma warning(suppress : 26433) HRESULT OnCallbackComplete(_In_ HRESULT hr, _In_ HINTERNET hInternet, _In_ DWORD dwInternetStatus, _In_opt_ LPVOID lpvStatusInformation, _In_ DWORD dwStatusInformationLength) override { UNREFERENCED_PARAMETER(hInternet); UNREFERENCED_PARAMETER(dwInternetStatus); UNREFERENCED_PARAMETER(lpvStatusInformation); UNREFERENCED_PARAMETER(dwStatusInformationLength); // Delegate the cleanup to the helper method ReleaseResources(); // Delete the file if it was partially downloaded if (hr != S_OK) DeleteDownloadedFile(); return S_OK; } }; // Wrapper for a simple WinHttp sync download class CWINHTTPWRAPPERS_EXT_CLASS CSyncDownloader : public CAsyncDownloader { public: // Methods virtual HRESULT SendRequestSync(_In_reads_bytes_opt_(dwOptionalLength) LPVOID lpOptional, _In_ DWORD dwOptionalLength, _In_ const String& sHeaders) { // Use the base class to send the request initially HRESULT hr{ __super::SendRequest(lpOptional, dwOptionalLength, sHeaders)}; if (FAILED(hr)) { ReleaseResources(); DeleteDownloadedFile(); return hr; } bool bDownloaded{false}; while (!bDownloaded) { // Loop around calling the OnWriteData virtual method until it // returns S_FALSE do { hr = OnWriteData(); } while (SUCCEEDED(hr) && (hr != S_FALSE)); if (FAILED(hr)) { ReleaseResources(); DeleteDownloadedFile(); return hr; } // Wait for the status code and response headers to be received hr = ReceiveResponse(); if (FAILED(hr)) { ReleaseResources(); DeleteDownloadedFile(); return hr; } // Get the HTTP status code DWORD dwStatusCode{0}; hr = QueryStatusCode(dwStatusCode); if (FAILED(hr)) { ReleaseResources(); DeleteDownloadedFile(); return hr; } m_dwStatusCode = dwStatusCode; // Check what to do with the status code we have got if (dwStatusCode == HTTP_STATUS_PROXY_AUTH_REQ) { hr = On407Response(); if (FAILED(hr)) { ReleaseResources(); DeleteDownloadedFile(); return hr; } continue; } else if (dwStatusCode == HTTP_STATUS_DENIED) { hr = On401Response(); if (FAILED(hr)) { ReleaseResources(); DeleteDownloadedFile(); return hr; } continue; } else { hr = OnCheckStatusCode(); if (FAILED(hr)) { ReleaseResources(); DeleteDownloadedFile(); return hr; } } // Cache the content length header if we can LONGLONG nContentLength{0}; hr = QueryContentLength(nContentLength); if (SUCCEEDED(hr)) m_nContentLength = nContentLength; // Loop around reading the response DWORD dwBytesRead{0}; do { #pragma warning(suppress : 26472) hr = ReadData(m_ReadBuffer.data(), static_cast(m_ReadBuffer.size()), &dwBytesRead); if (FAILED(hr)) { ReleaseResources(); DeleteDownloadedFile(); return hr; } if (dwBytesRead) { // Call the virtual OnReadData method hr = OnReadData(m_ReadBuffer.data(), dwBytesRead); if (FAILED(hr)) { ReleaseResources(); DeleteDownloadedFile(); return hr; } } } while (dwBytesRead); bDownloaded = true; } ReleaseResources(); return S_OK; } #pragma warning(suppress : 26477) virtual HRESULT SendRequestSync(_In_reads_bytes_opt_(dwOptionalLength) LPVOID lpOptional = WINHTTP_NO_REQUEST_DATA, _In_ DWORD dwOptionalLength = 0) { // Delegate to the other version of this method return SendRequestSync(lpOptional, dwOptionalLength, GetHeaders()); } }; }; // namespace WinHTTPWrappers #endif // #ifndef __WINHTTPWRAPPERS_H__ ================================================ FILE: src/windhawk/app/logger.cpp ================================================ #include "stdafx.h" #include "logger.h" #include "storage_manager.h" namespace { Logger::Verbosity GetVerbosityFromConfig() { try { auto settings = StorageManager::GetInstance().GetAppConfig(L"Settings", false); int verbosity = settings->GetInt(L"LoggingVerbosity").value_or(0); switch (verbosity) { case static_cast(Logger::Verbosity::kOff): return Logger::Verbosity::kOff; case static_cast(Logger::Verbosity::kOn): return Logger::Verbosity::kOn; case static_cast(Logger::Verbosity::kVerbose): return Logger::Verbosity::kVerbose; } } catch (const std::exception&) { // Ignore and use default settings. We can't log it, anyway. } return Logger::kDefaultVerbosity; } } // namespace Logger::Logger(Verbosity initialVerbosity) : LoggerBase(initialVerbosity) {} // static Logger& Logger::GetInstance() { static Logger s(GetVerbosityFromConfig()); return s; } ================================================ FILE: src/windhawk/app/logger.h ================================================ #pragma once #include "logger_base.h" class Logger : public LoggerBase { public: Logger(Verbosity initialVerbosity); static Logger& GetInstance(); }; #define LOG_WITH_VERBOSITY(verbosity, message, ...) \ do { \ auto& inst = Logger::GetInstance(); \ if (inst.GetVerbosity() >= verbosity) { \ inst.LogLine(L"[WH] [%S]: " message L"\n", __FUNCTION__, \ __VA_ARGS__); \ } \ } while (0) #define LOG(message, ...) \ LOG_WITH_VERBOSITY(Logger::Verbosity::kOn, message, __VA_ARGS__) #define VERBOSE(message, ...) \ LOG_WITH_VERBOSITY(Logger::Verbosity::kVerbose, message, __VA_ARGS__) ================================================ FILE: src/windhawk/app/main_window.cpp ================================================ #include "stdafx.h" #include "main_window.h" #include "functions.h" #include "logger.h" #include "resource.h" #include "ui_control.h" #include "version.h" namespace { constexpr auto kHandleNewProcessInterval = 1000; // 1sec constexpr auto kUpdateInitialDelay = 1000 * 10; // 10sec constexpr auto kUpdateInterval = 1000 * 60 * 60 * 24; // 24h constexpr auto kUpdateRetryTime = 1000 * 60 * 60; // 1h constexpr auto kModTasksDlgInitialDelay = 1000; // 1sec ULONGLONG GetTaskbarProcessCreationTime() { HWND currentTaskbarWindow = FindWindow(L"Shell_TrayWnd", nullptr); if (!currentTaskbarWindow) { return 0; } DWORD currentTaskbarProcessId; if (!GetWindowThreadProcessId(currentTaskbarWindow, ¤tTaskbarProcessId)) { return 0; } wil::unique_process_handle currentTaskbarProcess(OpenProcess( PROCESS_QUERY_LIMITED_INFORMATION, FALSE, currentTaskbarProcessId)); if (!currentTaskbarProcess) { return 0; } FILETIME creationTime; FILETIME exitTime; FILETIME kernelTime; FILETIME userTime; if (!GetProcessTimes(currentTaskbarProcess.get(), &creationTime, &exitTime, &kernelTime, &userTime)) { return 0; } return wil::filetime::to_int64(creationTime); } } // namespace CMainWindow::CMainWindow(bool trayOnly, bool portable) : m_trayOnly(trayOnly), m_portable(portable), m_taskbarCreatedMsg(RegisterWindowMessage(L"TaskbarCreated")) {} BOOL CMainWindow::PreTranslateMessage(MSG* pMsg) { if (m_modTasksDlg && m_modTasksDlg->IsDialogMessage(pMsg)) { return TRUE; } if (m_modStatusesDlg && m_modStatusesDlg->IsDialogMessage(pMsg)) { return TRUE; } if (m_toolkitDlg && m_toolkitDlg->IsDialogMessage(pMsg)) { return TRUE; } return FALSE; } BOOL CMainWindow::OnIdle() { enum { kServiceMutex, kAppSettingsChanged, kNewUpdatesFound, kModTasksChanged, kModStatusesChanged, kExplorerCrashed, kMaxHandles, }; HANDLE handleArray[kMaxHandles]; int handleTypes[kMaxHandles]; DWORD handleCount = 0; if (m_serviceMutex) { handleArray[handleCount] = m_serviceMutex.get(); handleTypes[handleCount] = kServiceMutex; handleCount++; } if (m_appSettingsChangedEvent) { handleArray[handleCount] = m_appSettingsChangedEvent.get(); handleTypes[handleCount] = kAppSettingsChanged; handleCount++; } if (m_newUpdatesFoundEvent) { handleArray[handleCount] = m_newUpdatesFoundEvent.get(); handleTypes[handleCount] = kNewUpdatesFound; handleCount++; } if (m_modTasksChangeNotification) { handleArray[handleCount] = m_modTasksChangeNotification->GetHandle(); handleTypes[handleCount] = kModTasksChanged; handleCount++; } if (m_modStatusesChangeNotification) { handleArray[handleCount] = m_modStatusesChangeNotification->GetHandle(); handleTypes[handleCount] = kModStatusesChanged; handleCount++; } if (m_explorerCrashMonitor) { handleArray[handleCount] = m_explorerCrashMonitor->GetEventHandle(); handleTypes[handleCount] = kExplorerCrashed; handleCount++; } if (handleCount > 0) { DWORD nWaitResult = MsgWaitForMultipleObjectsEx(handleCount, handleArray, INFINITE, QS_ALLINPUT, MWMO_INPUTAVAILABLE); if (nWaitResult >= WAIT_OBJECT_0 && nWaitResult < WAIT_OBJECT_0 + handleCount) { switch (handleTypes[nWaitResult - WAIT_OBJECT_0]) { case kServiceMutex: ::ReleaseMutex(m_serviceMutex.get()); Exit(); break; case kAppSettingsChanged: LoadSettings(); break; case kNewUpdatesFound: if (!m_disableUpdateCheck) { NotifyAboutAvailableUpdates( UserProfile::GetUpdateStatus(), true); } break; case kModTasksChanged: if (m_modTasksDlg) { m_modTasksDlg->DataChanged(); try { m_modTasksChangeNotification->ContinueMonitoring(); } catch (const std::exception& e) { LOG(L"Tasks ContinueMonitoring failed: %S", e.what()); m_modTasksChangeNotification.reset(); } } else { // In the common case, there's a short-lived event, such // as mod initialization, that is cleared right away. // Wait a bit before creating a dialog, and only create // it if events still exist. m_modTasksChangeNotification.reset(); SetTimer(Timer::kModTasksDlgCreate, kModTasksDlgInitialDelay); } break; case kModStatusesChanged: if (m_modStatusesDlg) { m_modStatusesDlg->DataChanged(); } try { m_modStatusesChangeNotification->ContinueMonitoring(); } catch (const std::exception& e) { LOG(L"Statuses ContinueMonitoring failed: %S", e.what()); m_modStatusesChangeNotification.reset(); } break; case kExplorerCrashed: { int explorerCrashCount = 0; try { explorerCrashCount = m_explorerCrashMonitor->GetAmountOfNewEvents(); } catch (const std::exception& e) { LOG(L"Explorer crash monitor failed: %S", e.what()); m_explorerCrashMonitor.reset(); break; } if (explorerCrashCount > 0) { try { HandleExplorerCrash(explorerCrashCount); } catch (const std::exception& e) { LOG(L"Explorer crash handling failed: %S", e.what()); } } break; } } } } else { // Just wait for a message to avoid running an infinite loop. MsgWaitForMultipleObjectsEx(0, nullptr, INFINITE, QS_ALLINPUT, MWMO_INPUTAVAILABLE); } return FALSE; } int CMainWindow::OnCreate(LPCREATESTRUCT lpCreateStruct) { // Register object for message filtering and idle updates. CMessageLoop* pLoop = _Module.GetMessageLoop(); ATLASSERT(pLoop != nullptr); pLoop->AddMessageFilter(this); pLoop->AddIdleHandler(this); try { if (m_portable) { InitForPortableVersion(); } else { InitForNonPortableVersion(); } } catch (const std::exception& e) { ::MessageBoxA(nullptr, e.what(), "Could not initialize Windhawk", MB_ICONERROR); return -1; } m_trayIcon.emplace(m_hWnd, UWM_TRAYICON, /*hidden=*/true); m_trayIcon->Create(); LoadSettings(); try { m_modTasksChangeNotification.emplace(L"mod-task"); } catch (const std::exception& e) { LOG(L"Tasks ChangeNotification failed: %S", e.what()); } if (!m_disableToolkitHotkey) { m_toolkitHotkeyRegistered = ::RegisterHotKey(m_hWnd, static_cast(Hotkey::kToolkit), MOD_CONTROL | MOD_WIN | MOD_NOREPEAT, 'W'); if (!m_toolkitHotkeyRegistered) { LOG(L"RegisterHotKey failed: %u", GetLastError()); } } if (!m_trayOnly) { RunUI(); } return 0; } void CMainWindow::OnDestroy() { if (m_toolkitHotkeyRegistered) { ::UnregisterHotKey(m_hWnd, static_cast(Hotkey::kToolkit)); m_toolkitHotkeyRegistered = false; } if (m_trayIcon) { m_trayIcon->Remove(); } // Unregister message filtering and idle updates. CMessageLoop* pLoop = _Module.GetMessageLoop(); ATLASSERT(pLoop != NULL); pLoop->RemoveMessageFilter(this); pLoop->RemoveIdleHandler(this); PostQuitMessage(0); } void CMainWindow::OnHotKey(int nHotKeyID, UINT uModifiers, UINT uVirtKey) { switch (static_cast(nHotKeyID)) { case Hotkey::kToolkit: SetForegroundWindow(GetLastActivePopup()); ShowToolkitDialog(); break; } } void CMainWindow::OnTimer(UINT_PTR nIDEvent) { switch (static_cast(nIDEvent)) { case Timer::kHandleNewProcesses: if (m_engineControl) { m_engineControl->HandleNewProcesses(); } SetTimer(Timer::kHandleNewProcesses, kHandleNewProcessInterval); break; case Timer::kUpdateCheck: KillTimer(Timer::kUpdateCheck); try { m_updateChecker = std::make_unique( m_portable ? UpdateChecker::kFlagPortable : 0, [this] { PostMessage(UWM_UPDATE_CHECKED); }); } catch (const std::exception& e) { LOG(L"UpdateChecker failed: %S", e.what()); SetTimer(Timer::kUpdateCheck, kUpdateRetryTime); } break; case Timer::kModTasksDlgCreate: KillTimer(Timer::kModTasksDlgCreate); try { m_modTasksChangeNotification.emplace(L"mod-task"); if (!CTaskManagerDlg::IsDataSourceEmpty( CTaskManagerDlg::DataSource::kModTask)) { m_modTasksDlg.emplace(CTaskManagerDlg::DialogOptions{ .dataSource = CTaskManagerDlg::DataSource::kModTask, .autonomousMode = true, .autonomousModeShowDelay = m_modTasksDlgDelay, .sessionManagerProcessId = m_serviceInfo.processId, .sessionManagerProcessCreationTime = m_serviceInfo.processCreationTime, .runButtonCallback = [this](HWND hWnd) { RunUI(hWnd); }, .finalMessageCallback = [this](HWND hWnd) { m_modTasksDlg.reset(); }}); if (!m_modTasksDlg->Create(m_hWnd)) { m_modTasksDlg.reset(); } } } catch (const std::exception& e) { LOG(L"%S", e.what()); } break; } } BOOL CMainWindow::OnPowerBroadcast(DWORD dwPowerEvent, DWORD_PTR dwData) { if (dwPowerEvent == PBT_APMRESUMEAUTOMATIC && m_checkForUpdates && !m_updateChecker) { KillTimer(Timer::kUpdateCheck); ULONGLONG lastUpdateCheck; try { auto settings = StorageManager::GetInstance().GetAppConfig(L"Settings", false); lastUpdateCheck = std::wcstoull( settings->GetString(L"LastUpdateCheck").value_or(L"0").c_str(), nullptr, 10); } catch (const std::exception& e) { LOG(L"Getting LastUpdateCheck failed: %S", e.what()); lastUpdateCheck = 0; } SetTimer(Timer::kUpdateCheck, GetNextUpdateDelay(lastUpdateCheck)); } return FALSE; } LRESULT CMainWindow::OnPortableAppCommand(UINT uMsg, WPARAM wParam, LPARAM lParam) { if (!m_portable) { return 0; } switch ((PortableAppCommand)wParam) { case PortableAppCommand::kRunUI: RunUI(); break; case PortableAppCommand::kExit: Exit(); break; } return 0; } LRESULT CMainWindow::OnTrayIcon(UINT uMsg, WPARAM wParam, LPARAM lParam) { enum class Action { kNone, kOpenUI, kOpenUpdatePage, kModTaskManager, kToolkit, kExit, }; auto contextMenuFunc = [this]() { CMenu menu; if (!menu.CreatePopupMenu()) { return Action::kNone; } menu.AppendMenu(MF_STRING, static_cast(Action::kOpenUI), Functions::LoadStrFromRsrc(IDS_TRAY_OPEN)); menu.AppendMenu(MF_SEPARATOR); menu.AppendMenu(MF_STRING, static_cast(Action::kModTaskManager), Functions::LoadStrFromRsrc(IDS_TRAY_LOADED_MODS)); menu.AppendMenu( MF_STRING, static_cast(Action::kToolkit), (std::wstring(Functions::LoadStrFromRsrc(IDS_TRAY_TOOLKIT)) + (m_disableToolkitHotkey ? L"" : L"\tCtrl+Win+W")) .c_str()); menu.AppendMenu(MF_SEPARATOR); menu.AppendMenu(MF_STRING, static_cast(Action::kExit), Functions::LoadStrFromRsrc(IDS_TRAY_EXIT)); CPoint point; GetCursorPos(&point); BOOL result = menu.TrackPopupMenu(TPM_RIGHTBUTTON | TPM_RETURNCMD, point.x, point.y, m_hWnd); return static_cast(result); }; Action action = Action::kNone; switch (m_trayIcon->HandleMsg(wParam, lParam)) { case AppTrayIcon::TrayAction::kDefault: action = Action::kOpenUI; break; case AppTrayIcon::TrayAction::kBalloon: if (m_lastUpdateStatus && m_lastUpdateStatus->appUpdateAvailable) { action = Action::kOpenUpdatePage; } else { action = Action::kOpenUI; } break; case AppTrayIcon::TrayAction::kContextMenu: ::SetForegroundWindow(m_hWnd); action = contextMenuFunc(); break; } switch (action) { case Action::kOpenUI: RunUI(); break; case Action::kOpenUpdatePage: OpenUpdatePage(); break; case Action::kModTaskManager: ShowLoadedModsDialog(); break; case Action::kToolkit: ShowToolkitDialog(); break; case Action::kExit: if (m_portable) { Exit(); } else { StopService(); } break; } return 0; } LRESULT CMainWindow::OnUpdateChecked(UINT uMsg, WPARAM wParam, LPARAM lParam) { UpdateChecker::Result result = m_updateChecker->HandleResponse(); m_updateChecker.reset(); if (m_exitWhenUpdateCheckDone) { DestroyWindow(); return 0; } if (!m_checkForUpdates) { return 0; } if (SUCCEEDED(result.hrError)) { NotifyAboutAvailableUpdates(result.updateStatus); SetLastUpdateTime(); SetTimer(Timer::kUpdateCheck, kUpdateInterval); } else { SetTimer(Timer::kUpdateCheck, kUpdateRetryTime); } return 0; } LRESULT CMainWindow::OnTaskbarCreated(UINT uMsg, WPARAM wParam, LPARAM lParam) { // If the toolkit was never active, close it. // if (m_toolkitDlg && !m_toolkitDlg->WasActive()) { // m_toolkitDlg->Close(); // } // Reload icons since the DPI might have changed. From the documentation: // "On Windows 10, the taskbar also broadcasts this message when the DPI of // the primary display changes." m_trayIcon->UpdateIcons(m_hWnd); m_trayIcon->Create(); // Necessary to apply the newly loaded icon in Windows 11 22H2. m_trayIcon->Modify(); return 0; } UINT_PTR CMainWindow::SetTimer(Timer nIDEvent, UINT nElapse, TIMERPROC lpfnTimer) { return CWindowImpl::SetTimer(static_cast(nIDEvent), nElapse, lpfnTimer); } BOOL CMainWindow::KillTimer(Timer nIDEvent) { return CWindowImpl::KillTimer(static_cast(nIDEvent)); } void CMainWindow::InitForPortableVersion() { auto settings = StorageManager::GetInstance().GetAppConfig(L"Settings", false); if (!settings->GetInt(L"SafeMode").value_or(0)) { m_engineControl.emplace(); m_engineControl->HandleNewProcesses(); } SetTimer(Timer::kHandleNewProcesses, kHandleNewProcessInterval); m_appSettingsChangedEvent.reset(Functions::CreateEventForMediumIntegrity( L"WindhawkAppSettingsChangedEvent-daemon")); FILETIME creationTime; FILETIME exitTime; FILETIME kernelTime; FILETIME userTime; THROW_IF_WIN32_BOOL_FALSE(GetProcessTimes( GetCurrentProcess(), &creationTime, &exitTime, &kernelTime, &userTime)); // For the portable version, there's no service, set app info instead. m_serviceInfo.version = VER_FILE_VERSION_LONG; m_serviceInfo.processId = GetCurrentProcessId(); m_serviceInfo.processCreationTime = wil::filetime::to_int64(creationTime); ::ChangeWindowMessageFilterEx(m_hWnd, UWM_PORTABLE_APP_COMMAND, MSGFLT_ALLOW, nullptr); } void CMainWindow::InitForNonPortableVersion() { m_serviceMutex.reset( OpenMutex(SYNCHRONIZE, FALSE, ServiceCommon::kMutexName)); THROW_LAST_ERROR_IF(!m_serviceMutex); DWORD sessionId; THROW_IF_WIN32_BOOL_FALSE( ProcessIdToSessionId(GetCurrentProcessId(), &sessionId)); std::wstring appSettingsChangedEventName = L"Global\\WindhawkAppSettingsChangedEvent-daemon-session=" + std::to_wstring(sessionId); m_appSettingsChangedEvent.reset(Functions::CreateEventForMediumIntegrity( appSettingsChangedEventName.c_str())); std::wstring newUpdatesFoundEventName = L"Global\\WindhawkNewUpdatesFoundEvent-daemon-session=" + std::to_wstring(sessionId); m_newUpdatesFoundEvent.reset(Functions::CreateEventForMediumIntegrity( newUpdatesFoundEventName.c_str())); wil::unique_handle fileMapping(OpenFileMapping( FILE_MAP_READ, FALSE, ServiceCommon::kInfoFileMappingName)); THROW_LAST_ERROR_IF(!fileMapping); wil::unique_mapview_ptr fileMappingView( reinterpret_cast( MapViewOfFile(fileMapping.get(), FILE_MAP_READ, 0, 0, sizeof(ServiceCommon::ServiceInfo)))); THROW_LAST_ERROR_IF(!fileMappingView); m_serviceInfo = *fileMappingView; if (m_serviceInfo.version != VER_FILE_VERSION_LONG) { LOG(L"Version mismatch, service: %08X, app: %08X", m_serviceInfo.version, VER_FILE_VERSION_LONG); } } void CMainWindow::LoadSettings() { LANGID languageId; bool hideTrayIcon; bool disableUpdateCheck; ULONGLONG lastUpdateCheck; bool dontAutoShowToolkit; bool disableToolkitHotkey; int modTasksDlgDelay; try { auto settings = StorageManager::GetInstance().GetAppConfig(L"Settings", false); languageId = LANGIDFROMLCID(LocaleNameToLCID( settings->GetString(L"Language").value_or(L"en").c_str(), 0)); hideTrayIcon = settings->GetInt(L"HideTrayIcon").value_or(0); disableUpdateCheck = settings->GetInt(L"DisableUpdateCheck").value_or(0); if (m_portable) { lastUpdateCheck = std::wcstoull( settings->GetString(L"LastUpdateCheck").value_or(L"0").c_str(), nullptr, 10); } else { // For the non-portable version, update checking is done by another // process, and we're notified via an event. lastUpdateCheck = 0; } dontAutoShowToolkit = settings->GetInt(L"DontAutoShowToolkit").value_or(0); disableToolkitHotkey = settings->GetInt(L"DisableToolkitHotkey").value_or(0); modTasksDlgDelay = settings->GetInt(L"ModTasksDialogDelay") .value_or(CTaskManagerDlg::kAutonomousModeShowDelayDefault); } catch (const std::exception& e) { ::MessageBoxA(nullptr, e.what(), "Could not load settings", MB_ICONERROR); return; } if (languageId != m_languageId) { ::SetThreadUILanguage( languageId ? languageId : MAKELANGID(LANG_ENGLISH, SUBLANG_ENGLISH_US)); bool languageRightToLeft = Functions::IsRightToLeftLanguage(languageId); ModifyStyleEx(languageRightToLeft ? 0 : WS_EX_LAYOUTRTL, languageRightToLeft ? WS_EX_LAYOUTRTL : 0); if (m_modTasksDlg) { m_modTasksDlg->LoadLanguageStrings(); } if (m_modStatusesDlg) { m_modStatusesDlg->LoadLanguageStrings(); } if (m_toolkitDlg) { m_toolkitDlg->LoadLanguageStrings(); } m_languageId = languageId; } if (hideTrayIcon != m_hideTrayIcon) { m_trayIcon->Hide(hideTrayIcon); m_hideTrayIcon = hideTrayIcon; } if (disableUpdateCheck != m_disableUpdateCheck) { // For the non-portable version, update checking is done by another // process, and we're notified via an event. if (m_portable) { m_checkForUpdates = !disableUpdateCheck; if (m_checkForUpdates) { if (!m_updateChecker) { SetTimer(Timer::kUpdateCheck, GetNextUpdateDelay(lastUpdateCheck)); } } else { if (m_updateChecker) { m_updateChecker->Abort(); } else { KillTimer(Timer::kUpdateCheck); } ResetLastUpdateTime(); } } if (disableUpdateCheck) { NotifyAboutAvailableUpdates(UserProfile::UpdateStatus{}); } else { NotifyAboutAvailableUpdates(UserProfile::GetUpdateStatus(), true); } m_disableUpdateCheck = disableUpdateCheck; } if (dontAutoShowToolkit != m_dontAutoShowToolkit) { if (!dontAutoShowToolkit) { try { auto explorerPath = wil::GetWindowsDirectory() + L"\\explorer.exe"; m_explorerCrashMonitor.emplace(explorerPath); } catch (const std::exception& e) { LOG(L"%S", e.what()); } } else { m_explorerCrashMonitor.reset(); } m_dontAutoShowToolkit = dontAutoShowToolkit; } m_disableToolkitHotkey = disableToolkitHotkey; m_modTasksDlgDelay = modTasksDlgDelay; } void CMainWindow::NotifyAboutAvailableUpdates( UserProfile::UpdateStatus updateStatus, bool alwaysShowUpdateNotification) { m_lastUpdateStatus.emplace(std::move(updateStatus)); if (alwaysShowUpdateNotification || m_lastUpdateStatus->newUpdatesFound) { ShowUpdateNotificationMessage(m_lastUpdateStatus->appUpdateAvailable, m_lastUpdateStatus->modUpdatesAvailable); } MarkAppUpdateAvailable(m_lastUpdateStatus->appUpdateAvailable); } void CMainWindow::Exit() { CloseUI(); if (m_portable) { KillTimer(Timer::kHandleNewProcesses); } if (m_updateChecker) { m_updateChecker->Abort(); m_exitWhenUpdateCheckDone = true; } else { if (m_checkForUpdates) { KillTimer(Timer::kUpdateCheck); } DestroyWindow(); } } void CMainWindow::StopService(HWND hWnd) { struct CALLBACK_STATE { bool showOnTaskbar; bool verificationChecked; bool handlingOkButton; }; CALLBACK_STATE callbackState{ .showOnTaskbar = !hWnd, }; TASKDIALOGCONFIG tdcTaskDialogConfig = {sizeof(TASKDIALOGCONFIG)}; TASKDIALOG_BUTTON tbButtons[2]; tbButtons[0].nButtonID = IDOK; tbButtons[0].pszButtonText = Functions::LoadStrFromRsrc(IDS_EXITDLG_BUTTON_EXIT); tbButtons[1].nButtonID = IDCANCEL; tbButtons[1].pszButtonText = Functions::LoadStrFromRsrc(IDS_EXITDLG_BUTTON_CANCEL); tdcTaskDialogConfig.hwndParent = hWnd ? hWnd : m_hWnd; tdcTaskDialogConfig.hInstance = GetModuleHandle(nullptr); tdcTaskDialogConfig.pszWindowTitle = Functions::LoadStrFromRsrc(IDS_EXITDLG_TITLE); tdcTaskDialogConfig.pszMainIcon = MAKEINTRESOURCE(IDR_MAINFRAME); tdcTaskDialogConfig.pszContent = Functions::LoadStrFromRsrc(IDS_EXITDLG_CONTENT); tdcTaskDialogConfig.cButtons = _countof(tbButtons); tdcTaskDialogConfig.pButtons = tbButtons; tdcTaskDialogConfig.nDefaultButton = IDOK; tdcTaskDialogConfig.pszVerificationText = Functions::LoadStrFromRsrc(IDS_EXITDLG_CHECKBOX_AUTOSTART); tdcTaskDialogConfig.pfCallback = [](HWND hWnd, UINT uNotification, WPARAM wParam, LPARAM lParam, LONG_PTR lpRefData) { auto& callbackState = *reinterpret_cast(lpRefData); CWindow wnd(hWnd); switch (uNotification) { case TDN_DIALOG_CONSTRUCTED: { if (callbackState.showOnTaskbar) { wnd.ModifyStyleEx(0, WS_EX_APPWINDOW); } bool languageRightToLeft = Functions::IsRightToLeftLanguage(GetThreadUILanguage()); wnd.ModifyStyleEx(languageRightToLeft ? 0 : WS_EX_LAYOUTRTL, languageRightToLeft ? WS_EX_LAYOUTRTL : 0); if (!Functions::IsRunAsAdmin()) { wnd.SendMessage(TDM_SET_BUTTON_ELEVATION_REQUIRED_STATE, IDOK, TRUE); } break; } case TDN_VERIFICATION_CLICKED: callbackState.verificationChecked = static_cast(wParam) != FALSE; break; case TDN_BUTTON_CLICKED: switch (wParam) { case IDOK: if (callbackState.handlingOkButton) { return S_FALSE; } callbackState.handlingOkButton = true; auto resetStateFlagOnScopeExit = wil::scope_exit([&callbackState] { callbackState.handlingOkButton = false; }); try { auto modulePath = wil::GetModuleFileName(); PCWSTR commandLine = L"-service-stop"; if (callbackState.verificationChecked) { commandLine = L"-service-stop -also-no-autostart"; } if ((int)(UINT_PTR)ShellExecute( nullptr, L"runas", modulePath.c_str(), commandLine, nullptr, SW_SHOWNORMAL) > 32) { return S_OK; } THROW_LAST_ERROR_IF(GetLastError() != ERROR_CANCELLED); } catch (const std::exception& e) { try { std::string msg = "Exiting failed with the error below. If " "nothing else works, you can choose to " "send an exit signal to the Windhawk " "service. Send exit signal?\n\nError:\n"; msg += e.what(); if (::MessageBoxA(hWnd, msg.c_str(), "Exiting failed", MB_ICONERROR | MB_YESNO | MB_DEFBUTTON2) == IDYES) { wil::unique_event namedEvent(::OpenEvent( EVENT_MODIFY_STATE, FALSE, ServiceCommon:: kEmergencyStopEventName)); THROW_LAST_ERROR_IF_NULL(namedEvent); namedEvent.SetEvent(); } } catch (const std::exception& e) { ::MessageBoxA(hWnd, e.what(), "Error", MB_ICONERROR); } } return S_FALSE; // leave dialog open } break; } return S_OK; }; tdcTaskDialogConfig.lpCallbackData = reinterpret_cast(&callbackState); BOOL bVerificationFlagChecked; ::TaskDialogIndirect(&tdcTaskDialogConfig, nullptr, nullptr, &bVerificationFlagChecked); } void CMainWindow::RunUI(HWND hWnd) { if (!hWnd) { hWnd = m_hWnd; } try { UIControl::RunUIOrBringToFront( hWnd, !m_portable && !Functions::IsRunAsAdmin()); } catch (const std::exception& e) { ::MessageBoxA(hWnd, e.what(), "Could not launch the UI process", MB_ICONERROR); } } void CMainWindow::CloseUI() { try { UIControl::CloseUI(); } catch (const std::exception& e) { LOG(L"CloseUI failed: %S", e.what()); } } void CMainWindow::ShowUpdateNotificationMessage(bool appUpdateAvailable, int modUpdatesAvailable) { WCHAR message[AppTrayIcon::kMaxNotificationTooltipSize] = L""; if (appUpdateAvailable) { if (modUpdatesAvailable == 0) { wcsncpy_s(message, Functions::LoadStrFromRsrc(IDS_NOTIFICATION_UPDATE_APP), _TRUNCATE); } else if (modUpdatesAvailable == 1) { wcsncpy_s( message, Functions::LoadStrFromRsrc(IDS_NOTIFICATION_UPDATE_APP_MOD), _TRUNCATE); } else { _snwprintf_s( message, _TRUNCATE, Functions::LoadStrFromRsrc(IDS_NOTIFICATION_UPDATE_APP_MODS), modUpdatesAvailable); } } else { if (modUpdatesAvailable == 1) { wcsncpy_s(message, Functions::LoadStrFromRsrc(IDS_NOTIFICATION_UPDATE_MOD), _TRUNCATE); } else if (modUpdatesAvailable > 1) { _snwprintf_s( message, _TRUNCATE, Functions::LoadStrFromRsrc(IDS_NOTIFICATION_UPDATE_MODS), modUpdatesAvailable); } } if (*message != L'\0') { m_trayIcon->ShowNotificationMessage(message); } } void CMainWindow::MarkAppUpdateAvailable(bool appUpdateAvailable) { m_trayIcon->SetNotificationIconAndTooltip( appUpdateAvailable ? Functions::LoadStrFromRsrc(IDS_TRAYICON_TOOLTIP_UPDATE) : nullptr); } UINT CMainWindow::GetNextUpdateDelay(ULONGLONG lastUpdateCheck) { if (lastUpdateCheck == 0) { return kUpdateInitialDelay; } ULONGLONG now = wil::filetime::convert_100ns_to_msec( wil::filetime::to_int64(wil::filetime::get_system_time())); ULONGLONG nextUpdateDelay = kUpdateInitialDelay; ULONGLONG nextUpdateTime = lastUpdateCheck + kUpdateInterval; if (nextUpdateTime > now) { nextUpdateDelay = nextUpdateTime - now; if (nextUpdateDelay < kUpdateInitialDelay) { nextUpdateDelay = kUpdateInitialDelay; } else if (nextUpdateDelay > kUpdateInterval) { nextUpdateDelay = kUpdateInterval; } } return static_cast(nextUpdateDelay); } void CMainWindow::SetLastUpdateTime() { ULONGLONG now = wil::filetime::convert_100ns_to_msec( wil::filetime::to_int64(wil::filetime::get_system_time())); try { auto settings = StorageManager::GetInstance().GetAppConfig(L"Settings", true); settings->SetString(L"LastUpdateCheck", std::to_wstring(now).c_str()); } catch (const std::exception& e) { LOG(L"%S", e.what()); } } void CMainWindow::ResetLastUpdateTime() { try { auto settings = StorageManager::GetInstance().GetAppConfig(L"Settings", true); settings->Remove(L"LastUpdateCheck"); } catch (const std::exception& e) { LOG(L"%S", e.what()); } } void CMainWindow::OpenUpdatePage() { PCWSTR url = L"https://windhawk.net/download?version=" VER_FILE_VERSION_WSTR; if ((int)(UINT_PTR)ShellExecute(m_hWnd, nullptr, url, nullptr, nullptr, SW_SHOWNORMAL) <= 32) { MessageBox( L"Could not open the update page, please update Windhawk manually", L"Error", MB_ICONERROR); } } void CMainWindow::ShowLoadedModsDialog() { if (m_modStatusesDlg) { ::SetForegroundWindow(*m_modStatusesDlg); return; } m_modStatusesDlg.emplace(CTaskManagerDlg::DialogOptions{ .dataSource = CTaskManagerDlg::DataSource::kModStatus, .sessionManagerProcessId = m_serviceInfo.processId, .sessionManagerProcessCreationTime = m_serviceInfo.processCreationTime, .runButtonCallback = [this](HWND hWnd) { RunUI(hWnd); }, .finalMessageCallback = [this](HWND hWnd) { m_modStatusesDlg.reset(); m_modStatusesChangeNotification.reset(); }}); if (!m_modStatusesDlg->Create(m_hWnd)) { m_modStatusesDlg.reset(); return; } m_modStatusesDlg->ShowWindow(SW_SHOWNORMAL); try { m_modStatusesChangeNotification.emplace(L"mod-status"); } catch (const std::exception& e) { LOG(L"Statuses ChangeNotification failed: %S", e.what()); } } void CMainWindow::ShowToolkitDialog(bool triggeredBySystemInstability) { if (m_toolkitDlg) { ::SetForegroundWindow(*m_toolkitDlg); return; } bool createInactive = triggeredBySystemInstability; m_toolkitDlg.emplace(CToolkitDlg::DialogOptions{ .createInactive = createInactive, .showTaskbarCrashExplanation = triggeredBySystemInstability, .runButtonCallback = [this](HWND hWnd) { RunUI(hWnd); }, .loadedModsButtonCallback = [this](HWND hWnd) { ShowLoadedModsDialog(); }, .exitButtonCallback = [this](HWND hWnd) { if (m_portable) { Exit(); } else { StopService(hWnd); } }, .safeModeButtonCallback = [this](HWND hWnd) { if (::MessageBox( hWnd, Functions::LoadStrFromRsrc(IDS_SAFE_MODE_TEXT), Functions::LoadStrFromRsrc(IDS_SAFE_MODE_TITLE), MB_ICONWARNING | MB_OKCANCEL | MB_DEFBUTTON2) == IDOK) { try { SwitchToSafeMode(); } catch (const std::exception& e) { ::MessageBoxA(hWnd, e.what(), "Error", MB_ICONERROR); } } }, .finalMessageCallback = [this](HWND hWnd) { m_toolkitDlg.reset(); }}); if (!m_toolkitDlg->Create(m_hWnd)) { m_toolkitDlg.reset(); return; } m_toolkitDlg->ShowWindow(createInactive ? SW_SHOWNOACTIVATE : SW_SHOWNORMAL); } void CMainWindow::SwitchToSafeMode() { try { auto modulePath = wil::GetModuleFileName(); std::wstring commandLine = L"\"" + modulePath + L"\" -wait"; STARTUPINFO si = {sizeof(STARTUPINFO)}; wil::unique_process_information process; THROW_IF_WIN32_BOOL_FALSE(CreateProcess( modulePath.c_str(), commandLine.data(), nullptr, nullptr, FALSE, NORMAL_PRIORITY_CLASS, nullptr, nullptr, &si, &process)); } catch (const std::exception& e) { LOG(L"%S", e.what()); } if (m_portable) { auto settings = StorageManager::GetInstance().GetAppConfig(L"Settings", true); settings->SetInt(L"SafeMode", 1); Exit(); } else { wil::unique_event namedEvent(::OpenEvent( EVENT_MODIFY_STATE, FALSE, ServiceCommon::kSafeModeStopEventName)); THROW_LAST_ERROR_IF_NULL(namedEvent); namedEvent.SetEvent(); } } void CMainWindow::HandleExplorerCrash(int explorerCrashCount) { VERBOSE(L"Detected %d explorer crashes", explorerCrashCount); ULONGLONG currentTickCount = GetTickCount64(); if (explorerCrashCount >= 2 || currentTickCount - m_explorerLastTerminatedTickCount <= kExplorerSecondCrashMaxPeriod) { bool skipShowingToolkit = false; ULONGLONG taskbarProcessCreationTime = GetTaskbarProcessCreationTime(); if (taskbarProcessCreationTime) { ULONGLONG currentTime = wil::filetime::to_int64(wil::filetime::get_system_time()); ULONGLONG msSinceCreationTime = wil::filetime::convert_100ns_to_msec( currentTime - taskbarProcessCreationTime); if (msSinceCreationTime > kExplorerSecondCrashMaxPeriod) { VERBOSE( L"Taskbar process created %u ms ago, not showing toolkit", msSinceCreationTime); skipShowingToolkit = true; } } if (!skipShowingToolkit && !m_toolkitDlg) { ShowToolkitDialog(/*triggeredBySystemInstability=*/true); } } m_explorerLastTerminatedTickCount = currentTickCount; } ================================================ FILE: src/windhawk/app/main_window.h ================================================ #pragma once #include "engine_control.h" #include "event_viewer_crash_monitor.h" #include "service_common.h" #include "storage_manager.h" #include "task_manager_dlg.h" #include "toolkit_dlg.h" #include "tray_icon.h" #include "update_checker.h" #include "userprofile.h" class CMainWindow : public CWindowImpl, public CMessageFilter, public CIdleHandler { public: DECLARE_WND_CLASS(L"WindhawkDaemon") // Custom messages. enum { UWM_PORTABLE_APP_COMMAND = WM_APP, UWM_TRAYICON, UWM_UPDATE_CHECKED, }; enum class PortableAppCommand { kRunUI = 1, kExit, }; CMainWindow(bool trayOnly, bool portable); private: enum class Timer { kHandleNewProcesses = 1, kUpdateCheck, kModTasksDlgCreate, }; enum class Hotkey { kToolkit = 1, }; BOOL PreTranslateMessage(MSG* pMsg) override; BOOL OnIdle() override; BEGIN_MSG_MAP_EX(CMainWindow) MSG_WM_CREATE(OnCreate) MSG_WM_DESTROY(OnDestroy) MSG_WM_HOTKEY(OnHotKey) MSG_WM_TIMER(OnTimer) MSG_WM_POWERBROADCAST(OnPowerBroadcast) MESSAGE_HANDLER_EX(UWM_PORTABLE_APP_COMMAND, OnPortableAppCommand) MESSAGE_HANDLER_EX(UWM_TRAYICON, OnTrayIcon) MESSAGE_HANDLER_EX(UWM_UPDATE_CHECKED, OnUpdateChecked) MESSAGE_HANDLER_EX(m_taskbarCreatedMsg, OnTaskbarCreated) END_MSG_MAP() int OnCreate(LPCREATESTRUCT lpCreateStruct); void OnDestroy(); void OnHotKey(int nHotKeyID, UINT uModifiers, UINT uVirtKey); void OnTimer(UINT_PTR nIDEvent); BOOL OnPowerBroadcast(DWORD dwPowerEvent, DWORD_PTR dwData); LRESULT OnPortableAppCommand(UINT uMsg, WPARAM wParam, LPARAM lParam); LRESULT OnTrayIcon(UINT uMsg, WPARAM wParam, LPARAM lParam); LRESULT OnUpdateChecked(UINT uMsg, WPARAM wParam, LPARAM lParam); LRESULT OnTaskbarCreated(UINT uMsg, WPARAM wParam, LPARAM lParam); UINT_PTR SetTimer(Timer nIDEvent, UINT nElapse, TIMERPROC lpfnTimer = nullptr); BOOL KillTimer(Timer nIDEvent); void InitForPortableVersion(); void InitForNonPortableVersion(); void LoadSettings(); void NotifyAboutAvailableUpdates(UserProfile::UpdateStatus updateStatus, bool alwaysShowUpdateNotification = false); void Exit(); void StopService(HWND hWnd = nullptr); void RunUI(HWND hWnd = nullptr); void CloseUI(); void ShowUpdateNotificationMessage(bool appUpdateAvailable, int modUpdatesAvailable); void MarkAppUpdateAvailable(bool appUpdateAvailable); UINT GetNextUpdateDelay(ULONGLONG lastUpdateCheck); void SetLastUpdateTime(); void ResetLastUpdateTime(); void OpenUpdatePage(); void ShowLoadedModsDialog(); void ShowToolkitDialog(bool triggeredBySystemInstability = false); void SwitchToSafeMode(); void HandleExplorerCrash(int explorerCrashCount); bool m_trayOnly; bool m_portable; UINT m_taskbarCreatedMsg; wil::unique_mutex_nothrow m_serviceMutex; wil::unique_event_nothrow m_appSettingsChangedEvent; wil::unique_event_nothrow m_newUpdatesFoundEvent; std::optional m_trayIcon; ServiceCommon::ServiceInfo m_serviceInfo{}; std::optional m_engineControl; std::unique_ptr m_updateChecker; bool m_exitWhenUpdateCheckDone = false; std::optional m_lastUpdateStatus; bool m_toolkitHotkeyRegistered = false; // Settings. LANGID m_languageId = 0; bool m_hideTrayIcon = true; bool m_disableUpdateCheck = true; bool m_checkForUpdates = false; // portable version only bool m_dontAutoShowToolkit = true; bool m_disableToolkitHotkey = false; int m_modTasksDlgDelay = CTaskManagerDlg::kAutonomousModeShowDelayDefault; // Shown automatically when mods are doing tasks such as initializing or // loading symbols. std::optional m_modTasksDlg; std::optional m_modTasksChangeNotification; // Opened by the user. std::optional m_modStatusesDlg; std::optional m_modStatusesChangeNotification; // Opened from the tray icon, with a hotkey, or when explorer isn't running. std::optional m_toolkitDlg; // Explorer instability monitoring. Instability is detected when explorer // terminates more than once in a short period of time. constexpr static UINT kExplorerSecondCrashMaxPeriod = 1000 * 60; std::optional m_explorerCrashMonitor; ULONGLONG m_explorerLastTerminatedTickCount = 0; }; ================================================ FILE: src/windhawk/app/packages.config ================================================  ================================================ FILE: src/windhawk/app/resource.h ================================================ //{{NO_DEPENDENCIES}} // Microsoft Visual C++ generated include file. // Used by rsrc.rc // #define IDR_MAINFRAME 0x80 #define IDI_NOTIFICATION 0x81 #define IDD_TASK_MANAGER 0x82 #define IDD_TOOLKIT 0x83 #define IDS_TRAY_OPEN 0x90 #define IDS_TRAY_LOADED_MODS 0x91 #define IDS_TRAY_TOOLKIT 0xA0 #define IDS_TRAY_EXIT 0xB0 #define IDS_EXITDLG_TITLE 0xB1 #define IDS_EXITDLG_CONTENT 0xB2 #define IDS_EXITDLG_CHECKBOX_AUTOSTART 0xB3 #define IDS_EXITDLG_BUTTON_EXIT 0xB4 #define IDS_EXITDLG_BUTTON_CANCEL 0xB5 #define IDS_NOTIFICATION_UPDATE_APP 0xB6 #define IDS_NOTIFICATION_UPDATE_APP_MOD 0xB7 #define IDS_NOTIFICATION_UPDATE_APP_MODS 0xB8 #define IDS_NOTIFICATION_UPDATE_MOD 0xB9 #define IDS_NOTIFICATION_UPDATE_MODS 0xBA #define IDS_TRAYICON_TOOLTIP_UPDATE 0xBB #define IDS_TASKDLG_TITLE_LOADED_MODS 0xBC #define IDS_TASKDLG_TITLE_TASKS_IN_PROGRESS 0xBD #define IDS_TASKDLG_BUTTON_OPEN_APP 0xBE #define IDS_TASKDLG_COLUMN_MOD 0xBF #define IDS_TASKDLG_COLUMN_PROCESS 0xC0 #define IDS_TASKDLG_COLUMN_PID 0xC1 #define IDS_TASKDLG_COLUMN_STATUS 0xC2 #define IDS_TASKDLG_STATUS_PENDING 0xC3 #define IDS_TASKDLG_STATUS_LOADING 0xC4 #define IDS_TASKDLG_STATUS_LOADED 0xC5 #define IDS_TASKDLG_STATUS_UNLOADED 0xC6 #define IDS_TASKDLG_TASK_INITIALIZING 0xC7 #define IDS_TASKDLG_TASK_LOADING_SYMBOLS 0xC8 #define IDS_TASKDLG_TASK_WAITING_FOR_SYMBOLS 0xD0 #define IDS_TASKDLG_TASK_UNINITIALIZING 0xE0 #define IDS_TASKDLG_PROCESS_SUSPENDED 0xF0 #define IDS_TOOLKITDLG_TITLE 0x100 #define IDS_TOOLKITDLG_EXPLANATION_CRASH 0x110 #define IDS_TOOLKITDLG_BUTTON_OPEN 0x120 #define IDS_TOOLKITDLG_BUTTON_LOADED_MODS 0x121 #define IDS_TOOLKITDLG_BUTTON_EXIT 0x122 #define IDS_TOOLKITDLG_BUTTON_SAFE_MODE 0x123 #define IDS_TOOLKITDLG_BUTTON_CLOSE 0x124 #define IDS_SAFE_MODE_TITLE 0x125 #define IDS_SAFE_MODE_TEXT 0x126 #define IDS_SAFE_MODE_DETECTED_TITLE 0x130 #define IDS_SAFE_MODE_DETECTED_TEXT 0x131 #define IDC_TASK_LIST 1001 #define IDC_TOOLKIT_EXPLANATION 1002 #define IDC_TOOLKIT_LOADED_MODS 1003 #define IDC_TOOLKIT_EXIT 1004 #define IDC_TOOLKIT_SAFE_MODE 1005 #define IDC_TOOLKIT_CLOSE 1006 // Next default values for new objects // #ifdef APSTUDIO_INVOKED #ifndef APSTUDIO_READONLY_SYMBOLS #define _APS_NEXT_RESOURCE_VALUE 0x132 #define _APS_NEXT_COMMAND_VALUE 32775 #define _APS_NEXT_CONTROL_VALUE 1007 #define _APS_NEXT_SYMED_VALUE 101 #endif #endif ================================================ FILE: src/windhawk/app/rsrc/compatibility.manifest ================================================ true PerMonitorV2 true ================================================ FILE: src/windhawk/app/service.cpp ================================================ #include "stdafx.h" #include "service.h" #include "engine_control.h" #include "functions.h" #include "logger.h" #include "service_common.h" #include "storage_manager.h" #include "version.h" namespace { HANDLE CreateServiceInfoFileMapping() { // Allow only FILE_MAP_READ (0x0004), only for medium integrity. PCWSTR pszStringSecurityDescriptor = L"D:(A;;0x0004;;;WD)S:(ML;;NW;;;ME)"; wil::unique_hlocal secDesc; THROW_IF_WIN32_BOOL_FALSE( ConvertStringSecurityDescriptorToSecurityDescriptor( pszStringSecurityDescriptor, SDDL_REVISION_1, &secDesc, nullptr)); SECURITY_ATTRIBUTES secAttr = {sizeof(SECURITY_ATTRIBUTES)}; secAttr.lpSecurityDescriptor = secDesc.get(); secAttr.bInheritHandle = FALSE; wil::unique_handle fileMapping( CreateFileMapping(INVALID_HANDLE_VALUE, &secAttr, PAGE_READWRITE, 0, sizeof(ServiceCommon::ServiceInfo), ServiceCommon::kInfoFileMappingName)); THROW_LAST_ERROR_IF(!fileMapping || GetLastError() == ERROR_ALREADY_EXISTS); wil::unique_mapview_ptr fileMappingView( reinterpret_cast( MapViewOfFile(fileMapping.get(), FILE_MAP_WRITE, 0, 0, 0))); THROW_LAST_ERROR_IF(!fileMappingView); FILETIME creationTime; FILETIME exitTime; FILETIME kernelTime; FILETIME userTime; THROW_IF_WIN32_BOOL_FALSE(GetProcessTimes( GetCurrentProcess(), &creationTime, &exitTime, &kernelTime, &userTime)); fileMappingView->version = VER_FILE_VERSION_LONG; fileMappingView->processId = GetCurrentProcessId(); fileMappingView->processCreationTime = wil::filetime::to_int64(creationTime); return fileMapping.release(); } HANDLE CreateServiceMutex() { // Allow only SYNCHRONIZE (0x00100000), only for medium integrity. PCWSTR pszStringSecurityDescriptor = L"D:(A;;0x00100000;;;WD)S:(ML;;NW;;;ME)"; wil::unique_hlocal secDesc; THROW_IF_WIN32_BOOL_FALSE( ConvertStringSecurityDescriptorToSecurityDescriptor( pszStringSecurityDescriptor, SDDL_REVISION_1, &secDesc, nullptr)); SECURITY_ATTRIBUTES secAttr = {sizeof(SECURITY_ATTRIBUTES)}; secAttr.lpSecurityDescriptor = secDesc.get(); secAttr.bInheritHandle = FALSE; wil::unique_mutex_nothrow mutex( CreateMutex(&secAttr, TRUE, ServiceCommon::kMutexName)); THROW_LAST_ERROR_IF(!mutex || GetLastError() == ERROR_ALREADY_EXISTS); return mutex.release(); } void CreateProcessOnSessionId(DWORD dwSessionId, const WCHAR* pszPath, WCHAR* pszCommandLine) { wil::unique_handle token; THROW_IF_WIN32_BOOL_FALSE(WTSQueryUserToken(dwSessionId, &token)); wil::unique_environment_block environment; THROW_IF_WIN32_BOOL_FALSE( CreateEnvironmentBlock(&environment, token.get(), FALSE)); wil::unique_process_information processInfo; STARTUPINFO startupInfo = {sizeof(STARTUPINFO)}; THROW_IF_WIN32_BOOL_FALSE(CreateProcessAsUser( token.get(), pszPath, pszCommandLine, nullptr, nullptr, FALSE, NORMAL_PRIORITY_CLASS | CREATE_UNICODE_ENVIRONMENT, environment.get(), nullptr, &startupInfo, &processInfo)); } void CreateProcessOnAllSessions(const WCHAR* pszPath, WCHAR* pszCommandLine) { WTS_SESSION_INFO* sessionInfo; DWORD dwCount; THROW_IF_WIN32_BOOL_FALSE(WTSEnumerateSessions(WTS_CURRENT_SERVER_HANDLE, 0, 1, &sessionInfo, &dwCount)); wil::unique_wtsmem_ptr scopedSessionInfo(sessionInfo); for (DWORD i = 0; i < dwCount; i++) { WCHAR* pszUserName; DWORD dwUserNameLen; THROW_IF_WIN32_BOOL_FALSE(WTSQuerySessionInformation( WTS_CURRENT_SERVER_HANDLE, sessionInfo[i].SessionId, WTSUserName, &pszUserName, &dwUserNameLen)); wil::unique_wtsmem_ptr scopedUserName(pszUserName); if (*pszUserName != L'\0') { CreateProcessOnSessionId(sessionInfo[i].SessionId, pszPath, pszCommandLine); } } } } // namespace class ServiceInstance { public: VOID SvcMain(DWORD dwArgc, LPTSTR* lpszArgv); private: VOID SvcInit(DWORD dwArgc, LPTSTR* lpszArgv); VOID SvcRun(DWORD dwArgc, LPTSTR* lpszArgv); VOID ReportSvcStatus(DWORD dwCurrentState, DWORD dwWin32ExitCode, DWORD dwWaitHint); static DWORD WINAPI SvcCtrlHandlerExThunk(DWORD dwControl, DWORD dwEventType, LPVOID lpEventData, LPVOID lpContext); DWORD SvcCtrlHandlerEx(DWORD dwControl, DWORD dwEventType, LPVOID lpEventData); SERVICE_STATUS_HANDLE m_svcStatusHandle{}; DWORD m_dwCheckPoint = 1; wil::unique_handle m_svcInfoFileMapping; wil::unique_mutex m_svcMutex; wil::mutex_release_scope_exit m_svcMutexLock; wil::unique_event m_svcStopEvent; wil::unique_event m_svcScanForProcessesEvent; wil::unique_event m_svcEmergencyStopEvent; wil::unique_event m_svcSafeModeStopEvent; std::optional m_engineControl; }; // // Purpose: // Entry point for the service // // Parameters: // dwArgc - Number of arguments in the lpszArgv array // lpszArgv - Array of strings. The first string is the name of // the service and subsequent strings are passed by the process // that called the StartService function to start the service. // // Return value: // None. // VOID ServiceInstance::SvcMain(DWORD dwArgc, LPTSTR* lpszArgv) { // Register the handler function for the service m_svcStatusHandle = RegisterServiceCtrlHandlerEx( ServiceCommon::kName, SvcCtrlHandlerExThunk, reinterpret_cast(this)); THROW_LAST_ERROR_IF_NULL(m_svcStatusHandle); // Report initial status to the SCM ReportSvcStatus(SERVICE_START_PENDING, NO_ERROR, 3000); // Perform service-specific initialization and work. try { VERBOSE(L"Running SvcInit"); SvcInit(dwArgc, lpszArgv); } catch (const std::exception& e) { LOG(L"SvcInit failed: %S", e.what()); ReportSvcStatus(SERVICE_STOPPED, wil::ResultFromCaughtException(), 0); return; } // Report running status when initialization is complete. ReportSvcStatus(SERVICE_RUNNING, NO_ERROR, 0); try { VERBOSE(L"Running SvcRun"); SvcRun(dwArgc, lpszArgv); } catch (const std::exception& e) { LOG(L"SvcRun failed: %S", e.what()); ReportSvcStatus(SERVICE_STOPPED, wil::ResultFromCaughtException(), 0); return; } VERBOSE(L"Reporting SERVICE_STOPPED"); ReportSvcStatus(SERVICE_STOPPED, NO_ERROR, 0); } // // Purpose: // The service code // // Parameters: // dwArgc - Number of arguments in the lpszArgv array // lpszArgv - Array of strings. The first string is the name of // the service and subsequent strings are passed by the process // that called the StartService function to start the service. // // Return value: // None // VOID ServiceInstance::SvcInit(DWORD dwArgc, LPTSTR* lpszArgv) { // TO_DO: Declare and set any required variables. // Be sure to periodically call ReportSvcStatus() with // SERVICE_START_PENDING. If initialization fails, call // ReportSvcStatus with SERVICE_STOPPED. if (!Functions::SetDebugPrivilege(TRUE)) { LOG(L"SetDebugPrivilege failed with error %u", GetLastError()); } m_svcInfoFileMapping.reset(CreateServiceInfoFileMapping()); m_svcMutex.reset(CreateServiceMutex()); m_svcMutexLock = m_svcMutex.ReleaseMutex_scope_exit(); // Create an event. The control handler function, SvcCtrlHandler, // signals this event when it receives the stop control code. m_svcStopEvent.reset(CreateEvent(nullptr, // default security attributes TRUE, // manual reset event FALSE, // not signaled nullptr)); // no name THROW_LAST_ERROR_IF_NULL(m_svcStopEvent); m_svcScanForProcessesEvent.reset(Functions::CreateEventForMediumIntegrity( ServiceCommon::kScanForProcessesEventName, FALSE)); m_svcEmergencyStopEvent.reset(Functions::CreateEventForMediumIntegrity( ServiceCommon::kEmergencyStopEventName, TRUE)); m_svcSafeModeStopEvent.reset(Functions::CreateEventForMediumIntegrity( ServiceCommon::kSafeModeStopEventName, TRUE)); auto settings = StorageManager::GetInstance().GetAppConfig(L"Settings", false); if (!settings->GetInt(L"SafeMode").value_or(0)) { m_engineControl.emplace(); m_engineControl->HandleNewProcesses(); } } // // Purpose: // The service code // VOID ServiceInstance::SvcRun(DWORD dwArgc, LPTSTR* lpszArgv) { // TO_DO: Perform work until service stops. try { auto modulePath = wil::GetModuleFileName(); auto commandLine = L"\"" + modulePath + L"\" -tray-only"; CreateProcessOnAllSessions(modulePath.c_str(), commandLine.data()); } catch (const std::exception& e) { LOG(L"CreateProcessOnAllSessions failed: %S", e.what()); } HANDLE events[] = { m_svcStopEvent.get(), m_svcScanForProcessesEvent.get(), m_svcEmergencyStopEvent.get(), m_svcSafeModeStopEvent.get(), }; while (true) { bool keepLooping = false; DWORD dwWaitResult = WaitForMultipleObjectsEx(ARRAYSIZE(events), events, FALSE, 1000, FALSE); switch (dwWaitResult) { case WAIT_FAILED: THROW_LAST_ERROR(); break; case WAIT_TIMEOUT: keepLooping = true; break; case WAIT_OBJECT_0: VERBOSE(L"Received stop event"); break; case WAIT_OBJECT_0 + 1: VERBOSE(L"Received scan for processes event"); keepLooping = true; break; case WAIT_OBJECT_0 + 2: LOG(L"Received emergency stop event"); break; case WAIT_OBJECT_0 + 3: { LOG(L"Received safe mode stop event"); auto settings = StorageManager::GetInstance().GetAppConfig( L"Settings", true); settings->SetInt(L"SafeMode", 1); // Flush the settings to ensure they are saved, in case // unloading will cause a BSOD. StorageManager::GetInstance().FlushAppConfig(L"Settings"); break; } default: LOG(L"Received unknown event %u", dwWaitResult); break; } if (!keepLooping) { break; } if (m_engineControl) { m_engineControl->HandleNewProcesses(); } } } // // Purpose: // Sets the current service status and reports it to the SCM. // // Parameters: // dwCurrentState - The current state (see SERVICE_STATUS) // dwWin32ExitCode - The system error code // dwWaitHint - Estimated time for pending operation, // in milliseconds // // Return value: // None // VOID ServiceInstance::ReportSvcStatus(DWORD dwCurrentState, DWORD dwWin32ExitCode, DWORD dwWaitHint) { SERVICE_STATUS SvcStatus{}; // These SERVICE_STATUS members remain as set here. SvcStatus.dwServiceType = SERVICE_WIN32_OWN_PROCESS; SvcStatus.dwServiceSpecificExitCode = 0; // Fill in the SERVICE_STATUS structure. SvcStatus.dwCurrentState = dwCurrentState; SvcStatus.dwWin32ExitCode = dwWin32ExitCode; SvcStatus.dwWaitHint = dwWaitHint; SvcStatus.dwControlsAccepted = SERVICE_ACCEPT_SESSIONCHANGE; if (dwCurrentState != SERVICE_START_PENDING) SvcStatus.dwControlsAccepted |= SERVICE_ACCEPT_STOP; if (dwCurrentState == SERVICE_RUNNING || dwCurrentState == SERVICE_STOPPED) SvcStatus.dwCheckPoint = 0; else SvcStatus.dwCheckPoint = m_dwCheckPoint++; // Report the status of the service to the SCM. SetServiceStatus(m_svcStatusHandle, &SvcStatus); } // static DWORD WINAPI ServiceInstance::SvcCtrlHandlerExThunk(DWORD dwControl, DWORD dwEventType, LPVOID lpEventData, LPVOID lpContext) { auto serviceInstance = reinterpret_cast(lpContext); return serviceInstance->SvcCtrlHandlerEx(dwControl, dwEventType, lpEventData); } // // Purpose: // Called by SCM whenever a control code is sent to the service // using the ControlService function. // DWORD ServiceInstance::SvcCtrlHandlerEx(DWORD dwControl, DWORD dwEventType, LPVOID lpEventData) { // Handle the requested control code. switch (dwControl) { case SERVICE_CONTROL_STOP: VERBOSE("Handling SERVICE_CONTROL_STOP"); ReportSvcStatus(SERVICE_STOP_PENDING, NO_ERROR, 0); // Signal the service to stop. SetEvent(m_svcStopEvent.get()); return NO_ERROR; case SERVICE_CONTROL_SESSIONCHANGE: if (dwEventType == WTS_SESSION_LOGON) { VERBOSE("Handling WTS_SESSION_LOGON"); try { auto sessionNotification = reinterpret_cast( lpEventData); WCHAR* pszUserName; DWORD dwUserNameLen; THROW_IF_WIN32_BOOL_FALSE(WTSQuerySessionInformation( WTS_CURRENT_SERVER_HANDLE, sessionNotification->dwSessionId, WTSUserName, &pszUserName, &dwUserNameLen)); wil::unique_wtsmem_ptr scopedUserName(pszUserName); if (*pszUserName != L'\0') { auto modulePath = wil::GetModuleFileName(); auto commandLine = L"\"" + modulePath + L"\" -tray-only"; CreateProcessOnSessionId( sessionNotification->dwSessionId, modulePath.c_str(), commandLine.data()); } } catch (const std::exception& e) { LOG(L"WTS_SESSION_LOGON handler failed: %S", e.what()); } } return NO_ERROR; case SERVICE_CONTROL_INTERROGATE: return NO_ERROR; default: return ERROR_CALL_NOT_IMPLEMENTED; } } VOID WINAPI SvcMain(DWORD dwArgc, LPTSTR* lpszArgv) { try { ServiceInstance serviceInstance; serviceInstance.SvcMain(dwArgc, lpszArgv); } catch (const std::exception& e) { LOG(L"SvcMain failed: %S", e.what()); } } namespace Service { void Run() { auto serviceName{std::to_array(ServiceCommon::kName)}; SERVICE_TABLE_ENTRY DispatchTable[] = {{serviceName.data(), SvcMain}, {nullptr, nullptr}}; THROW_IF_WIN32_BOOL_FALSE(StartServiceCtrlDispatcher(DispatchTable)); } bool IsRunning(bool waitIfStarting) { wil::unique_schandle scManager( OpenSCManager(nullptr, // local computer nullptr, // ServicesActive database 0)); THROW_LAST_ERROR_IF_NULL(scManager); wil::unique_schandle service(OpenService( scManager.get(), ServiceCommon::kName, SERVICE_QUERY_STATUS)); THROW_LAST_ERROR_IF_NULL(service); SERVICE_STATUS_PROCESS ssp; DWORD dwBytesNeeded; THROW_IF_WIN32_BOOL_FALSE(QueryServiceStatusEx( service.get(), SC_STATUS_PROCESS_INFO, reinterpret_cast(&ssp), sizeof(ssp), &dwBytesNeeded)); switch (ssp.dwCurrentState) { case SERVICE_RUNNING: return true; case SERVICE_START_PENDING: if (!waitIfStarting) { return false; } break; default: return false; } constexpr DWORD kStartStopTimeout = 30000; // Save the tick count and initial checkpoint. DWORD dwStartTickCount = GetTickCount(); DWORD dwOldCheckPoint = ssp.dwCheckPoint; while (ssp.dwCurrentState == SERVICE_START_PENDING) { if (ssp.dwCheckPoint > dwOldCheckPoint) { // Continue to wait and check dwStartTickCount = GetTickCount(); dwOldCheckPoint = ssp.dwCheckPoint; } else { if (GetTickCount() - dwStartTickCount > kStartStopTimeout) { // Timeout. break; } } // Do not wait longer than the wait hint. A good interval is // one-tenth the wait hint, but no less than 1 second and no // more than 10 seconds. DWORD dwWaitTime = ssp.dwWaitHint / 10; // if (dwWaitTime < 1000) // dwWaitTime = 1000; // else if (dwWaitTime > 10000) // dwWaitTime = 10000; // 200-1000 ms for better responsiveness. if (dwWaitTime < 200) dwWaitTime = 200; else if (dwWaitTime > 1000) dwWaitTime = 1000; Sleep(dwWaitTime); // Check the status again. THROW_IF_WIN32_BOOL_FALSE(QueryServiceStatusEx( service.get(), // handle to service SC_STATUS_PROCESS_INFO, // info level reinterpret_cast(&ssp), // address of structure sizeof(ssp), // size of structure &dwBytesNeeded)); // if buffer too small } return ssp.dwCurrentState == SERVICE_RUNNING; } void Start() { wil::unique_schandle scManager( OpenSCManager(nullptr, // local computer nullptr, // ServicesActive database 0)); THROW_LAST_ERROR_IF_NULL(scManager); wil::unique_schandle service( OpenService(scManager.get(), ServiceCommon::kName, SERVICE_START | SERVICE_CHANGE_CONFIG)); THROW_LAST_ERROR_IF_NULL(service); if (!StartService(service.get(), 0, nullptr)) { THROW_LAST_ERROR_IF(GetLastError() != ERROR_SERVICE_ALREADY_RUNNING); } // Change start type to autostart. THROW_IF_WIN32_BOOL_FALSE( ChangeServiceConfig(service.get(), SERVICE_NO_CHANGE, // service type SERVICE_AUTO_START, // start type SERVICE_NO_CHANGE, // error control type nullptr, // path to service's binary nullptr, // no load ordering group nullptr, // no tag identifier nullptr, // no dependencies nullptr, // LocalSystem account nullptr, // no password nullptr)); // service name to display } void Stop(bool disableAutoStart) { wil::unique_schandle scManager( OpenSCManager(nullptr, // local computer nullptr, // ServicesActive database 0)); THROW_LAST_ERROR_IF_NULL(scManager); wil::unique_schandle service( OpenService(scManager.get(), ServiceCommon::kName, SERVICE_STOP | SERVICE_CHANGE_CONFIG)); THROW_LAST_ERROR_IF_NULL(service); SERVICE_STATUS serviceStatus; if (!ControlService(service.get(), SERVICE_CONTROL_STOP, &serviceStatus)) { THROW_LAST_ERROR_IF(GetLastError() != ERROR_SERVICE_NOT_ACTIVE); } // Change start type. if (disableAutoStart) { THROW_IF_WIN32_BOOL_FALSE( ChangeServiceConfig(service.get(), SERVICE_NO_CHANGE, // service type SERVICE_DEMAND_START, // start type SERVICE_NO_CHANGE, // error control type nullptr, // path to service's binary nullptr, // no load ordering group nullptr, // no tag identifier nullptr, // no dependencies nullptr, // LocalSystem account nullptr, // no password nullptr)); // service name to display } } } // namespace Service ================================================ FILE: src/windhawk/app/service.h ================================================ #pragma once namespace Service { void Run(); bool IsRunning(bool waitIfStarting); void Start(); void Stop(bool disableAutoStart); } // namespace Service ================================================ FILE: src/windhawk/app/service_common.h ================================================ #pragma once namespace ServiceCommon { static inline constexpr WCHAR kName[] = L"Windhawk"; static inline constexpr WCHAR kInfoFileMappingName[] = L"Global\\WindhawkServiceInfoFileMapping"; static inline constexpr WCHAR kMutexName[] = L"Global\\WindhawkServiceMutex"; static inline constexpr WCHAR kScanForProcessesEventName[] = L"Global\\WindhawkScanForProcesses"; static inline constexpr WCHAR kEmergencyStopEventName[] = L"Global\\WindhawkServiceEmergencyStopEvent"; static inline constexpr WCHAR kSafeModeStopEventName[] = L"Global\\WindhawkServiceSafeModeStopEvent"; struct ServiceInfo { DWORD version; DWORD processId; ULONGLONG processCreationTime; }; } // namespace ServiceCommon ================================================ FILE: src/windhawk/app/stdafx.cpp ================================================ // stdafx.cpp : source file that includes just the standard includes // app.pch will be the pre-compiled header // stdafx.obj will contain the pre-compiled type information #include "stdafx.h" ================================================ FILE: src/windhawk/app/stdafx.h ================================================ #pragma once // Change these values to use different versions #define WINVER _WIN32_WINNT_WIN7 #define _WIN32_WINNT _WIN32_WINNT_WIN7 #define _WIN32_IE _WIN32_IE_IE80 #define _RICHEDIT_VER 0x0500 #define WIN32_LEAN_AND_MEAN // Exclude rarely-used stuff from Windows headers #define NOMINMAX ////////////////////////////////////////////////////////////////////////// // WTL #define _WTL_NO_CSTRING #define _WTL_NO_WTYPES #define _WTL_NO_UNION_CLASSES #define _ATL_CSTRING_EXPLICIT_CONSTRUCTORS #include #include #include #include #include #include extern CAppModule _Module; #include #include #include #include // #include #include // #include ////////////////////////////////////////////////////////////////////////// // Windows #include #include #include #include #include #include #include #include #include #include #include ////////////////////////////////////////////////////////////////////////// // STL #include #include #include #include #include #include #include #include #include #include #include #include ////////////////////////////////////////////////////////////////////////// // Libraries // https://github.com/nlohmann/json#implicit-conversions #define JSON_USE_IMPLICIT_CONVERSIONS 0 #include #include #include // must be included before other wil includes #include #include #include #include #include ================================================ FILE: src/windhawk/app/storage_manager.cpp ================================================ #include "stdafx.h" #include "functions.h" #include "storage_manager.h" namespace { std::filesystem::path PathFromStorage( const PortableSettings& storage, PCWSTR valueName, const std::filesystem::path& baseFolderPath) { auto storedPath = storage.GetString(valueName).value_or(L""); if (storedPath.empty()) { throw std::runtime_error("Missing path value: " + CStringA(valueName)); } #ifndef _WIN64 BOOL isWow64; if (IsWow64Process(GetCurrentProcess(), &isWow64) && isWow64) { // Get the native Program Files path regardless of the current // process architecture. storedPath = Functions::ReplaceAll(storedPath, L"%ProgramFiles%", L"%ProgramW6432%", /*ignoreCase=*/true); } #endif // _WIN64 auto expandedPath = wil::ExpandEnvironmentStrings(storedPath.c_str()); return (baseFolderPath / expandedPath).lexically_normal(); } } // namespace // static StorageManager& StorageManager::GetInstance() { static StorageManager s; return s; } std::unique_ptr StorageManager::GetAppConfig(PCWSTR section, bool write) { if (portableStorage) { const auto& iniFileSettingsPath = std::get(settingsPath); return std::make_unique( iniFileSettingsPath.path.c_str(), section, write); } else { const auto& registrySettingsPath = std::get(settingsPath); std::wstring subKey = registrySettingsPath.subKey + L'\\' + section; return std::make_unique(registrySettingsPath.hKey, subKey.c_str(), write); } } bool StorageManager::FlushAppConfig(PCWSTR section) { if (portableStorage) { return false; } const auto& registrySettingsPath = std::get(settingsPath); std::wstring subKey = registrySettingsPath.subKey + L'\\' + section; wil::unique_hkey hKey; LSTATUS error = RegOpenKeyEx(registrySettingsPath.hKey, subKey.c_str(), 0, KEY_WOW64_64KEY | KEY_QUERY_VALUE, &hKey); if (error != ERROR_SUCCESS) { return false; } return RegFlushKey(hKey.get()) == ERROR_SUCCESS; } std::filesystem::path StorageManager::GetModMetadataPath( PCWSTR metadataCategory) { return GetEngineAppDataPath() / L"ModsWritable" / metadataCategory; } bool StorageManager::IsPortable() { return portableStorage; } std::filesystem::path StorageManager::GetEnginePath(USHORT machine) { if (machine == IMAGE_FILE_MACHINE_UNKNOWN) { // Use current architecture. #if defined(_M_IX86) machine = IMAGE_FILE_MACHINE_I386; #elif defined(_M_X64) machine = IMAGE_FILE_MACHINE_AMD64; #elif defined(_M_ARM64) machine = IMAGE_FILE_MACHINE_ARM64; #else #error "Unsupported architecture" #endif } PCWSTR folderName; switch (machine) { case IMAGE_FILE_MACHINE_I386: folderName = L"32"; break; case IMAGE_FILE_MACHINE_AMD64: folderName = L"64"; break; case IMAGE_FILE_MACHINE_ARM64: folderName = L"arm64"; break; default: throw std::logic_error("Unknown architecture"); } return enginePath / folderName; } std::filesystem::path StorageManager::GetUIPath() { return uiPath; } std::filesystem::path StorageManager::GetCompilerPath() { return compilerPath; } std::filesystem::path StorageManager::GetUIDataPath() { return appDataPath / L"UIData"; } std::filesystem::path StorageManager::GetEditorWorkspacePath() { return appDataPath / L"EditorWorkspace"; } std::filesystem::path StorageManager::GetUserProfileJsonPath() { return appDataPath / L"userprofile.json"; } StorageManager::StorageManager() { std::filesystem::path modulePath = wil::GetModuleFileName(); auto folderPath = modulePath.parent_path(); std::filesystem::path iniFilePath = modulePath; iniFilePath.replace_extension("ini"); auto storage = IniFileSettings(iniFilePath.c_str(), L"Storage", false); enginePath = PathFromStorage(storage, L"EnginePath", folderPath); uiPath = PathFromStorage(storage, L"UIPath", folderPath); compilerPath = PathFromStorage(storage, L"CompilerPath", folderPath); appDataPath = PathFromStorage(storage, L"AppDataPath", folderPath); if (!std::filesystem::is_directory(appDataPath)) { std::error_code ec; std::filesystem::create_directories(appDataPath, ec); } portableStorage = storage.GetInt(L"Portable").value_or(0); if (portableStorage) { settingsPath = IniFilePath{appDataPath / L"settings.ini"}; } else { std::wstring registryKey = storage.GetString(L"RegistryKey").value_or(L""); if (registryKey.empty()) { throw std::runtime_error("Missing RegistryKey value"); } auto firstBackslash = registryKey.find(L'\\'); if (firstBackslash == registryKey.npos) { throw std::runtime_error("Invalid RegistryKey value"); } HKEY hkey; std::wstring baseKey = registryKey.substr(0, firstBackslash); if (baseKey == L"HKEY_CURRENT_USER" || baseKey == L"HKCU") { hkey = HKEY_CURRENT_USER; } else if (baseKey == L"HKEY_USERS" || baseKey == L"HKU") { hkey = HKEY_USERS; } else if (baseKey == L"HKEY_LOCAL_MACHINE" || baseKey == L"HKLM") { hkey = HKEY_LOCAL_MACHINE; } else { throw std::runtime_error("Unsupported RegistryKey value"); } std::wstring subKey = registryKey.substr(firstBackslash + 1); settingsPath = RegistryPath{hkey, std::move(subKey)}; } } StorageManager::~StorageManager() = default; std::filesystem::path StorageManager::GetEngineAppDataPath() { return appDataPath / L"Engine"; } StorageManager::ModMetadataChangeNotification::ModMetadataChangeNotification( PCWSTR metadataCategory) { auto& storageManager = GetInstance(); auto metadataPath = storageManager.GetModMetadataPath(metadataCategory); if (!std::filesystem::is_directory(metadataPath)) { std::error_code ec; std::filesystem::create_directories(metadataPath, ec); } m_findChange = wil::unique_hfind_change(FindFirstChangeNotification( metadataPath.c_str(), FALSE, FILE_NOTIFY_CHANGE_FILE_NAME | FILE_NOTIFY_CHANGE_LAST_WRITE)); THROW_LAST_ERROR_IF(!m_findChange); } HANDLE StorageManager::ModMetadataChangeNotification::GetHandle() { return m_findChange.get(); } void StorageManager::ModMetadataChangeNotification::ContinueMonitoring() { THROW_IF_WIN32_BOOL_FALSE(FindNextChangeNotification(m_findChange.get())); } ================================================ FILE: src/windhawk/app/storage_manager.h ================================================ #pragma once #include "portable_settings.h" class StorageManager { public: StorageManager(const StorageManager&) = delete; StorageManager(StorageManager&&) = delete; StorageManager& operator=(const StorageManager&) = delete; StorageManager& operator=(StorageManager&&) = delete; static StorageManager& GetInstance(); std::unique_ptr GetAppConfig(PCWSTR section, bool write); bool FlushAppConfig(PCWSTR section); std::filesystem::path GetModMetadataPath(PCWSTR metadataCategory); bool IsPortable(); std::filesystem::path GetEnginePath( USHORT machine = IMAGE_FILE_MACHINE_UNKNOWN); std::filesystem::path GetUIPath(); std::filesystem::path GetCompilerPath(); std::filesystem::path GetUIDataPath(); std::filesystem::path GetEditorWorkspacePath(); std::filesystem::path GetUserProfileJsonPath(); class ModMetadataChangeNotification { public: ModMetadataChangeNotification(PCWSTR metadataCategory); HANDLE GetHandle(); void ContinueMonitoring(); private: wil::unique_hfind_change m_findChange; }; private: StorageManager(); ~StorageManager(); std::filesystem::path GetEngineAppDataPath(); struct RegistryPath { HKEY hKey = 0; std::wstring subKey; }; struct IniFilePath { std::wstring path; }; bool portableStorage; std::filesystem::path appDataPath; std::filesystem::path enginePath; std::filesystem::path uiPath; std::filesystem::path compilerPath; std::variant settingsPath; }; ================================================ FILE: src/windhawk/app/task_manager_dlg.cpp ================================================ #include "stdafx.h" #include "task_manager_dlg.h" #include "functions.h" #include "logger.h" #include "storage_manager.h" namespace { // Wait for a bit before refreshing the list, in case more changes will follow. constexpr auto kRefreshListOnDataChangeDelay = 200; constexpr auto kUpdateProcessesStatusInterval = 1000; struct ListItemData { std::wstring filePath; std::wstring processName; DWORD processId = 0; ULONGLONG creationTime = 0; bool isFrozen = false; wil::unique_process_handle executionRequiredRequestProcess; wil::unique_handle executionRequiredRequest; }; bool CanShowDialog() { QUERY_USER_NOTIFICATION_STATE pquns; if (FAILED(SHQueryUserNotificationState(&pquns))) { return true; } // Prevent the dialog from being shown when in fullscreen mode, which can // cause troubles such as interrupting a fullscreen game. switch (pquns) { case QUNS_NOT_PRESENT: case QUNS_BUSY: case QUNS_RUNNING_D3D_FULL_SCREEN: return false; } return true; } std::wstring GetMetadataContent(PCWSTR filePath, FILETIME* pCreationTime) { wil::unique_hfile file( CreateFile(filePath, GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, nullptr, OPEN_EXISTING, 0, nullptr)); THROW_LAST_ERROR_IF(!file); LARGE_INTEGER fileSizeLarge; THROW_IF_WIN32_BOOL_FALSE(GetFileSizeEx(file.get(), &fileSizeLarge)); DWORD fileSize = 0; if (fileSizeLarge.QuadPart <= DWORD_MAX || (fileSizeLarge.QuadPart % sizeof(WCHAR)) == 0) { fileSize = static_cast(fileSizeLarge.QuadPart); } std::wstring fileContents(fileSize / sizeof(WCHAR), L'\0'); DWORD numberOfBytesRead; THROW_IF_WIN32_BOOL_FALSE(ReadFile(file.get(), fileContents.data(), fileSize, &numberOfBytesRead, nullptr)); if (pCreationTime) { THROW_IF_WIN32_BOOL_FALSE( GetFileTime(file.get(), pCreationTime, nullptr, nullptr)); } return fileContents; } std::wstring LocalizeStatus(PCWSTR status) { static const std::unordered_map translation = { {L"Pending...", IDS_TASKDLG_STATUS_PENDING}, {L"Loading...", IDS_TASKDLG_STATUS_LOADING}, {L"Loaded", IDS_TASKDLG_STATUS_LOADED}, {L"Unloaded", IDS_TASKDLG_STATUS_UNLOADED}, {L"Initializing...", IDS_TASKDLG_TASK_INITIALIZING}, {L"Uninitializing...", IDS_TASKDLG_TASK_UNINITIALIZING}, }; auto it = translation.find(status); if (it != translation.end()) { return Functions::LoadStrFromRsrc(it->second); } std::wstring_view statusView{status}; std::wstring_view prefixLoading = L"Loading symbols..."; if (statusView.starts_with(prefixLoading)) { return Functions::LoadStrFromRsrc(IDS_TASKDLG_TASK_LOADING_SYMBOLS) + std::wstring(statusView.substr(prefixLoading.length())); } std::wstring_view prefixWaiting = L"Waiting for symbols..."; if (statusView.starts_with(prefixWaiting)) { return Functions::LoadStrFromRsrc( IDS_TASKDLG_TASK_WAITING_FOR_SYMBOLS) + std::wstring(statusView.substr(prefixWaiting.length())); } return status; } bool IsProcessFrozen(DWORD processId) { wil::unique_process_handle process( OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, FALSE, processId)); if (!process) { return false; } return Functions::IsProcessFrozen(process.get()); } } // namespace // static bool CTaskManagerDlg::IsDataSourceEmpty(DataSource dataSource) { PCWSTR metadataCategory = nullptr; switch (dataSource) { case DataSource::kModStatus: metadataCategory = L"mod-status"; break; case DataSource::kModTask: metadataCategory = L"mod-task"; break; } auto metadataPath = StorageManager::GetInstance().GetModMetadataPath(metadataCategory); if (!std::filesystem::exists(metadataPath)) { return true; } for (const auto& p : std::filesystem::directory_iterator(metadataPath)) { if (!p.is_regular_file()) { continue; } return false; } return true; } CTaskManagerDlg::CTaskManagerDlg(DialogOptions dialogOptions) : m_dialogOptions(std::move(dialogOptions)) {} void CTaskManagerDlg::LoadLanguageStrings() { UINT titleId = 0; switch (m_dialogOptions.dataSource) { case DataSource::kModStatus: titleId = IDS_TASKDLG_TITLE_LOADED_MODS; break; case DataSource::kModTask: titleId = IDS_TASKDLG_TITLE_TASKS_IN_PROGRESS; break; } WCHAR title[1024] = L"Windhawk"; if (titleId) { _snwprintf_s(title, _TRUNCATE, L"%s - Windhawk", Functions::LoadStrFromRsrc(titleId)); } SetWindowText(title); SetDlgItemText(IDOK, Functions::LoadStrFromRsrc(IDS_TASKDLG_BUTTON_OPEN_APP)); UINT columnStringIds[] = { IDS_TASKDLG_COLUMN_MOD, IDS_TASKDLG_COLUMN_PROCESS, IDS_TASKDLG_COLUMN_PID, IDS_TASKDLG_COLUMN_STATUS, }; for (int i = 0; i < ARRAYSIZE(columnStringIds); i++) { LVCOLUMN column = {LVCF_TEXT}; column.pszText = (PWSTR)Functions::LoadStrFromRsrc(columnStringIds[i]); m_taskListSort.SetColumn(i, &column); } bool languageRightToLeft = Functions::IsRightToLeftLanguage(GetThreadUILanguage()); Functions::ApplyDialogLayoutRtl(*this, languageRightToLeft); } void CTaskManagerDlg::DataChanged() { if (m_refreshListOnDataChangePending) { return; } SetTimer(Timer::kRefreshList, kRefreshListOnDataChangeDelay); m_refreshListOnDataChangePending = true; } BOOL CTaskManagerDlg::OnInitDialog(CWindow wndFocus, LPARAM lInitParam) { ReloadMainIcon(); DlgResize_Init(); m_ptMinTrackSize.x /= 2; m_ptMinTrackSize.y /= 2; if (m_dialogOptions.autonomousMode) { ModifyStyle(WS_MINIMIZEBOX | WS_MAXIMIZEBOX | WS_SYSMENU, 0); ModifyStyleEx(0, WS_EX_TOOLWINDOW); // Make the window topmost, slightly wider and less high. CRect rect; GetWindowRect(&rect); rect.right += rect.Width() / 4; rect.bottom -= rect.Height() / 3; SetWindowPos(HWND_TOPMOST, rect, SWP_NOMOVE | SWP_NOACTIVATE); PlaceWindowAtTrayArea(); } else { ModifyStyleEx(0, WS_EX_APPWINDOW); CenterWindow(); } InitTaskList(); LoadLanguageStrings(); SetTimer(Timer::kUpdateProcessesStatus, kUpdateProcessesStatusInterval); if (!m_dialogOptions.autonomousMode) { try { LoadTaskList(); } catch (const std::exception& e) { ::MessageBoxA(m_hWnd, e.what(), "Failed to initialize data", MB_ICONERROR); DestroyWindow(); return FALSE; } return TRUE; } else { SetTimer(Timer::kRefreshList, kRefreshListOnDataChangeDelay); m_refreshListOnDataChangePending = true; SetTimer(Timer::kShowDlg, std::max(m_dialogOptions.autonomousModeShowDelay, kAutonomousModeShowDelayMin)); m_showDlgPending = true; return FALSE; } } void CTaskManagerDlg::OnDestroy() { KillTimer(Timer::kUpdateProcessesStatus); if (m_refreshListOnDataChangePending) { KillTimer(Timer::kRefreshList); } if (m_showDlgPending) { KillTimer(Timer::kShowDlg); } int count = m_taskListSort.GetItemCount(); for (int i = 0; i < count; i++) { delete reinterpret_cast(m_taskListSort.GetItemData(i)); } // From GDI handle checks, not all icons are freed automatically. ::DestroyIcon(SetIcon(nullptr, TRUE)); ::DestroyIcon(SetIcon(nullptr, FALSE)); } void CTaskManagerDlg::OnTimer(UINT_PTR nIDEvent) { switch ((Timer)nIDEvent) { case Timer::kUpdateProcessesStatus: UpdateTaskListProcessesStatus(); break; case Timer::kRefreshList: KillTimer(Timer::kRefreshList); m_refreshListOnDataChangePending = false; RefreshTaskList(); break; case Timer::kShowDlg: KillTimer(Timer::kShowDlg); if (CanShowDialog()) { m_showDlgPending = false; ShowWindow(SW_SHOWNA); } else { SetTimer(Timer::kShowDlg, kUpdateProcessesStatusInterval); } break; } } void CTaskManagerDlg::OnDpiChanged(UINT nDpiX, UINT nDpiY, PRECT pRect) { ReloadMainIcon(); } void CTaskManagerDlg::OnOK(UINT uNotifyCode, int nID, CWindow wndCtl) { if (m_dialogOptions.runButtonCallback) { m_dialogOptions.runButtonCallback(m_hWnd); } } void CTaskManagerDlg::OnCancel(UINT uNotifyCode, int nID, CWindow wndCtl) { if (m_dialogOptions.autonomousMode) { return; } DestroyWindow(); } LRESULT CTaskManagerDlg::OnListRightClick(LPNMHDR pnmh) { // LPNMITEMACTIVATE pnmItemActivate = (LPNMITEMACTIVATE)pnmh; // if (m_taskListSort.GetItemCount() == 0) { // return 1; // } return 1; } void CTaskManagerDlg::OnFinalMessage(HWND hWnd) { if (m_dialogOptions.finalMessageCallback) { m_dialogOptions.finalMessageCallback(m_hWnd); } } UINT_PTR CTaskManagerDlg::SetTimer(Timer nIDEvent, UINT nElapse, TIMERPROC lpfnTimer) { return CDialogImpl::SetTimer(static_cast(nIDEvent), nElapse, lpfnTimer); } BOOL CTaskManagerDlg::KillTimer(Timer nIDEvent) { return CDialogImpl::KillTimer(static_cast(nIDEvent)); } void CTaskManagerDlg::ReloadMainIcon() { UINT dpi = Functions::GetDpiForWindowWithFallback(m_hWnd); CIconHandle mainIcon; mainIcon.LoadIconWithScaleDown( IDR_MAINFRAME, Functions::GetSystemMetricsForDpiWithFallback(SM_CXICON, dpi), Functions::GetSystemMetricsForDpiWithFallback(SM_CYICON, dpi)); CIcon prevMainIcon = SetIcon(mainIcon, TRUE); CIconHandle mainIconSmall; mainIconSmall.LoadIconWithScaleDown( IDR_MAINFRAME, Functions::GetSystemMetricsForDpiWithFallback(SM_CXSMICON, dpi), Functions::GetSystemMetricsForDpiWithFallback(SM_CYSMICON, dpi)); CIcon prevMainIconSmall = SetIcon(mainIconSmall, FALSE); } void CTaskManagerDlg::PlaceWindowAtTrayArea() { CRect windowRect; GetWindowRect(&windowRect); CRect workAreaRect; ::SystemParametersInfo(SPI_GETWORKAREA, 0, &workAreaRect, 0); int margin = 8; windowRect.MoveToXY(workAreaRect.right - windowRect.Width() - margin, workAreaRect.bottom - windowRect.Height() - margin); SetWindowPos(nullptr, windowRect, SWP_NOSIZE | SWP_NOZORDER | SWP_NOACTIVATE); } void CTaskManagerDlg::InitTaskList() { CListViewCtrl list(GetDlgItem(IDC_TASK_LIST)); m_taskListSort.SubclassWindow(list); list.SetExtendedListViewStyle(LVS_EX_HEADERDRAGDROP | LVS_EX_FULLROWSELECT | LVS_EX_LABELTIP | LVS_EX_DOUBLEBUFFER); ::SetWindowTheme(list, L"Explorer", nullptr); UINT windowDpi = Functions::GetDpiForWindowWithFallback(m_hWnd); // Sort PIDs as decimals, signed 128-bit (16-byte) values representing // 96-bit (12-byte) integer numbers: // https://learn.microsoft.com/en-us/dotnet/visual-basic/language-reference/data-types/decimal-data-type // A bit of an overkill, but LVCOLSORT_LONG is signed 32-bit while PIDs are // unsigned 32-bit, so it doesn't fit. And a custom type isn't worth the // effort. struct { PCWSTR name; int width; WORD sort; } columns[] = { {L"Mod", 160, LVCOLSORT_TEXT}, {L"Process", 80, LVCOLSORT_TEXT}, {L"PID", 60, LVCOLSORT_DECIMAL}, {L"Status", LVSCW_AUTOSIZE_USEHEADER, LVCOLSORT_TEXT}, }; for (int i = 0; i < ARRAYSIZE(columns); i++) { list.InsertColumn(i, columns[i].name); int width = columns[i].width; if (width > 0) { width = MulDiv(width, windowDpi, 96); } list.SetColumnWidth(i, width); m_taskListSort.SetColumnSortType(i, columns[i].sort); } // Reduce the width of the last column so that a horizontal scrollbar won't // appear when the vertical scrollbar is visible. int lastColumn = ARRAYSIZE(columns) - 1; int scrollbarWidth = Functions::GetSystemMetricsForDpiWithFallback(SM_CXVSCROLL, windowDpi); list.SetColumnWidth( lastColumn, std::max(list.GetColumnWidth(lastColumn) - scrollbarWidth, scrollbarWidth)); m_taskListSort.SetSortColumn(0); // Fix tooltip not always on top. if (GetExStyle() & WS_EX_TOPMOST) { list.GetToolTips().SetWindowPos( HWND_TOPMOST, 0, 0, 0, 0, SWP_NOSIZE | SWP_NOMOVE | SWP_NOACTIVATE); } } void CTaskManagerDlg::LoadTaskList() { m_taskListSort.SetRedraw(FALSE); auto redrawWhenDone = wil::scope_exit([this] { m_taskListSort.SetRedraw(TRUE); m_taskListSort.RedrawWindow( nullptr, nullptr, RDW_ERASE | RDW_FRAME | RDW_INVALIDATE | RDW_ALLCHILDREN); }); PCWSTR metadataCategory = nullptr; switch (m_dialogOptions.dataSource) { case DataSource::kModStatus: metadataCategory = L"mod-status"; break; case DataSource::kModTask: metadataCategory = L"mod-task"; break; } auto metadataPath = StorageManager::GetInstance().GetModMetadataPath(metadataCategory); int firstItemIndex = m_taskListSort.GetItemCount(); int itemIndex = firstItemIndex; int selectedIndex = m_taskListSort.GetSelectedIndex(); bool isSelectionVisible = selectedIndex == -1 ? false : m_taskListSort.IsItemVisible(selectedIndex); std::wstring selectedFilePath = selectedIndex == -1 ? std::wstring() : reinterpret_cast( m_taskListSort.GetItemData(selectedIndex)) ->filePath; if (std::filesystem::exists(metadataPath)) { for (const auto& p : std::filesystem::directory_iterator(metadataPath)) { if (!p.is_regular_file()) { continue; } try { if (!LoadTaskItemFromMetadataFile(p.path(), itemIndex)) { VERBOSE(L"Didn't load %s", p.path().c_str()); continue; } if (selectedFilePath == p.path()) { // Like SelectItem, but without EnsureVisible. if (m_taskListSort.SetItemState( itemIndex, LVIS_SELECTED | LVIS_FOCUSED, LVIS_SELECTED | LVIS_FOCUSED)) { m_taskListSort.SetSelectionMark(itemIndex); } } itemIndex++; } catch (const std::exception& e) { LOG(L"Error handling %s: %S", p.path().c_str(), e.what()); } } } // Remove old items only after adding new items to preserve the scroll // position. for (int i = 0; i < firstItemIndex; i++) { delete reinterpret_cast(m_taskListSort.GetItemData(0)); m_taskListSort.DeleteItem(0); } m_taskListSort.DoSortItems(m_taskListSort.GetSortColumn(), m_taskListSort.IsSortDescending()); if (isSelectionVisible) { int newSelectedIndex = m_taskListSort.GetSelectedIndex(); if (newSelectedIndex != -1) { m_taskListSort.EnsureVisible(newSelectedIndex, FALSE); } } } bool CTaskManagerDlg::LoadTaskItemFromMetadataFile( const std::filesystem::path& filePath, int itemIndex) { auto filenameParts = Functions::SplitString(filePath.filename().native(), L'_'); if (filenameParts.size() != 4) { return false; } DWORD sessionManagerProcessId = std::stoul(filenameParts[0]); ULONGLONG sessionManagerProcessCreationTime = std::stoull(filenameParts[1]); if (sessionManagerProcessId != m_dialogOptions.sessionManagerProcessId || sessionManagerProcessCreationTime != m_dialogOptions.sessionManagerProcessCreationTime) { // Probably a stale file, try to remove. std::error_code ec; std::filesystem::remove(filePath, ec); return false; } DWORD targetProcessId = std::stoul(filenameParts[2]); auto& modName = filenameParts[3]; FILETIME creationTime; std::wstring metadata = GetMetadataContent(filePath.c_str(), &creationTime); PCWSTR processName = metadata.c_str(); PCWSTR status = L""; auto separator = metadata.find(L'|'); if (separator != metadata.npos) { metadata[separator] = L'\0'; status = metadata.c_str() + separator + 1; } AddItemToList(itemIndex, filePath.c_str(), modName.c_str(), processName, targetProcessId, status, creationTime); return true; } void CTaskManagerDlg::AddItemToList(int itemIndex, PCWSTR filePath, PCWSTR mod, PCWSTR processName, DWORD processId, PCWSTR status, FILETIME creationTime) { std::wstring processNameFormatted = processName; bool isFrozen = IsProcessFrozen(processId); if (isFrozen) { processNameFormatted += L' '; processNameFormatted += Functions::LoadStrFromRsrc(IDS_TASKDLG_PROCESS_SUSPENDED); } m_taskListSort.AddItem(itemIndex, 0, mod); m_taskListSort.AddItem(itemIndex, 1, processNameFormatted.c_str()); m_taskListSort.AddItem(itemIndex, 2, std::to_wstring(processId).c_str()); m_taskListSort.AddItem(itemIndex, 3, LocalizeStatus(status).c_str()); // The process handle must be kept alive while the request is active. // Otherwise, a BSOD might occur in Windows 10. wil::unique_process_handle executionRequiredRequestProcess; wil::unique_handle executionRequiredRequest; if (Functions::IsWindowsVersionOrGreaterWithBuildNumber(10, 0, 0)) { executionRequiredRequestProcess.reset( OpenProcess(PROCESS_SET_LIMITED_INFORMATION, FALSE, processId)); if (executionRequiredRequestProcess) { HRESULT hr = Functions::CreateExecutionRequiredRequest( executionRequiredRequestProcess.get(), executionRequiredRequest.put()); if (FAILED(hr) || !executionRequiredRequest) { LOG(L"Failed to create execution required request: %08X", hr); executionRequiredRequest.reset(); executionRequiredRequestProcess.reset(); } } } auto* itemData = new ListItemData{ .filePath = filePath, .processName = processName, .processId = processId, .creationTime = wil::filetime::to_int64(creationTime), .isFrozen = isFrozen, .executionRequiredRequestProcess = std::move(executionRequiredRequestProcess), .executionRequiredRequest = std::move(executionRequiredRequest), }; m_taskListSort.SetItemData(itemIndex, reinterpret_cast(itemData)); } void CTaskManagerDlg::RefreshTaskList() { try { LoadTaskList(); } catch (const std::exception& e) { if (!m_dialogOptions.autonomousMode) { ::MessageBoxA(m_hWnd, e.what(), "Failed to update data", MB_ICONERROR); } else { LOG(L"%S", e.what()); } DestroyWindow(); return; } UpdateDialogAfterListUpdate(); } void CTaskManagerDlg::UpdateTaskListProcessesStatus() { bool updated = false; int itemCount = m_taskListSort.GetItemCount(); for (int i = 0; i < itemCount; i++) { auto* itemData = reinterpret_cast(m_taskListSort.GetItemData(i)); bool isFrozen = IsProcessFrozen(itemData->processId); if (isFrozen == itemData->isFrozen) { continue; } itemData->isFrozen = isFrozen; std::wstring processNameFormatted = itemData->processName; if (isFrozen) { processNameFormatted += L' '; processNameFormatted += Functions::LoadStrFromRsrc(IDS_TASKDLG_PROCESS_SUSPENDED); } m_taskListSort.SetItemText(i, 1, processNameFormatted.c_str()); updated = true; } if (updated) { UpdateDialogAfterListUpdate(); } } void CTaskManagerDlg::UpdateDialogAfterListUpdate() { if (!m_dialogOptions.autonomousMode) { return; } int itemCount = m_taskListSort.GetItemCount(); if (itemCount == 0) { DestroyWindow(); return; } bool allProcessesAreFrozen = true; for (int i = 0; i < itemCount; i++) { auto* itemData = reinterpret_cast(m_taskListSort.GetItemData(i)); if (!itemData->isFrozen) { allProcessesAreFrozen = false; break; } } if (allProcessesAreFrozen) { if (m_showDlgPending) { KillTimer(Timer::kShowDlg); m_showDlgPending = false; } ShowWindow(SW_HIDE); return; } if (!IsWindowVisible()) { // Set timer to show the dialog. The delay is the defined amount of // delay in autonomousModeShowDelay, minus the earliest item age. This // is to avoid showing the dialog when items come and go - the delay // will always be updated and the dialog will never be shown. ULONGLONG earliestCreationTime = ULONGLONG_MAX; for (int i = 0; i < itemCount; i++) { auto* itemData = reinterpret_cast(m_taskListSort.GetItemData(i)); ULONGLONG creationTime = itemData->creationTime; if (creationTime < earliestCreationTime) { earliestCreationTime = creationTime; } } ULONGLONG currentTime = wil::filetime::to_int64(wil::filetime::get_system_time()); UINT delay = std::max(m_dialogOptions.autonomousModeShowDelay, kAutonomousModeShowDelayMin); if (earliestCreationTime <= currentTime) { ULONGLONG msSinceEarliestCreationTime = wil::filetime::convert_100ns_to_msec(currentTime - earliestCreationTime); if (msSinceEarliestCreationTime >= delay) { delay = 0; } else { delay -= static_cast(msSinceEarliestCreationTime); } } SetTimer(Timer::kShowDlg, delay); m_showDlgPending = true; } } ================================================ FILE: src/windhawk/app/task_manager_dlg.h ================================================ #pragma once #include "resource.h" class CTaskManagerDlg : public CDialogImpl, public CDialogResize { public: enum { IDD = IDD_TASK_MANAGER }; BEGIN_DLGRESIZE_MAP(CTaskManagerDlg) DLGRESIZE_CONTROL(IDC_TASK_LIST, DLSZ_SIZE_X | DLSZ_SIZE_Y) DLGRESIZE_CONTROL(IDOK, DLSZ_MOVE_X | DLSZ_MOVE_Y) END_DLGRESIZE_MAP() enum class DataSource { kModStatus, kModTask, }; using DlgCallback = std::function; // Wait before showing the autonomous dialog in case the data is short // lived. static constexpr int kAutonomousModeShowDelayDefault = 2000; static constexpr int kAutonomousModeShowDelayMin = 400; struct DialogOptions { DataSource dataSource = DataSource::kModStatus; bool autonomousMode = false; int autonomousModeShowDelay = kAutonomousModeShowDelayDefault; DWORD sessionManagerProcessId{}; ULONGLONG sessionManagerProcessCreationTime{}; DlgCallback runButtonCallback; DlgCallback finalMessageCallback; }; static bool IsDataSourceEmpty(DataSource dataSource); CTaskManagerDlg(DialogOptions dialogOptions); void LoadLanguageStrings(); void DataChanged(); private: enum class Timer { kUpdateProcessesStatus = 1, kRefreshList, kShowDlg, }; BEGIN_MSG_MAP_EX(CTaskManagerDlg) CHAIN_MSG_MAP(CDialogResize) MSG_WM_INITDIALOG(OnInitDialog) MSG_WM_DESTROY(OnDestroy) MSG_WM_TIMER(OnTimer) MSG_WM_DPICHANGED(OnDpiChanged) COMMAND_ID_HANDLER_EX(IDOK, OnOK) COMMAND_ID_HANDLER_EX(IDCANCEL, OnCancel) NOTIFY_HANDLER_EX(IDC_TASK_LIST, NM_RCLICK, OnListRightClick) END_MSG_MAP() BOOL OnInitDialog(CWindow wndFocus, LPARAM lInitParam); void OnDestroy(); void OnTimer(UINT_PTR nIDEvent); void OnDpiChanged(UINT nDpiX, UINT nDpiY, PRECT pRect); void OnOK(UINT uNotifyCode, int nID, CWindow wndCtl); void OnCancel(UINT uNotifyCode, int nID, CWindow wndCtl); LRESULT OnListRightClick(LPNMHDR pnmh); void OnFinalMessage(HWND hWnd) override; UINT_PTR SetTimer(Timer nIDEvent, UINT nElapse, TIMERPROC lpfnTimer = nullptr); BOOL KillTimer(Timer nIDEvent); void ReloadMainIcon(); void PlaceWindowAtTrayArea(); void InitTaskList(); void LoadTaskList(); bool LoadTaskItemFromMetadataFile(const std::filesystem::path& filePath, int itemIndex); void AddItemToList(int itemIndex, PCWSTR filePath, PCWSTR mod, PCWSTR processName, DWORD processId, PCWSTR status, FILETIME creationTime); void RefreshTaskList(); void UpdateTaskListProcessesStatus(); void UpdateDialogAfterListUpdate(); const DialogOptions m_dialogOptions; CSortListViewCtrl m_taskListSort; bool m_refreshListOnDataChangePending = false; bool m_showDlgPending = false; }; ================================================ FILE: src/windhawk/app/toolkit_dlg.cpp ================================================ #include "stdafx.h" #include "toolkit_dlg.h" #include "functions.h" namespace { int AutoSizeStaticHeight(CStatic stat) { CRect rc; stat.GetWindowRect(&rc); CString str; stat.GetWindowText(str); CRect rcNew(rc); { CDC dc = stat.GetDC(); CFontHandle oldFont(dc.SelectFont(stat.GetFont())); dc.DrawText(str, str.GetLength(), &rcNew, DT_WORDBREAK | DT_EXPANDTABS | DT_NOCLIP | DT_CALCRECT); dc.SelectFont(oldFont); } if (rcNew.Height() == rc.Height()) { return 0; } stat.SetWindowPos(NULL, 0, 0, rc.Width(), rcNew.Height(), SWP_NOMOVE | SWP_NOACTIVATE | SWP_NOZORDER); return rcNew.Height() - rc.Height(); } } // namespace CToolkitDlg::CToolkitDlg(DialogOptions dialogOptions) : m_dialogOptions(std::move(dialogOptions)) {} void CToolkitDlg::LoadLanguageStrings() { SetWindowText(Functions::LoadStrFromRsrc(IDS_TOOLKITDLG_TITLE)); SetDlgItemText(IDOK, Functions::LoadStrFromRsrc(IDS_TOOLKITDLG_BUTTON_OPEN)); SetDlgItemText( IDC_TOOLKIT_LOADED_MODS, Functions::LoadStrFromRsrc(IDS_TOOLKITDLG_BUTTON_LOADED_MODS)); SetDlgItemText(IDC_TOOLKIT_EXIT, Functions::LoadStrFromRsrc(IDS_TOOLKITDLG_BUTTON_EXIT)); SetDlgItemText(IDC_TOOLKIT_SAFE_MODE, Functions::LoadStrFromRsrc(IDS_TOOLKITDLG_BUTTON_SAFE_MODE)); SetDlgItemText(IDC_TOOLKIT_CLOSE, Functions::LoadStrFromRsrc(IDS_TOOLKITDLG_BUTTON_CLOSE)); if (m_dialogOptions.showTaskbarCrashExplanation) { UINT windowDpi = Functions::GetDpiForWindowWithFallback(m_hWnd); const int extraWidth = MulDiv(100, windowDpi, 96); CRect rc; CStatic explanationStatic{GetDlgItem(IDC_TOOLKIT_EXPLANATION)}; explanationStatic.GetWindowRect(&rc); explanationStatic.SetWindowPos( nullptr, 0, 0, rc.Width() + extraWidth, rc.Height(), SWP_NOMOVE | SWP_NOZORDER | SWP_NOACTIVATE); explanationStatic.SetWindowText( Functions::LoadStrFromRsrc(IDS_TOOLKITDLG_EXPLANATION_CRASH)); AutoSizeStaticHeight(explanationStatic); explanationStatic.ShowWindow(SW_SHOW); explanationStatic.GetWindowRect(&rc); int offsetY = rc.Height() + MulDiv(12, windowDpi, 96); for (int controlId : { IDOK, IDC_TOOLKIT_LOADED_MODS, IDC_TOOLKIT_EXIT, IDC_TOOLKIT_SAFE_MODE, IDC_TOOLKIT_CLOSE, }) { CWindow control = GetDlgItem(controlId); control.GetWindowRect(&rc); ::MapWindowPoints(nullptr, m_hWnd, (POINT*)&rc, 2); CPoint ptMove = rc.TopLeft(); ptMove.Offset(extraWidth / 2, offsetY); control.SetWindowPos(nullptr, ptMove.x, ptMove.y, 0, 0, SWP_NOSIZE | SWP_NOZORDER | SWP_NOACTIVATE); } GetWindowRect(&rc); rc.top -= offsetY / 2; rc.bottom += offsetY / 2 + offsetY % 2; rc.left -= extraWidth / 2; rc.right += extraWidth / 2 + extraWidth % 2; SetWindowPos(nullptr, rc, SWP_NOZORDER | SWP_NOACTIVATE); } bool languageRightToLeft = Functions::IsRightToLeftLanguage(GetThreadUILanguage()); Functions::ApplyDialogLayoutRtl(*this, languageRightToLeft); } bool CToolkitDlg::WasActive() { return m_wasActive; } void CToolkitDlg::Close() { DestroyWindow(); } BOOL CToolkitDlg::OnInitDialog(CWindow wndFocus, LPARAM lInitParam) { ReloadMainIcon(); SetWindowPos(HWND_TOPMOST, 0, 0, 0, 0, SWP_NOSIZE | SWP_NOMOVE | SWP_NOACTIVATE); // PlaceWindowAtTrayArea(); CenterWindow(); LoadLanguageStrings(); return !m_dialogOptions.createInactive; } void CToolkitDlg::OnDestroy() { // From GDI handle checks, not all icons are freed automatically. ::DestroyIcon(SetIcon(nullptr, TRUE)); ::DestroyIcon(SetIcon(nullptr, FALSE)); } void CToolkitDlg::OnActivate(UINT nState, BOOL bMinimized, CWindow wndOther) { switch (nState) { case WA_ACTIVE: case WA_CLICKACTIVE: m_wasActive = true; break; } } void CToolkitDlg::OnDpiChanged(UINT nDpiX, UINT nDpiY, PRECT pRect) { ReloadMainIcon(); } void CToolkitDlg::OnOK(UINT uNotifyCode, int nID, CWindow wndCtl) { if (m_dialogOptions.runButtonCallback) { m_dialogOptions.runButtonCallback(m_hWnd); } } void CToolkitDlg::OnLoadedMods(UINT uNotifyCode, int nID, CWindow wndCtl) { if (m_dialogOptions.loadedModsButtonCallback) { m_dialogOptions.loadedModsButtonCallback(m_hWnd); } } void CToolkitDlg::OnExit(UINT uNotifyCode, int nID, CWindow wndCtl) { if (m_dialogOptions.exitButtonCallback) { m_dialogOptions.exitButtonCallback(m_hWnd); } } void CToolkitDlg::OnSafeMode(UINT uNotifyCode, int nID, CWindow wndCtl) { if (m_dialogOptions.safeModeButtonCallback) { m_dialogOptions.safeModeButtonCallback(m_hWnd); } } void CToolkitDlg::OnClose(UINT uNotifyCode, int nID, CWindow wndCtl) { Close(); } void CToolkitDlg::OnFinalMessage(HWND hWnd) { if (m_dialogOptions.finalMessageCallback) { m_dialogOptions.finalMessageCallback(m_hWnd); } } void CToolkitDlg::ReloadMainIcon() { UINT dpi = Functions::GetDpiForWindowWithFallback(m_hWnd); CIconHandle mainIcon; mainIcon.LoadIconWithScaleDown( IDR_MAINFRAME, Functions::GetSystemMetricsForDpiWithFallback(SM_CXICON, dpi), Functions::GetSystemMetricsForDpiWithFallback(SM_CYICON, dpi)); CIcon prevMainIcon = SetIcon(mainIcon, TRUE); CIconHandle mainIconSmall; mainIconSmall.LoadIconWithScaleDown( IDR_MAINFRAME, Functions::GetSystemMetricsForDpiWithFallback(SM_CXSMICON, dpi), Functions::GetSystemMetricsForDpiWithFallback(SM_CYSMICON, dpi)); CIcon prevMainIconSmall = SetIcon(mainIconSmall, FALSE); } void CToolkitDlg::PlaceWindowAtTrayArea() { CRect windowRect; GetWindowRect(&windowRect); CRect workAreaRect; ::SystemParametersInfo(SPI_GETWORKAREA, 0, &workAreaRect, 0); int margin = 8; windowRect.MoveToXY(workAreaRect.right - windowRect.Width() - margin, workAreaRect.bottom - windowRect.Height() - margin); SetWindowPos(nullptr, windowRect, SWP_NOSIZE | SWP_NOZORDER | SWP_NOACTIVATE); } ================================================ FILE: src/windhawk/app/toolkit_dlg.h ================================================ #pragma once #include "resource.h" class CToolkitDlg : public CDialogImpl { public: enum { IDD = IDD_TOOLKIT }; using DlgCallback = std::function; struct DialogOptions { bool createInactive = false; bool showTaskbarCrashExplanation = false; DlgCallback runButtonCallback; DlgCallback loadedModsButtonCallback; DlgCallback exitButtonCallback; DlgCallback safeModeButtonCallback; DlgCallback finalMessageCallback; }; CToolkitDlg(DialogOptions dialogOptions); void LoadLanguageStrings(); bool WasActive(); void Close(); private: BEGIN_MSG_MAP_EX(CToolkitDlg) MSG_WM_INITDIALOG(OnInitDialog) MSG_WM_DESTROY(OnDestroy) MSG_WM_ACTIVATE(OnActivate) MSG_WM_DPICHANGED(OnDpiChanged) COMMAND_ID_HANDLER_EX(IDOK, OnOK) COMMAND_ID_HANDLER_EX(IDC_TOOLKIT_LOADED_MODS, OnLoadedMods) COMMAND_ID_HANDLER_EX(IDC_TOOLKIT_EXIT, OnExit) COMMAND_ID_HANDLER_EX(IDC_TOOLKIT_SAFE_MODE, OnSafeMode) COMMAND_ID_HANDLER_EX(IDC_TOOLKIT_CLOSE, OnClose) END_MSG_MAP() BOOL OnInitDialog(CWindow wndFocus, LPARAM lInitParam); void OnDestroy(); void OnActivate(UINT nState, BOOL bMinimized, CWindow wndOther); void OnDpiChanged(UINT nDpiX, UINT nDpiY, PRECT pRect); void OnOK(UINT uNotifyCode, int nID, CWindow wndCtl); void OnLoadedMods(UINT uNotifyCode, int nID, CWindow wndCtl); void OnExit(UINT uNotifyCode, int nID, CWindow wndCtl); void OnSafeMode(UINT uNotifyCode, int nID, CWindow wndCtl); void OnClose(UINT uNotifyCode, int nID, CWindow wndCtl); void OnFinalMessage(HWND hWnd) override; void ReloadMainIcon(); void PlaceWindowAtTrayArea(); const DialogOptions m_dialogOptions; bool m_wasActive = false; }; ================================================ FILE: src/windhawk/app/tray_icon.cpp ================================================ #include "stdafx.h" #include "tray_icon.h" #include "functions.h" #include "resource.h" AppTrayIcon::AppTrayIcon(HWND hWnd, UINT uCallbackMsg, bool hidden /*= false*/) { ReloadIcons(hWnd); m_nid.cbSize = sizeof(NOTIFYICONDATA); m_nid.hWnd = hWnd; m_nid.uID = 1; m_nid.uFlags = NIF_MESSAGE | NIF_ICON | NIF_TIP | NIF_STATE | NIF_SHOWTIP; m_nid.uCallbackMessage = uCallbackMsg; m_nid.hIcon = m_trayIcon; m_nid.uVersion = NOTIFYICON_VERSION_4; wcscpy_s(m_nid.szTip, L"Windhawk"); m_nid.dwState = hidden ? NIS_HIDDEN : 0; m_nid.dwStateMask = NIS_HIDDEN; m_nid.hBalloonIcon = m_balloonIcon; } void AppTrayIcon::Create() { Shell_NotifyIcon(NIM_ADD, &m_nid); Shell_NotifyIcon(NIM_SETVERSION, &m_nid); } void AppTrayIcon::Modify() { Shell_NotifyIcon(NIM_MODIFY, &m_nid); } void AppTrayIcon::UpdateIcons(HWND hWnd) { bool usingNotificationIcon = m_nid.hIcon == m_trayIconWithNotification; ReloadIcons(hWnd); if (usingNotificationIcon) { m_nid.hIcon = m_trayIconWithNotification; } else { m_nid.hIcon = m_trayIcon; } m_nid.hBalloonIcon = m_balloonIcon; } void AppTrayIcon::Hide(bool hidden) { if (hidden) { m_nid.dwState |= NIS_HIDDEN; } else { m_nid.dwState &= ~NIS_HIDDEN; } Shell_NotifyIcon(NIM_MODIFY, &m_nid); } void AppTrayIcon::SetNotificationIconAndTooltip(PCWSTR pText) { if (pText) { m_nid.hIcon = m_trayIconWithNotification; _snwprintf_s(m_nid.szTip, _TRUNCATE, L"%s - Windhawk", pText); } else { m_nid.hIcon = m_trayIcon; wcscpy_s(m_nid.szTip, L"Windhawk"); } Shell_NotifyIcon(NIM_MODIFY, &m_nid); } void AppTrayIcon::ShowNotificationMessage(PCWSTR pText) { m_nid.uFlags |= NIF_INFO; wcsncpy_s(m_nid.szInfo, pText, _TRUNCATE); wcscpy_s(m_nid.szInfoTitle, L"Windhawk"); m_nid.dwInfoFlags = NIIF_USER | NIIF_LARGE_ICON; Shell_NotifyIcon(NIM_MODIFY, &m_nid); m_nid.uFlags &= ~NIF_INFO; } void AppTrayIcon::Remove() { Shell_NotifyIcon(NIM_DELETE, &m_nid); } AppTrayIcon::TrayAction AppTrayIcon::HandleMsg(WPARAM wParam, LPARAM lParam) { DWORD tickCount; WORD notificationEvent = LOWORD(lParam); switch (notificationEvent) { case NIN_SELECT: case NIN_KEYSELECT: // Prevent multiple actions for accidental double clicks. tickCount = GetTickCount(); if (tickCount - m_lastClickTickCount <= 400) { return TrayAction::kNone; } m_lastClickTickCount = tickCount; return TrayAction::kDefault; case NIN_BALLOONUSERCLICK: return TrayAction::kBalloon; case WM_CONTEXTMENU: return TrayAction::kContextMenu; } return TrayAction::kNone; } void AppTrayIcon::ReloadIcons(HWND hWnd) { HWND hTaskbarWnd = FindWindow(L"Shell_TrayWnd", nullptr); UINT dpi = Functions::GetDpiForWindowWithFallback(hTaskbarWnd ? hTaskbarWnd : hWnd); m_trayIcon = nullptr; m_trayIcon.LoadIconWithScaleDown( IDR_MAINFRAME, Functions::GetSystemMetricsForDpiWithFallback(SM_CXSMICON, dpi), Functions::GetSystemMetricsForDpiWithFallback(SM_CYSMICON, dpi)); m_balloonIcon = nullptr; m_balloonIcon.LoadIconWithScaleDown( IDR_MAINFRAME, Functions::GetSystemMetricsForDpiWithFallback(SM_CXICON, dpi), Functions::GetSystemMetricsForDpiWithFallback(SM_CYICON, dpi)); m_trayIconWithNotification = nullptr; m_trayIconWithNotification.LoadIconWithScaleDown( IDI_NOTIFICATION, Functions::GetSystemMetricsForDpiWithFallback(SM_CXSMICON, dpi), Functions::GetSystemMetricsForDpiWithFallback(SM_CYSMICON, dpi)); } ================================================ FILE: src/windhawk/app/tray_icon.h ================================================ #pragma once class AppTrayIcon { public: enum class TrayAction { kNone, kDefault, kBalloon, kContextMenu, }; static inline constexpr size_t kMaxNotificationTooltipSize = ARRAYSIZE(NOTIFYICONDATA::szInfo); AppTrayIcon(HWND hWnd, UINT uCallbackMsg, bool hidden = false); void Create(); void Modify(); void UpdateIcons(HWND hWnd); void Hide(bool hidden); void SetNotificationIconAndTooltip(PCWSTR pText); void ShowNotificationMessage(PCWSTR pText); void Remove(); TrayAction HandleMsg(WPARAM wParam, LPARAM lParam); private: void ReloadIcons(HWND hWnd); CIcon m_trayIcon; CIcon m_balloonIcon; CIcon m_trayIconWithNotification; NOTIFYICONDATA m_nid{}; DWORD m_lastClickTickCount = 0; }; ================================================ FILE: src/windhawk/app/ui_control.cpp ================================================ #include "stdafx.h" #include "ui_control.h" #include "logger.h" #include "storage_manager.h" using json = nlohmann::ordered_json; namespace { const json uiSettings = { {"telemetry.telemetryLevel", "off"}, {"update.mode", "none"}, {"update.showReleaseNotes", false}, {"extensions.autoCheckUpdates", false}, {"extensions.autoUpdate", false}, {"files.autoSave", "afterDelay"}, {"window.title", "${dirty}${activeEditorShort}${separator}${appName}"}, {"workbench.enableExperiments", false}, {"workbench.settings.enableNaturalLanguageSearch", false}, {"workbench.editor.restoreViewState", false}, {"workbench.tips.enabled", false}, {"workbench.startupEditor", "none"}, {"workbench.layoutControl.enabled", false}, {"security.workspace.trust.enabled", false}, {"editor.inlayHints.enabled", "off"}, {"editor.tabSize", 4}, {"editor.insertSpaces", true}, {"editor.detectIndentation", false}, {"clangd.path", "${env:WINDHAWK_COMPILER_PATH}\\bin\\clangd.exe"}, {"clangd.arguments", {"-header-insertion=never"}}, {"clangd.checkUpdates", false}, {"window.menuBarVisibility", "compact"}, {"workbench.activityBar.visible", false}, {"workbench.editor.showTabs", false}, {"workbench.statusBar.visible", false}, {"git.enabled", false}, {"git.showProgress", false}, {"git.decorations.enabled", false}, {"git.ignoreMissingGitWarning", true}, {"git.ignoreLegacyWarning", true}, {"git.ignoreWindowsGit27Warning", true}, }; const json uiSettingsToMigrate = { {"clangd.path", "${env:WINDHAWK_UI_PATH}" "\\resources\\app\\extensions\\clangd\\clangd\\bin\\clangd.exe"}, }; void MakeSureDirectoryExists(const std::filesystem::path& directory) { if (!std::filesystem::is_directory(directory)) { try { std::filesystem::create_directories(directory); } catch (const std::exception&) { if (!std::filesystem::is_directory(directory)) { throw; } // An exception was thrown, but the folder now exists. This // can happen when e.g. not all the path is accessible. } } } void PrepareUISettings(const std::filesystem::path& uiDataPath) { std::filesystem::path settingsPath = uiDataPath / L"user-data" / L"User"; MakeSureDirectoryExists(settingsPath); settingsPath /= L"settings.json"; json settingsJson; { std::ifstream settingsFile(settingsPath); if (settingsFile) { try { settingsFile >> settingsJson; } catch (const std::exception& e) { LOG(L"Parsing settings.json failed: %S", e.what()); } } } if (!settingsJson.is_object()) { settingsJson = json::object(); } bool updatedData = false; for (auto& [key, value] : uiSettings.items()) { bool updateValue = !settingsJson.contains(key); if (!updateValue) { auto it = uiSettingsToMigrate.find(key); if (it != uiSettingsToMigrate.end() && settingsJson[key] == *it) { updateValue = true; } } if (updateValue) { settingsJson[key] = value; updatedData = true; } } if (updatedData) { std::ofstream userProfileFile(settingsPath); if (userProfileFile) { userProfileFile << std::setw(4) << settingsJson; } } } BOOL IsArm64NativeMachine() { using IsWow64Process2_t = BOOL(WINAPI*)( HANDLE hProcess, USHORT * pProcessMachine, USHORT * pNativeMachine); IsWow64Process2_t pIsWow64Process2 = nullptr; HMODULE kernel32Module = GetModuleHandle(L"kernel32.dll"); if (kernel32Module) { pIsWow64Process2 = reinterpret_cast( GetProcAddress(kernel32Module, "IsWow64Process2")); } if (!pIsWow64Process2) { // ARM64 OSes should have IsWow64Process2. return FALSE; } USHORT processMachine = 0; USHORT nativeMachine = 0; return pIsWow64Process2(GetCurrentProcess(), &processMachine, &nativeMachine) && nativeMachine == IMAGE_FILE_MACHINE_ARM64; } } // namespace namespace UIControl { void RunUI() { auto uiDataPath = StorageManager::GetInstance().GetUIDataPath(); PrepareUISettings(uiDataPath); // Will be passed to VSCode to make it use the specified folder for data // storage. SetEnvironmentVariable(L"VSCODE_PORTABLE", uiDataPath.c_str()); // Will be used to locate the clangd executable. auto uiPath = StorageManager::GetInstance().GetUIPath(); SetEnvironmentVariable(L"WINDHAWK_UI_PATH", uiPath.c_str()); auto compilerPath = StorageManager::GetInstance().GetCompilerPath(); SetEnvironmentVariable(L"WINDHAWK_COMPILER_PATH", compilerPath.c_str()); static bool arm64Enabled = IsArm64NativeMachine(); if (arm64Enabled) { SetEnvironmentVariable(L"WINDHAWK_ARM64_ENABLED", L"1"); } auto uiExePath = uiPath / L"VSCodium.exe"; // If the VSCodium executable doesn't exist, try the VSCode executable. if (GetFileAttributes(uiExePath.c_str()) == INVALID_FILE_ATTRIBUTES && GetLastError() == ERROR_FILE_NOT_FOUND) { uiExePath = uiPath / L"Code.exe"; // If VSCode executable doesn't exist, give up. THROW_LAST_ERROR_IF(GetFileAttributes(uiExePath.c_str()) == INVALID_FILE_ATTRIBUTES && GetLastError() == ERROR_FILE_NOT_FOUND); } auto editorWorkspacePath = StorageManager::GetInstance().GetEditorWorkspacePath(); MakeSureDirectoryExists(editorWorkspacePath); // The --locale command line switch is needed to avoid the "Install // language pack to change the display language" message if the OS // locale is not English. // // The --no-sandbox, --disable-gpu-sandbox command line switches seem to fix // a bug that sometimes causes VSCode to be stuck with an empty window when // launched: // https://github.com/ramensoftware/windhawk/issues/26 // VSCode reference: // https://github.com/microsoft/vscode/issues/122951 // Also, from the FAQ: // > Q: Unable to run as admin when AppLocker is enabled // > A: With the introduction of process sandboxing (discussed in this blog // post) running as administrator is currently unsupported when AppLocker is // configured due to a limitation of the runtime sandbox. You can refer to // Chromium issue #740132 for additional context. If your work requires that // you run VS Code from an elevated terminal, you can launch code with // --no-sandbox --disable-gpu-sandbox as a workaround. // https://github.com/microsoft/vscode-docs/blob/vnext/docs/setup/windows.md#unable-to-run-as-admin-when-applocker-is-enabled std::wstring commandLine = L"\"" + uiExePath.native() + L"\" \"" + editorWorkspacePath.native() + L"\" --locale=en --no-sandbox --disable-gpu-sandbox"; STARTUPINFO si = {sizeof(STARTUPINFO)}; wil::unique_process_information process; THROW_IF_WIN32_BOOL_FALSE(CreateProcess( uiExePath.c_str(), commandLine.data(), nullptr, nullptr, FALSE, NORMAL_PRIORITY_CLASS, nullptr, nullptr, &si, &process)); } bool RunUIViaSchedTask() { // Access the Windows Task Service API by creating an instance of it and // attempt to connect to the Task Scheduler service on the local machine. wil::com_ptr taskService = wil::CoCreateInstance(CLSID_TaskScheduler); THROW_IF_FAILED(taskService->Connect(_variant_t(), _variant_t(), _variant_t(), _variant_t())); // Get a pointer to the root task folder, which is where the task resides. auto rootFolderPath = wil::make_bstr(L"\\"); wil::com_ptr rootFolder; THROW_IF_FAILED(taskService->GetFolder(rootFolderPath.get(), &rootFolder)); auto taskName = wil::make_bstr(L"WindhawkRunUITask"); wil::com_ptr task; THROW_IF_FAILED(rootFolder->GetTask(taskName.get(), &task)); AllowSetForegroundWindow(ASFW_ANY); wil::com_ptr runTask; HRESULT hr = task->RunEx(_variant_t(), TASK_RUN_AS_SELF, 0, _bstr_t(), &runTask); if (hr == SCHED_E_TASK_DISABLED) { return false; } THROW_IF_FAILED(hr); return true; } std::vector GetOpenUIWindows() { struct EnumWindowsParam { std::filesystem::path uiExePath1; std::filesystem::path uiExePath2; std::vector windows; }; auto uiPath = StorageManager::GetInstance().GetUIPath(); EnumWindowsParam enumWindowsParam = {uiPath / L"VSCodium.exe", uiPath / L"Code.exe"}; EnumWindows( [](HWND hWnd, LPARAM lParam) { auto& enumWindowsParam = *reinterpret_cast(lParam); if (!IsWindowVisible(hWnd)) { return TRUE; } WCHAR szClassName[32]; if (!GetClassName(hWnd, szClassName, _countof(szClassName)) || _wcsicmp(szClassName, L"Chrome_WidgetWin_1") != 0) { return TRUE; } DWORD dwProceccID; if (!GetWindowThreadProcessId(hWnd, &dwProceccID)) { return TRUE; } try { wil::unique_process_handle process(OpenProcess( PROCESS_QUERY_LIMITED_INFORMATION, FALSE, dwProceccID)); if (!process) { return TRUE; } std::filesystem::path fullProcessImageName = wil::QueryFullProcessImageName(process.get()); std::error_code ec; if (std::filesystem::equivalent(fullProcessImageName, enumWindowsParam.uiExePath1, ec) || std::filesystem::equivalent(fullProcessImageName, enumWindowsParam.uiExePath2, ec)) { enumWindowsParam.windows.push_back(hWnd); } } catch (const std::exception& e) { LOG(L"EnumWindows callback failed for window %08X: %S", static_cast(reinterpret_cast(hWnd)), e.what()); } return TRUE; }, reinterpret_cast(&enumWindowsParam)); return enumWindowsParam.windows; } bool BringUIToFront() { auto windows = GetOpenUIWindows(); if (windows.size() == 0) { return false; } for (HWND hWnd : windows) { if (IsIconic(hWnd)) { PostMessage(hWnd, WM_SYSCOMMAND, SC_RESTORE, 0); } SetForegroundWindow(hWnd); } return true; } void RunUIOrBringToFront(HWND hWnd, bool mustRunAsAdmin) { // If running, just bring to front. if (UIControl::BringUIToFront()) { return; } // If possible, just run the process. if (!mustRunAsAdmin) { UIControl::RunUI(); return; } // Try to trigger the scheduled task to avoid elevation. try { if (UIControl::RunUIViaSchedTask()) { return; } } catch (const std::exception& e) { LOG(L"RunUIViaSchedTask error: %S", e.what()); } // Elevate and run a process that will start the UI. auto modulePath = wil::GetModuleFileName(); PCWSTR commandLine = L"-run-ui"; int nResult = (int)(UINT_PTR)ShellExecute(hWnd, L"runas", modulePath.c_str(), commandLine, nullptr, SW_SHOWNORMAL); THROW_LAST_ERROR_IF(nResult <= 32 && GetLastError() != ERROR_CANCELLED); } bool CloseUI() { auto windows = GetOpenUIWindows(); bool succeeded = false; for (HWND hWnd : windows) { succeeded |= !!PostMessage(hWnd, WM_SYSCOMMAND, SC_CLOSE, 0); } return succeeded; } } // namespace UIControl ================================================ FILE: src/windhawk/app/ui_control.h ================================================ #pragma once namespace UIControl { void RunUI(); bool RunUIViaSchedTask(); std::vector GetOpenUIWindows(); bool BringUIToFront(); void RunUIOrBringToFront(HWND hWnd, bool mustRunAsAdmin); bool CloseUI(); } // namespace UIControl ================================================ FILE: src/windhawk/app/update_checker.cpp ================================================ #include "stdafx.h" #include "update_checker.h" #include "logger.h" #include "version.h" namespace { constexpr auto* kUpdateCheckerUrl = L"https://update.windhawk.net/versions.json"; USHORT GetNativeMachineImpl() { using IsWow64Process2_t = BOOL(WINAPI*)( HANDLE hProcess, USHORT * pProcessMachine, USHORT * pNativeMachine); IsWow64Process2_t pIsWow64Process2 = nullptr; HMODULE kernel32Module = GetModuleHandle(L"kernel32.dll"); if (kernel32Module) { pIsWow64Process2 = reinterpret_cast( GetProcAddress(kernel32Module, "IsWow64Process2")); } if (pIsWow64Process2) { USHORT processMachine = 0; USHORT nativeMachine = 0; if (pIsWow64Process2(GetCurrentProcess(), &processMachine, &nativeMachine)) { return nativeMachine; } return IMAGE_FILE_MACHINE_UNKNOWN; } #if defined(_M_IX86) BOOL isWow64Process = FALSE; if (IsWow64Process(GetCurrentProcess(), &isWow64Process)) { return isWow64Process ? IMAGE_FILE_MACHINE_AMD64 : IMAGE_FILE_MACHINE_I386; } #elif defined(_M_X64) return IMAGE_FILE_MACHINE_AMD64; #else // ARM64 OSes should have IsWow64Process2. Other architectures aren't // supported. #endif return IMAGE_FILE_MACHINE_UNKNOWN; } USHORT GetNativeMachine() { static USHORT nativeMachine = GetNativeMachineImpl(); return nativeMachine; } CWinHTTPSimpleOptions GetUpdateCheckerOptions(DWORD flags, const void* postData, size_t postDataSize) { CWinHTTPSimpleOptions options; options.sURL = kUpdateCheckerUrl; options.sUserAgent = L"Windhawk/" VER_FILE_VERSION_WSTR " ("; options.sUserAgent += std::to_wstring(GetNativeMachine()); if (flags & UpdateChecker::kFlagPortable) { options.sUserAgent += L"; portable"; } options.sUserAgent += L")"; if (postData && postDataSize > 0) { options.sVerb = L"POST"; options.lpOptional = postData; options.dwOptionalSize = static_cast(postDataSize); } return options; } } // namespace UpdateChecker::UpdateChecker(DWORD flags, std::function onUpdateCheckDone) : m_flags(flags), m_postedData(UserProfile::GetLocalUpdatedContentAsString()), m_httpSimple(GetUpdateCheckerOptions(m_flags, m_postedData.data(), m_postedData.length()), onUpdateCheckDone != nullptr), m_onUpdateCheckDone(std::move(onUpdateCheckDone)) { if (!m_postedData.empty()) { THROW_IF_FAILED(m_httpSimple.AddHeaders( L"Content-Type: application/json", -1L, WINHTTP_ADDREQ_FLAG_ADD)); } if (m_onUpdateCheckDone) { THROW_IF_FAILED(m_httpSimple.SendRequest([this] { OnRequestDone(); })); } else { m_httpSimple.SendRequest(nullptr); if (ShouldRetryWithAGetRequest()) { m_httpSimpleGetRequest = std::make_unique( GetUpdateCheckerOptions(m_flags, nullptr, 0), false); m_httpSimpleGetRequest->SendRequest(nullptr); } } } void UpdateChecker::Abort() { m_aborted = true; m_httpSimple.Abort(); { std::lock_guard guard(m_httpSimpleGetRequestMutex); if (m_httpSimpleGetRequest) { m_httpSimpleGetRequest->Abort(); } } } UpdateChecker::Result UpdateChecker::HandleResponse() { CWinHTTPSimple& httpSimple = m_httpSimpleGetRequest ? *m_httpSimpleGetRequest : m_httpSimple; Result result = {}; result.hrError = httpSimple.GetRequestResult(); result.httpStatusCode = httpSimple.GetLastStatusCode(); if (SUCCEEDED(result.hrError)) { try { const auto& response = httpSimple.GetResponse(); result.updateStatus = UserProfile::UpdateContentWithOnlineData( reinterpret_cast(response.data()), response.size()); } catch (const std::exception& e) { LOG(L"Handling server response failed: %S", e.what()); result.hrError = E_FAIL; } } return result; } bool UpdateChecker::ShouldRetryWithAGetRequest() { // If the server doesn't support POST requests, // it can return 405 NOT ALLOWED. // Try with a GET request. return m_httpSimple.GetRequestResult() == HRESULT_FROM_WIN32(ERROR_WINHTTP_INVALID_HEADER) && m_httpSimple.GetLastStatusCode() == 405; } void UpdateChecker::OnRequestDone() { if (ShouldRetryWithAGetRequest() && !m_aborted) { std::lock_guard guard(m_httpSimpleGetRequestMutex); if (!m_aborted) { try { m_httpSimpleGetRequest = std::make_unique( GetUpdateCheckerOptions(m_flags, nullptr, 0), true); THROW_IF_FAILED(m_httpSimpleGetRequest->SendRequest( [this] { m_onUpdateCheckDone(); })); } catch (const std::exception& e) { m_httpSimpleGetRequest.reset(); LOG(L"Get request failed: %S", e.what()); m_onUpdateCheckDone(); } return; } } m_onUpdateCheckDone(); } ================================================ FILE: src/windhawk/app/update_checker.h ================================================ #pragma once #include "userprofile.h" #include "winhttpsimple.h" class UpdateChecker { public: struct Result { HRESULT hrError; DWORD httpStatusCode; UserProfile::UpdateStatus updateStatus; }; enum { kFlagPortable = 1, }; UpdateChecker(DWORD flags, std::function onUpdateCheckDone); void Abort(); Result HandleResponse(); private: bool ShouldRetryWithAGetRequest(); void OnRequestDone(); std::atomic m_aborted = false; DWORD m_flags = 0; std::string m_postedData; CWinHTTPSimple m_httpSimple; std::unique_ptr m_httpSimpleGetRequest; std::mutex m_httpSimpleGetRequestMutex; std::function m_onUpdateCheckDone; }; ================================================ FILE: src/windhawk/app/userprofile.cpp ================================================ #include "stdafx.h" #include "userprofile.h" #include "functions.h" #include "logger.h" #include "storage_manager.h" #include "version.h" using json = nlohmann::json; namespace { bool ValidateUserId(const std::string& id) { GUID guid; return SUCCEEDED(IIDFromString( CStringW(id.c_str(), static_cast(id.length())), &guid)); } std::string GenerateUserId() { GUID guid; if (FAILED(CoCreateGuid(&guid))) { return {}; } // GUID to string: https://stackoverflow.com/a/12934635 const CComBSTR guidBstr(guid); // Converts from binary GUID to BSTR const CStringA guidStr( guidBstr); // Converts from BSTR to appropriate string return guidStr.GetString(); } std::string GetCurrentOSVersion() { ULONG majorVersion = 0; ULONG minorVersion = 0; ULONG buildNumber = 0; Functions::GetNtVersionNumbers(&majorVersion, &minorVersion, &buildNumber); return std::to_string(majorVersion) + "." + std::to_string(minorVersion) + "." + std::to_string(buildNumber); } json ReadUserProfileJsonFromFile( const std::filesystem::path& userProfileJsonPath) { json userProfileJson; std::ifstream userProfileFile(userProfileJsonPath); if (userProfileFile) { try { userProfileFile >> userProfileJson; } catch (const std::exception& e) { LOG(L"Parsing userprofile.json failed: %S", e.what()); } } if (!userProfileJson.is_object()) { userProfileJson = json::object(); } return userProfileJson; } json GetLocalUpdatedContent() { auto userProfileJsonPath = StorageManager::GetInstance().GetUserProfileJsonPath(); json userProfileJson = ReadUserProfileJsonFromFile(userProfileJsonPath); bool updatedData = false; // Update user id if necessary. auto& id = userProfileJson["id"]; if (!id.is_string() || !ValidateUserId(id.get())) { id = GenerateUserId(); updatedData = true; } // Update OS version if necessary. auto& os = userProfileJson["os"]; auto currentOsVersion = GetCurrentOSVersion(); if (os != currentOsVersion) { os = currentOsVersion; updatedData = true; } // Update app version if necessary. auto& app = userProfileJson["app"]; if (!app.is_object()) { app = json::object(); updatedData = true; } auto& version = app["version"]; if (version != VER_FILE_VERSION_STR) { version = VER_FILE_VERSION_STR; updatedData = true; } // Save data. if (updatedData) { std::ofstream userProfileFile(userProfileJsonPath); if (userProfileFile) { userProfileFile << std::setw(2) << userProfileJson; } else { LOG(L"Updating userprofile.json failed (%s)", userProfileJsonPath.c_str()); } } return userProfileJson; } // https://stackoverflow.com/a/54067471 // Method to compare two version strings. bool version_less_than(std::string v1, std::string v2) { size_t i = 0, j = 0; while (i < v1.length() || j < v2.length()) { int acc1 = 0, acc2 = 0; while (i < v1.length() && v1[i] != '.') { acc1 = acc1 * 10 + (v1[i] - '0'); i++; } while (j < v2.length() && v2[j] != '.') { acc2 = acc2 * 10 + (v2[j] - '0'); j++; } if (acc1 < acc2) { return true; } if (acc1 > acc2) { return false; } ++i; ++j; } return false; } } // namespace namespace UserProfile { std::string GetLocalUpdatedContentAsString() { return GetLocalUpdatedContent().dump(2); } UpdateStatus UpdateContentWithOnlineData(PCSTR onlineData, size_t onlineDataLength) { UpdateStatus updateStatus{}; const json onlineDataJson = json::parse(onlineData, onlineData + onlineDataLength); auto userProfileJsonPath = StorageManager::GetInstance().GetUserProfileJsonPath(); json userProfileJson = ReadUserProfileJsonFromFile(userProfileJsonPath); bool updatedData = false; // Update app latest version if necessary. { std::string onlineLatestVersion; auto& onlineApp = onlineDataJson.at("app"); if (onlineApp.is_string()) { onlineLatestVersion = onlineApp.get(); } else { onlineLatestVersion = onlineApp.at("version").get(); } auto& app = userProfileJson["app"]; if (!app.is_object()) { app = json::object(); updatedData = true; } auto& latestVersion = app["latestVersion"]; std::string prevLatestVersion = latestVersion.is_string() ? latestVersion.get() : ""; if (latestVersion != onlineLatestVersion) { latestVersion = onlineLatestVersion; updatedData = true; } if (!onlineLatestVersion.empty()) { auto version = app.find("version"); if (version != app.end() && version->is_string() && version_less_than(version->get(), onlineLatestVersion)) { updateStatus.appUpdateAvailable = true; if (prevLatestVersion.empty() || *version == prevLatestVersion) { updateStatus.newUpdatesFound = true; } } } } // Update mods latest version if necessary. auto& mods = userProfileJson["mods"]; if (!mods.is_object()) { mods = json::object(); updatedData = true; } for (auto& [key, value] : onlineDataJson.at("mods").items()) { auto it = mods.find(key); if (it == mods.end()) { continue; } auto& mod = *it; if (!mod.is_object()) { mod = json::object(); updatedData = true; } std::string onlineLatestModVersion; if (value.is_string()) { onlineLatestModVersion = value.get(); } else { onlineLatestModVersion = value.at("metadata").at("version").get(); } auto& latestModVersion = mod["latestVersion"]; std::string prevLatestModVersion = latestModVersion.is_string() ? latestModVersion.get() : ""; if (latestModVersion != onlineLatestModVersion) { latestModVersion = onlineLatestModVersion; updatedData = true; } if (!onlineLatestModVersion.empty()) { auto modVersion = mod.find("version"); if (modVersion != mod.end() && *modVersion != onlineLatestModVersion) { updateStatus.modUpdatesAvailable++; if (prevLatestModVersion.empty() || *modVersion == prevLatestModVersion) { updateStatus.newUpdatesFound = true; } } } } // Save data. if (updatedData) { std::ofstream userProfileFile(userProfileJsonPath); if (userProfileFile) { userProfileFile << std::setw(2) << userProfileJson; } else { LOG(L"Updating userprofile.json failed (%s)", userProfileJsonPath.c_str()); } } return updateStatus; } UpdateStatus GetUpdateStatus() { UpdateStatus updateStatus{}; const json userProfileJson = GetLocalUpdatedContent(); // Check app update. { auto app = userProfileJson.find("app"); if (app != userProfileJson.end() && app->is_object()) { auto version = app->find("version"); auto latestVersion = app->find("latestVersion"); if (version != app->end() && latestVersion != app->end() && version->is_string() && latestVersion->is_string() && *latestVersion != "" && version_less_than(version->get(), latestVersion->get())) { updateStatus.appUpdateAvailable = true; } } } // Check mod updates. auto mods = userProfileJson.find("mods"); if (mods != userProfileJson.end() && mods->is_object()) { for (auto& [key, mod] : mods->items()) { if (mod.is_object()) { auto modVersion = mod.find("version"); auto latestModVersion = mod.find("latestVersion"); if (modVersion != mod.end() && latestModVersion != mod.end() && modVersion->is_string() && latestModVersion->is_string() && *latestModVersion != "" && *modVersion != *latestModVersion) { updateStatus.modUpdatesAvailable++; } } } } return updateStatus; } } // namespace UserProfile ================================================ FILE: src/windhawk/app/userprofile.h ================================================ #pragma once namespace UserProfile { struct UpdateStatus { bool appUpdateAvailable; int modUpdatesAvailable; bool newUpdatesFound; }; std::string GetLocalUpdatedContentAsString(); UpdateStatus UpdateContentWithOnlineData(PCSTR onlineData, size_t onlineDataLength); UpdateStatus GetUpdateStatus(); } // namespace UserProfile ================================================ FILE: src/windhawk/app/winhttpsimple.h ================================================ #ifndef __WINHTTPSIMPLE_H__ #define __WINHTTPSIMPLE_H__ struct CWinHTTPSimpleOptions { std::wstring sURL; std::wstring sVerb; // GET/POST etc. std::wstring sUserAgent; std::wstring sReferrer; std::vector AcceptTypes; ULONGLONG nDownloadStartPos = 0; // Offset to resume the download at bool bNoURLRedirect = FALSE; // Set to true if you want to disable URL redirection following std::wstring sFileToUpload; // The path of the file to upload std::wstring sFileToDownloadInto; // The path of the file to download into LPCVOID lpOptional = NULL; // Optional data to send immediately after the request headers DWORD dwOptionalSize = 0; // The size in bytes of lpOptional LPCVOID lpRequest = NULL; // The in memory data to send in the HTTP request DWORD dwRequestSize = 0; // The size in bytes of lpRequest double dbLimit = 0; // For bandwidth throttling, The value in KB/Second to limit the connection to DWORD dwAccessType = WINHTTP_ACCESS_TYPE_DEFAULT_PROXY; // WINHTTP_ACCESS_TYPE_XXX, proxy/direct connection type std::wstring sProxyServer; // The server for Proxy authentication std::wstring sProxyUserName; // The username to use for Proxy authentication std::wstring sProxyPassword; // The password to use for Proxy authentication DWORD dwProxyPreauthenticationScheme = WINHTTP_AUTH_SCHEME_NEGOTIATE; // The authentication scheme to use for proxy preauthentication bool bProxyPreauthentication = TRUE; // Should we supply credentials on the first request for the Proxy rather than starting out with anonymous credentials // and only authenticating when challenged std::wstring sHTTPUserName; // The username to use for HTTP authentication std::wstring sHTTPPassword; // the password to use for HTTP authentication DWORD dwHTTPPreauthenticationScheme = WINHTTP_AUTH_SCHEME_NEGOTIATE; // The authentication scheme to use for HTTP server preauthentication bool bHTTPPreauthentication = TRUE; // Should we supply credentials on the first request for the HTTP Server rather than starting out with anonymous credentials // and only authenticating when challenged }; class CWinHTTPSimple { class CSimpleWinHTTPDownloader : public WinHTTPWrappers::CSyncDownloader { public: CSimpleWinHTTPDownloader() : m_hHandleClosedEvent(CreateEvent(NULL, TRUE, FALSE, NULL)) { if (!m_hHandleClosedEvent) AtlThrowLastWin32(); } ~CSimpleWinHTTPDownloader() { Close(); WaitForSingleObject(m_hHandleClosedEvent, INFINITE); } HRESULT SendRequest(_In_ std::function doneCallback, _In_reads_bytes_opt_(dwOptionalLength) LPVOID lpOptional = WINHTTP_NO_REQUEST_DATA, _In_ DWORD dwOptionalLength = 0) { // Call the base class HRESULT hr = __super::SendRequest(lpOptional, dwOptionalLength); if (FAILED(hr)) { m_hr = hr; } else { m_hr = E_PENDING; if (doneCallback) { m_doneCallback = std::move(doneCallback); } } return hr; } HRESULT SendRequestSync(_In_reads_bytes_opt_(dwOptionalLength) LPVOID lpOptional = WINHTTP_NO_REQUEST_DATA, _In_ DWORD dwOptionalLength = 0) override { // Call the base class HRESULT hr = __super::SendRequestSync(lpOptional, dwOptionalLength); m_hr = hr; return hr; } HRESULT GetHresult() { return m_hr; } protected: virtual HRESULT OnCallback(_In_ HINTERNET hInternet, _In_ DWORD dwInternetStatus, _In_opt_ LPVOID lpvStatusInformation, _In_ DWORD dwStatusInformationLength) { // Let the base class do the real work HRESULT ret = __super::OnCallback(hInternet, dwInternetStatus, lpvStatusInformation, dwStatusInformationLength); if (dwInternetStatus == WINHTTP_CALLBACK_STATUS_HANDLE_CLOSING) { SetEvent(m_hHandleClosedEvent); } return ret; } virtual HRESULT OnCallbackComplete(_In_ HRESULT hr, _In_ HINTERNET hInternet, _In_ DWORD dwInternetStatus, _In_opt_ LPVOID lpvStatusInformation, _In_ DWORD dwStatusInformationLength) { m_hr = hr; // Let the base class do the cleanup HRESULT ret = __super::OnCallbackComplete(hr, hInternet, dwInternetStatus, lpvStatusInformation, dwStatusInformationLength); if (m_doneCallback) { m_doneCallback(); m_doneCallback = nullptr; } return ret; } ATL::CHandle m_hHandleClosedEvent; std::function m_doneCallback; HRESULT m_hr = E_FAIL; }; bool m_bAsync; WinHTTPWrappers::CSession m_session; WinHTTPWrappers::CConnection m_connection; std::vector m_optionalData; std::vector m_requestData; CSimpleWinHTTPDownloader m_downloadRequest; public: CWinHTTPSimple(CWinHTTPSimpleOptions options, bool bAsync = false) : m_bAsync(bAsync), m_downloadRequest() { // Crack the URL provided into its constituent parts URL_COMPONENTS urlComponents = { sizeof(urlComponents) }; urlComponents.dwSchemeLength = static_cast(-1); urlComponents.dwHostNameLength = static_cast(-1); urlComponents.dwUrlPathLength = static_cast(-1); urlComponents.dwExtraInfoLength = static_cast(-1); BOOL bSuccess = WinHttpCrackUrl(options.sURL.c_str(), 0, 0, &urlComponents); ATLENSURE_THROW(bSuccess, AtlHresultFromLastError()); // Create the session object HRESULT hr = m_session.Initialize(options.sUserAgent.c_str(), options.dwAccessType, options.sProxyServer.c_str() ? options.sProxyServer.c_str() : WINHTTP_NO_PROXY_NAME, WINHTTP_NO_PROXY_BYPASS, bAsync ? WINHTTP_FLAG_ASYNC : 0); ATLENSURE_SUCCEEDED(hr); // Create the connection object hr = m_connection.Initialize(m_session, std::wstring(urlComponents.lpszHostName, urlComponents.dwHostNameLength).c_str(), urlComponents.nPort); ATLENSURE_SUCCEEDED(hr); // Fill in all the member variables if (options.lpOptional) { m_optionalData.assign(reinterpret_cast(options.lpOptional), reinterpret_cast(options.lpOptional) + options.dwOptionalSize); } if (options.lpRequest) { m_requestData.assign(reinterpret_cast(options.lpRequest), reinterpret_cast(options.lpRequest) + options.dwRequestSize); } m_downloadRequest.m_sHTTPUserName = std::move(options.sHTTPUserName); m_downloadRequest.m_sHTTPPassword = std::move(options.sHTTPPassword); m_downloadRequest.m_sProxyUserName = std::move(options.sProxyUserName); m_downloadRequest.m_sProxyPassword = std::move(options.sProxyPassword); m_downloadRequest.m_nDownloadStartPos = options.nDownloadStartPos; m_downloadRequest.m_bHTTPPreauthentication = options.bHTTPPreauthentication; m_downloadRequest.m_dwHTTPPreauthenticationScheme = options.dwHTTPPreauthenticationScheme; m_downloadRequest.m_bProxyPreauthentication = options.bProxyPreauthentication; m_downloadRequest.m_dwProxyPreauthenticationScheme = options.dwProxyPreauthenticationScheme; m_downloadRequest.m_bNoURLRedirect = options.bNoURLRedirect; m_downloadRequest.m_sFileToDownloadInto = std::move(options.sFileToDownloadInto); m_downloadRequest.m_sFileToUpload = std::move(options.sFileToUpload); m_downloadRequest.m_lpRequest = m_requestData.data(); m_downloadRequest.m_dwRequestSize = options.dwRequestSize; m_downloadRequest.m_dbLimit = options.dbLimit; size_t nAcceptTypes = options.AcceptTypes.size(); std::vector ppszAcceptTypes; if (nAcceptTypes) { ppszAcceptTypes.reserve(nAcceptTypes + 1); for (const auto& str : options.AcceptTypes) ppszAcceptTypes.push_back(str.c_str()); ppszAcceptTypes.push_back(nullptr); } std::wstring sUrl = std::wstring(urlComponents.lpszUrlPath, urlComponents.dwUrlPathLength) + std::wstring(urlComponents.lpszExtraInfo, urlComponents.dwExtraInfoLength); // Create the request hr = m_downloadRequest.Initialize(m_connection, sUrl.c_str(), options.sVerb.length() ? options.sVerb.c_str() : NULL, NULL, options.sReferrer.length() ? options.sReferrer.c_str() : WINHTTP_NO_REFERER, nAcceptTypes ? ppszAcceptTypes.data() : WINHTTP_DEFAULT_ACCEPT_TYPES, urlComponents.nScheme == INTERNET_SCHEME_HTTPS ? WINHTTP_FLAG_SECURE : 0); ATLENSURE_SUCCEEDED(hr); } HRESULT AddHeaders(LPCWSTR pwszHeaders, DWORD dwHeadersLength, DWORD dwModifiers) { return m_downloadRequest.AddHeaders(pwszHeaders, dwHeadersLength, dwModifiers); } HRESULT SendRequest(std::function doneCallback) { void* lpOptional = NULL; DWORD dwOptionalLength = 0; if (!m_optionalData.empty()) { lpOptional = m_optionalData.data(); dwOptionalLength = static_cast(m_optionalData.size()); } if (m_bAsync) { return m_downloadRequest.SendRequest(std::move(doneCallback), lpOptional, dwOptionalLength); } else { return m_downloadRequest.SendRequestSync(lpOptional, dwOptionalLength); } } HRESULT QueryHeaders(DWORD dwInfoLevel, LPCWSTR pwszName, LPVOID lpBuffer, DWORD& dwBufferLength, DWORD* lpdwIndex) { return m_downloadRequest.QueryHeaders(dwInfoLevel, pwszName, lpBuffer, dwBufferLength, lpdwIndex); } HRESULT GetRequestResult() { return m_downloadRequest.GetHresult(); } DWORD GetLastStatusCode() { bool valid; DWORD code = m_downloadRequest.GetLastStatusCode(valid); return valid ? code : 0; } const std::vector& GetResponse() { return m_downloadRequest.m_Response; } void Abort() { m_downloadRequest.Close(); } }; #endif // __WINHTTPSIMPLE_H__ ================================================ FILE: src/windhawk/build.bat ================================================ @ECHO OFF REM // Usage: REM // build.bat Debug "" REM // build.bat Release :rebuild SET "VSCMD_START_DIR=%CD%" IF "%FrameworkVersion%" == "" CALL "%ProgramFiles%\Microsoft Visual Studio\2022\Community\VC\Auxiliary\Build\vcvars64.bat" MSBuild.exe "windhawk.sln" /m /t:"app%~2" /p:Configuration="%~1" /p:Platform="Win32" || GOTO fail MSBuild.exe "windhawk.sln" /m /t:"engine%~2" /p:Configuration="%~1" /p:Platform="Win32" || GOTO fail MSBuild.exe "windhawk.sln" /m /t:"engine%~2" /p:Configuration="%~1" /p:Platform="x64" || GOTO fail MSBuild.exe "windhawk.sln" /m /t:"engine%~2" /p:Configuration="%~1" /p:Platform="ARM64" || GOTO fail REM // Done EXIT /b 0 :fail EXIT /b %ERRORLEVEL% ================================================ FILE: src/windhawk/engine/_exports.def ================================================ LIBRARY "windhawk.dll" EXPORTS InjectInit GlobalHookSessionStart GlobalHookSessionHandleNewProcesses GlobalHookSessionEnd InternalWh_IsLogEnabled InternalWh_Log InternalWh_GetIntValue InternalWh_SetIntValue InternalWh_GetStringValue InternalWh_SetStringValue InternalWh_GetBinaryValue InternalWh_SetBinaryValue InternalWh_DeleteValue InternalWh_GetModStoragePath InternalWh_GetIntSetting InternalWh_GetStringSetting InternalWh_FreeStringSetting InternalWh_SetFunctionHook InternalWh_RemoveFunctionHook InternalWh_ApplyHookOperations InternalWh_FindFirstSymbol InternalWh_FindFirstSymbol2 InternalWh_FindFirstSymbol3 InternalWh_FindFirstSymbol4 InternalWh_FindNextSymbol InternalWh_FindNextSymbol2 InternalWh_FindCloseSymbol InternalWh_HookSymbols InternalWh_Disasm InternalWh_GetUrlContent InternalWh_FreeUrlContent ================================================ FILE: src/windhawk/engine/all_processes_injector.cpp ================================================ #include "stdafx.h" #include "all_processes_injector.h" #include "dll_inject.h" #include "functions.h" #include "logger.h" #include "process_lists.h" #include "session_private_namespace.h" #include "storage_manager.h" #include "var_init_once.h" #ifndef STATUS_NO_MORE_ENTRIES #define STATUS_NO_MORE_ENTRIES ((NTSTATUS)0x8000001AL) #endif namespace { struct __declspec(align(16)) MY_CONTEXT_AMD64 { DWORD64 dummy1[6]; DWORD ContextFlags; DWORD MxCsr; WORD SegCs; WORD SegDs; WORD SegEs; WORD SegFs; WORD SegGs; WORD SegSs; DWORD EFlags; DWORD64 dummy2[6]; DWORD64 Rax; DWORD64 Rcx; DWORD64 Rdx; DWORD64 Rbx; DWORD64 Rsp; DWORD64 Rbp; DWORD64 Rsi; DWORD64 Rdi; DWORD64 R8; DWORD64 R9; DWORD64 R10; DWORD64 R11; DWORD64 R12; DWORD64 R13; DWORD64 R14; DWORD64 R15; DWORD64 Rip; DWORD64 dummy3[122]; }; #define MY_CONTEXT_AMD64_CONTROL 0x100001 USHORT GetNativeMachineImpl() { using IsWow64Process2_t = BOOL(WINAPI*)( HANDLE hProcess, USHORT * pProcessMachine, USHORT * pNativeMachine); IsWow64Process2_t pIsWow64Process2 = nullptr; HMODULE kernel32Module = GetModuleHandle(L"kernel32.dll"); if (kernel32Module) { pIsWow64Process2 = reinterpret_cast( GetProcAddress(kernel32Module, "IsWow64Process2")); } if (pIsWow64Process2) { USHORT processMachine = 0; USHORT nativeMachine = 0; if (pIsWow64Process2(GetCurrentProcess(), &processMachine, &nativeMachine)) { return nativeMachine; } return IMAGE_FILE_MACHINE_UNKNOWN; } #if defined(_M_IX86) BOOL isWow64Process = FALSE; if (IsWow64Process(GetCurrentProcess(), &isWow64Process)) { return isWow64Process ? IMAGE_FILE_MACHINE_AMD64 : IMAGE_FILE_MACHINE_I386; } #elif defined(_M_X64) return IMAGE_FILE_MACHINE_AMD64; #else // ARM64 OSes should have IsWow64Process2. Other architectures aren't // supported. #endif return IMAGE_FILE_MACHINE_UNKNOWN; } USHORT GetNativeMachine() { STATIC_INIT_ONCE_TRIVIAL(USHORT, nativeMachine, GetNativeMachineImpl()); return nativeMachine; } // This function is used to get the address of the x64 stub of // RtlUserThreadStart on ARM64. It's done by creating a suspended process and // querying its initial instruction pointer. For details of why it's needed, // look for the mention of RtlUserThreadStart in // https://m417z.com/Implementing-Global-Injection-and-Hooking-in-Windows/. DWORD64 GetRtlUserThreadStart_x64OnArm64() { std::filesystem::path x64HelperPath = wil::GetModuleFileName(); x64HelperPath.replace_filename(L"windhawk-x64-helper.exe"); STARTUPINFO si = {sizeof(STARTUPINFO)}; wil::unique_process_information process; THROW_IF_WIN32_BOOL_FALSE( CreateProcess(x64HelperPath.c_str(), nullptr, nullptr, nullptr, FALSE, NORMAL_PRIORITY_CLASS | CREATE_SUSPENDED, nullptr, nullptr, &si, &process)); auto terminateProcessOnScopeExit = wil::scope_exit([&process] { TerminateProcess(process.hProcess, 0); }); #ifdef _M_IX86 auto ntdll = wow64pp::module_handle("ntdll.dll"); auto pNtGetContextThread = wow64pp::import(ntdll, "NtGetContextThread"); ARM64_NT_CONTEXT context; auto result64 = wow64pp::call_function( pNtGetContextThread, wow64pp::handle_to_uint64(process.hThread), wow64pp::ptr_to_uint64(&context)); NTSTATUS result = static_cast(result64); THROW_IF_NTSTATUS_FAILED(result); return context.Pc; #else #error "Unsupported architecture" #endif // _M_IX86 } void GetThreadContext64(HANDLE thread, CONTEXT* context) { STATIC_INIT_ONCE_TRIVIAL(DWORD64, pNtGetContextThread, []() { auto ntdll = wow64pp::module_handle("ntdll.dll"); return wow64pp::import(ntdll, "NtGetContextThread"); }()); auto result64 = wow64pp::call_function(pNtGetContextThread, wow64pp::handle_to_uint64(thread), wow64pp::ptr_to_uint64(context)); NTSTATUS result = static_cast(result64); THROW_IF_NTSTATUS_FAILED(result); } HANDLE CreateProcessInitAPCMutex(DWORD processId, BOOL initialOwner) { WCHAR szMutexName[SessionPrivateNamespace::kPrivateNamespaceMaxLen + sizeof("\\ProcessInitAPCMutex-pid=1234567890")]; int mutexNamePos = SessionPrivateNamespace::MakeName(szMutexName, GetCurrentProcessId()); swprintf_s(szMutexName + mutexNamePos, ARRAYSIZE(szMutexName) - mutexNamePos, L"\\ProcessInitAPCMutex-pid=%u", processId); wil::unique_hlocal secDesc; THROW_IF_WIN32_BOOL_FALSE( Functions::GetFullAccessSecurityDescriptor(&secDesc, nullptr)); SECURITY_ATTRIBUTES secAttr = {sizeof(SECURITY_ATTRIBUTES)}; secAttr.lpSecurityDescriptor = secDesc.get(); secAttr.bInheritHandle = FALSE; wil::unique_mutex_nothrow mutex( CreateMutex(&secAttr, initialOwner, szMutexName)); THROW_LAST_ERROR_IF_NULL(mutex); return mutex.release(); } HANDLE OpenProcessInitAPCMutex(DWORD processId, DWORD desiredAccess) { WCHAR szMutexName[SessionPrivateNamespace::kPrivateNamespaceMaxLen + sizeof("\\ProcessInitAPCMutex-pid=1234567890")]; int mutexNamePos = SessionPrivateNamespace::MakeName(szMutexName, GetCurrentProcessId()); swprintf_s(szMutexName + mutexNamePos, ARRAYSIZE(szMutexName) - mutexNamePos, L"\\ProcessInitAPCMutex-pid=%u", processId); return OpenMutex(desiredAccess, FALSE, szMutexName); } } // namespace AllProcessesInjector::AllProcessesInjector() { HMODULE hNtdll = GetModuleHandle(L"ntdll.dll"); THROW_LAST_ERROR_IF_NULL(hNtdll); m_NtGetNextProcess = (NtGetNextProcess_t)GetProcAddress(hNtdll, "NtGetNextProcess"); THROW_LAST_ERROR_IF_NULL(m_NtGetNextProcess); m_NtGetNextThread = (NtGetNextThread_t)GetProcAddress(hNtdll, "NtGetNextThread"); THROW_LAST_ERROR_IF_NULL(m_NtGetNextThread); #ifdef _M_IX86 USHORT nativeMachine = GetNativeMachine(); if (nativeMachine == IMAGE_FILE_MACHINE_I386) { m_pRtlUserThreadStart = wow64pp::ptr_to_uint64( GetProcAddress(hNtdll, "RtlUserThreadStart")); } else { auto ntdll = wow64pp::module_handle("ntdll.dll"); m_pRtlUserThreadStart = wow64pp::import(ntdll, "RtlUserThreadStart"); if (nativeMachine == IMAGE_FILE_MACHINE_ARM64) { m_pRtlUserThreadStart_x64OnArm64 = GetRtlUserThreadStart_x64OnArm64(); } } #else #error "Unsupported architecture" #endif // _M_IX86 THROW_LAST_ERROR_IF(m_pRtlUserThreadStart == 0); m_appPrivateNamespace = SessionPrivateNamespace::Create(GetCurrentProcessId()); auto settings = StorageManager::GetInstance().GetAppConfig(L"Settings"); m_includePattern = settings->GetString(L"Include").value_or(L""); m_excludePattern = settings->GetString(L"Exclude").value_or(L""); m_threadAttachExemptPattern = settings->GetString(L"ThreadAttachExempt").value_or(L""); if (!settings->GetInt(L"InjectIntoCriticalProcesses").value_or(0)) { if (!m_excludePattern.empty()) { m_excludePattern += L'|'; } m_excludePattern += ProcessLists::kCriticalProcesses; } if (!settings->GetInt(L"InjectIntoIncompatiblePrograms").value_or(0)) { if (!m_excludePattern.empty()) { m_excludePattern += L'|'; } m_excludePattern += ProcessLists::kIncompatiblePrograms; } if (!settings->GetInt(L"InjectIntoGames").value_or(0)) { if (!m_excludePattern.empty()) { m_excludePattern += L'|'; } m_excludePattern += ProcessLists::kGames; } } int AllProcessesInjector::InjectIntoNewProcesses() noexcept { int count = 0; while (true) { // Note: If we don't have the required permissions, the process is // skipped. HANDLE hNewProcess; NTSTATUS status = m_NtGetNextProcess( m_lastEnumeratedProcess.get(), SYNCHRONIZE | DllInject::kProcessAccess, 0, 0, &hNewProcess); if (!SUCCEEDED_NTSTATUS(status)) { if (status != STATUS_NO_MORE_ENTRIES) { LOG(L"NtGetNextProcess error: %08X", status); } break; } m_lastEnumeratedProcess.reset(hNewProcess); if (WaitForSingleObject(hNewProcess, 0) == WAIT_OBJECT_0) { // Process is no longer alive. continue; } DWORD dwNewProcessId = GetProcessId(hNewProcess); if (dwNewProcessId == 0) { LOG(L"GetProcessId error: %u", GetLastError()); continue; } std::wstring processImageName; switch (HRESULT hr = wil::QueryFullProcessImageName( hNewProcess, 0, processImageName)) { case S_OK: break; case HRESULT_FROM_WIN32(ERROR_ACCESS_DENIED): // Often means the process is terminating. VERBOSE(L"Process %u is inaccessible (likely terminating)", dwNewProcessId); continue; // https://stackoverflow.com/a/74456572 case HRESULT_FROM_WIN32(ERROR_GEN_FAILURE): VERBOSE(L"Process %u is likely terminating", dwNewProcessId); continue; default: LOG(L"QueryFullProcessImageName error for process %u: %08X", dwNewProcessId, hr); continue; } if (ShouldSkipNewProcess(processImageName)) { VERBOSE(L"Skipping excluded process %u", dwNewProcessId); continue; } try { InjectIntoNewProcess(hNewProcess, dwNewProcessId, ShouldAttachExemptThread(processImageName)); count++; } catch (const wil::ResultException& e) { switch (e.GetErrorCode()) { // STATUS_PROCESS_IS_TERMINATING case 0xC000010A: VERBOSE(L"Process %u is terminating: %S", dwNewProcessId, e.what()); break; case HRESULT_FROM_WIN32(ERROR_ACCESS_DENIED): // May happen if process is terminating. VERBOSE(L"Access denied for process %u: %S", dwNewProcessId, e.what()); break; default: LOG(L"Error handling a new process %u: %S", dwNewProcessId, e.what()); break; } } catch (const std::exception& e) { LOG(L"Error handling a new process %u: %S", dwNewProcessId, e.what()); } } return count; } bool AllProcessesInjector::ShouldSkipNewProcess( std::wstring_view processImageName) const { return Functions::DoesPathMatchPattern(processImageName, m_excludePattern) && !Functions::DoesPathMatchPattern(processImageName, m_includePattern); } bool AllProcessesInjector::ShouldAttachExemptThread( std::wstring_view processImageName) const { return Functions::DoesPathMatchPattern(processImageName, m_threadAttachExemptPattern); } void AllProcessesInjector::InjectIntoNewProcess(HANDLE hProcess, DWORD dwProcessId, bool threadAttachExempt) { // We check whether the process began running or not. If it didn't, it's // supposed to have only one thread which has its instruction pointer at // RtlUserThreadStart. For other cases, we assume the main thread was // resumed. // // If the process didn't begin running, creating a remote thread might be // too early and unsafe. One known problem with this is with console apps - // if we trigger console initialization (KERNELBASE!ConsoleCommitState) // before the parent process notified csrss.exe // (KERNELBASE!CsrClientCallServer), csrss.exe returns an access denied // error and the parent's CreateProcess call fails. // // If the process is the current process, we skip this check since it // obviously began running, and we don't want to suspend the current thread // and cause a deadlock. wil::unique_process_handle suspendedThread; if (dwProcessId != GetCurrentProcessId()) { DWORD threadAccess = THREAD_SUSPEND_RESUME | THREAD_GET_CONTEXT | DllInject::kApcThreadsAccess; wil::unique_process_handle thread1; THROW_IF_NTSTATUS_FAILED( m_NtGetNextThread(hProcess, nullptr, threadAccess, 0, 0, &thread1)); wil::unique_process_handle thread2; NTSTATUS status = m_NtGetNextThread(hProcess, thread1.get(), threadAccess, 0, 0, &thread2); if (status == STATUS_NO_MORE_ENTRIES) { // Exactly one thread. DWORD previousSuspendCount = SuspendThread(thread1.get()); THROW_LAST_ERROR_IF(previousSuspendCount == (DWORD)-1); if (previousSuspendCount == 0) { // The thread was already running. ResumeThread(thread1.get()); } else { suspendedThread = std::move(thread1); } } else { THROW_IF_NTSTATUS_FAILED(status); } } if (suspendedThread) { auto suspendThreadCleanup = wil::scope_exit( [&suspendedThread] { ResumeThread(suspendedThread.get()); }); bool threadNotStartedYet = false; #ifdef _M_IX86 switch (GetNativeMachine()) { case IMAGE_FILE_MACHINE_I386: { CONTEXT c; c.ContextFlags = CONTEXT_CONTROL; THROW_IF_WIN32_BOOL_FALSE( GetThreadContext(suspendedThread.get(), &c)); if (c.Eip == m_pRtlUserThreadStart) { threadNotStartedYet = true; } break; } case IMAGE_FILE_MACHINE_AMD64: { MY_CONTEXT_AMD64 c; c.ContextFlags = MY_CONTEXT_AMD64_CONTROL; GetThreadContext64(suspendedThread.get(), (CONTEXT*)&c); if (c.Rip == m_pRtlUserThreadStart) { threadNotStartedYet = true; } break; } case IMAGE_FILE_MACHINE_ARM64: { ARM64_NT_CONTEXT c; c.ContextFlags = CONTEXT_ARM64_CONTROL; GetThreadContext64(suspendedThread.get(), (CONTEXT*)&c); if (c.Pc == m_pRtlUserThreadStart || c.Pc == m_pRtlUserThreadStart_x64OnArm64) { threadNotStartedYet = true; } break; } default: { throw std::runtime_error("Unsupported architecture"); } } #else #error "Unsupported architecture" #endif // _M_IX86 if (threadNotStartedYet) { wil::unique_mutex_nothrow mutex( CreateProcessInitAPCMutex(dwProcessId, TRUE)); if (GetLastError() == ERROR_ALREADY_EXISTS) { return; // APC was already created } auto mutexLock = mutex.ReleaseMutex_scope_exit(); DllInject::DllInject(hProcess, suspendedThread.get(), GetCurrentProcess(), mutex.get(), threadAttachExempt); VERBOSE(L"DllInject succeeded for new process %u via APC", dwProcessId); return; } } wil::unique_mutex_nothrow mutex( OpenProcessInitAPCMutex(dwProcessId, SYNCHRONIZE)); if (mutex) { return; // APC was already created } DllInject::DllInject(hProcess, nullptr, GetCurrentProcess(), nullptr, threadAttachExempt); VERBOSE(L"DllInject succeeded for new process %u via a remote thread", dwProcessId); } ================================================ FILE: src/windhawk/engine/all_processes_injector.h ================================================ #pragma once class AllProcessesInjector { public: AllProcessesInjector(); int InjectIntoNewProcesses() noexcept; private: bool ShouldSkipNewProcess(std::wstring_view processImageName) const; bool ShouldAttachExemptThread(std::wstring_view processImageName) const; void InjectIntoNewProcess(HANDLE hProcess, DWORD dwProcessId, bool threadAttachExempt); using NtGetNextProcess_t = NTSTATUS(NTAPI*)(_In_opt_ HANDLE ProcessHandle, _In_ ACCESS_MASK DesiredAccess, _In_ ULONG HandleAttributes, _In_ ULONG Flags, _Out_ PHANDLE NewProcessHandle); using NtGetNextThread_t = NTSTATUS(NTAPI*)(_In_ HANDLE ProcessHandle, _In_opt_ HANDLE ThreadHandle, _In_ ACCESS_MASK DesiredAccess, _In_ ULONG HandleAttributes, _In_ ULONG Flags, _Out_ PHANDLE NewThreadHandle); NtGetNextProcess_t m_NtGetNextProcess; NtGetNextThread_t m_NtGetNextThread; DWORD64 m_pRtlUserThreadStart = 0; DWORD64 m_pRtlUserThreadStart_x64OnArm64 = 0; wil::unique_private_namespace_destroy m_appPrivateNamespace; std::wstring m_includePattern; std::wstring m_excludePattern; std::wstring m_threadAttachExemptPattern; wil::unique_process_handle m_lastEnumeratedProcess; }; ================================================ FILE: src/windhawk/engine/customization_session.cpp ================================================ #include "stdafx.h" #include "customization_session.h" #include "functions.h" #include "logger.h" #include "session_private_namespace.h" extern HINSTANCE g_hDllInst; #ifndef STATUS_NO_MORE_ENTRIES #define STATUS_NO_MORE_ENTRIES ((NTSTATUS)0x8000001AL) #endif namespace { std::optional GetFirstThreadOfCurrentProcess(DWORD accessMask) { using NtGetNextThread_t = NTSTATUS(NTAPI*)( _In_ HANDLE ProcessHandle, _In_opt_ HANDLE ThreadHandle, _In_ ACCESS_MASK DesiredAccess, _In_ ULONG HandleAttributes, _In_ ULONG Flags, _Out_ PHANDLE NewThreadHandle); GET_PROC_ADDRESS_ONCE(NtGetNextThread_t, pNtGetNextThread, L"ntdll.dll", "NtGetNextThread"); if (!pNtGetNextThread) { LOG(L"Failed to get NtGetNextThread address"); return std::nullopt; } DWORD currentThreadId = GetCurrentThreadId(); HANDLE thread = nullptr; while (true) { HANDLE nextThread = nullptr; NTSTATUS status = pNtGetNextThread( GetCurrentProcess(), thread, THREAD_QUERY_LIMITED_INFORMATION | SYNCHRONIZE | accessMask, 0, 0, &nextThread); if (status == STATUS_NO_MORE_ENTRIES && !thread) { LOG(L"Failed to get first thread, likely a sandboxed process"); return std::nullopt; } if (thread) { CloseHandle(thread); } if (status == STATUS_NO_MORE_ENTRIES) { VERBOSE(L"Current process has no alive threads left"); return nullptr; } if (FAILED_NTSTATUS(status)) { LOG(L"NtGetNextThread failed: 0x%08X", status); return std::nullopt; } thread = nextThread; // Skip the current thread. if (GetThreadId(nextThread) == currentThreadId) { continue; } DWORD waitResult = WaitForSingleObject(nextThread, 0); if (waitResult == WAIT_OBJECT_0) { // Thread is terminated, continue to the next thread. continue; } if (waitResult == WAIT_TIMEOUT) { // Thread is alive. return nextThread; } LOG(L"WaitForSingleObject on thread failed: %u %u", waitResult, GetLastError()); CloseHandle(nextThread); return std::nullopt; } } bool CurrentProcessHasMitigationPolicy() { using GetProcessMitigationPolicy_t = BOOL(WINAPI*)( _In_ HANDLE hProcess, _In_ PROCESS_MITIGATION_POLICY MitigationPolicy, _Out_ LPVOID lpBuffer, _In_ SIZE_T dwLength); GET_PROC_ADDRESS_ONCE(GetProcessMitigationPolicy_t, pGetProcessMitigationPolicy, L"kernel32.dll", "GetProcessMitigationPolicy"); if (!pGetProcessMitigationPolicy) { LOG(L"Failed to get GetProcessMitigationPolicy address"); return false; } PROCESS_MITIGATION_DYNAMIC_CODE_POLICY policy; return pGetProcessMitigationPolicy(GetCurrentProcess(), ProcessDynamicCodePolicy, &policy, sizeof(policy)) && policy.ProhibitDynamicCode; } } // namespace // static void CustomizationSession::Start( bool runningFromAPC, bool threadAttachExempt, wil::unique_process_handle sessionManagerProcess, wil::unique_mutex_nothrow sessionMutex) { std::wstring semaphoreName = L"WindhawkCustomizationSessionSemaphore-pid=" + std::to_wstring(GetCurrentProcessId()); wil::unique_semaphore semaphore(1, 1, semaphoreName.c_str()); // We don't want to wait in APC context infinitely, since it will prevent // the process from launching. If we can't acquire the semaphore while // running from APC, it means that two Windhawk engines are being loaded // simultaneously, which is generally not supported. DWORD timeout = runningFromAPC ? 0 : INFINITE; wil::semaphore_release_scope_exit semaphoreLock = semaphore.acquire(nullptr, timeout); if (!semaphoreLock) { throw std::runtime_error( "Failed to acquire customization session semaphore"); } std::optional& session = GetInstance(); if (session) { throw std::logic_error( "Only one session is supported at any given time"); } session.emplace(ConstructorSecret{}, runningFromAPC, threadAttachExempt, std::move(sessionManagerProcess), std::move(sessionMutex)); session->StartInitialized(std::move(semaphore), std::move(semaphoreLock), runningFromAPC); } // static DWORD CustomizationSession::GetSessionManagerProcessId() { HANDLE sessionManagerProcess = ScopedStaticSessionManagerProcess::GetInstance().value().get(); DWORD processId = GetProcessId(sessionManagerProcess); THROW_LAST_ERROR_IF(processId == 0); return processId; } // static FILETIME CustomizationSession::GetSessionManagerProcessCreationTime() { HANDLE sessionManagerProcess = ScopedStaticSessionManagerProcess::GetInstance().value().get(); FILETIME creationTime; FILETIME exitTime; FILETIME kernelTime; FILETIME userTime; THROW_IF_WIN32_BOOL_FALSE(GetProcessTimes(sessionManagerProcess, &creationTime, &exitTime, &kernelTime, &userTime)); return creationTime; } // static bool CustomizationSession::IsEndingSoon() { HANDLE sessionManagerProcess = ScopedStaticSessionManagerProcess::GetInstance().value().get(); return WaitForSingleObject(sessionManagerProcess, 0) == WAIT_OBJECT_0; } CustomizationSession::CustomizationSession( ConstructorSecret constructorSecret, bool runningFromAPC, bool threadAttachExempt, wil::unique_process_handle sessionManagerProcess, wil::unique_mutex_nothrow sessionMutex) : m_threadAttachExempt(threadAttachExempt), m_scopedStaticSessionManagerProcess(std::move(sessionManagerProcess)), m_sessionMutex(std::move(sessionMutex)), m_privateNamespace(OpenSessionPrivateNamespace()), #ifdef WH_HOOKING_ENGINE_MINHOOK // If runningFromAPC, no other threads should be running, skip thread // freeze. m_minHookScopeInit(runningFromAPC ? MH_FREEZE_METHOD_NONE_UNSAFE : MH_FREEZE_METHOD_FAST_UNDOCUMENTED), #endif // WH_HOOKING_ENGINE_MINHOOK m_modsManager(), m_newProcessInjector(m_scopedStaticSessionManagerProcess) #ifdef WH_HOOKING_ENGINE_MINHOOK , m_minHookScopeApply() #endif // WH_HOOKING_ENGINE_MINHOOK { try { m_modsManager.AfterInit(); } catch (const std::exception& e) { LOG(L"AfterInit failed: %S", e.what()); } } CustomizationSession::~CustomizationSession() { try { m_modsManager.BeforeUninit(); } catch (const std::exception& e) { LOG(L"BeforeUninit failed: %S", e.what()); } } #ifdef WH_HOOKING_ENGINE_MINHOOK CustomizationSession::MinHookScopeInit::MinHookScopeInit( MH_THREAD_FREEZE_METHOD freezeMethod) { MH_STATUS status = MH_Initialize(); if (status != MH_OK) { LOG(L"MH_Initialize failed with %d", status); throw std::runtime_error("Failed to initialize MinHook"); } MH_SetThreadFreezeMethod(freezeMethod); #ifdef WH_HOOKING_ENGINE_MINHOOK_DETOURS MH_SetBulkOperationMode( /*continueOnError=*/TRUE, [](LPVOID pTarget, NTSTATUS detoursStatus) { LOG(L"Hooking operation failed for %p with status 0x%08X", pTarget, detoursStatus); }); #endif } CustomizationSession::MinHookScopeInit::~MinHookScopeInit() { MH_STATUS status = MH_Uninitialize(); if (status != MH_OK) { LOG(L"MH_Uninitialize failed with status %d", status); } } CustomizationSession::MinHookScopeApply::MinHookScopeApply() { MH_STATUS status = MH_ApplyQueuedEx(MH_ALL_IDENTS); if (status != MH_OK) { LOG(L"MH_ApplyQueuedEx failed with %d", status); } MH_SetThreadFreezeMethod(MH_FREEZE_METHOD_FAST_UNDOCUMENTED); } CustomizationSession::MinHookScopeApply::~MinHookScopeApply() { MH_STATUS status = MH_DisableHookEx(MH_ALL_IDENTS, MH_ALL_HOOKS); if (status != MH_OK) { LOG(L"MH_DisableHookEx failed with status %d", status); } } #endif // WH_HOOKING_ENGINE_MINHOOK CustomizationSession::MainLoopRunner::MainLoopRunner() noexcept { try { m_modConfigChangeNotification.emplace(); } catch (const std::exception& e) { LOG(L"ModConfigChangeNotification constructor failed: %S", e.what()); } } CustomizationSession::MainLoopRunner::Result CustomizationSession::MainLoopRunner::Run(HANDLE sessionManagerProcess, DWORD* lastThreadExitCode) noexcept { DWORD lastThreadExitCodeLocal = 0; while (true) { wil::unique_handle firstThread; auto maybeFirstThread = GetFirstThreadOfCurrentProcess(THREAD_QUERY_LIMITED_INFORMATION); if (maybeFirstThread) { firstThread.reset(*maybeFirstThread); if (!firstThread) { // No threads left in the process, we're done. if (lastThreadExitCode) { *lastThreadExitCode = lastThreadExitCodeLocal; } return Result::kCompleted; } } enum class WaitHandleId { kSessionManagerProcess, kFirstThread, kModConfigChangeNotification, kCount, }; constexpr size_t kMaxWaitHandlesCount = static_cast(WaitHandleId::kCount); DWORD waitHandlesCount = 0; HANDLE waitHandles[kMaxWaitHandlesCount]{}; WaitHandleId waitHandleIds[kMaxWaitHandlesCount]{}; waitHandles[waitHandlesCount] = sessionManagerProcess; waitHandleIds[waitHandlesCount] = WaitHandleId::kSessionManagerProcess; waitHandlesCount++; if (firstThread) { waitHandles[waitHandlesCount] = firstThread.get(); waitHandleIds[waitHandlesCount] = WaitHandleId::kFirstThread; waitHandlesCount++; } if (m_modConfigChangeNotification) { waitHandles[waitHandlesCount] = m_modConfigChangeNotification->GetHandle(); waitHandleIds[waitHandlesCount] = WaitHandleId::kModConfigChangeNotification; waitHandlesCount++; } DWORD waitResult = WaitForMultipleObjects(waitHandlesCount, waitHandles, FALSE, INFINITE); if (waitResult >= WAIT_OBJECT_0 && waitResult < WAIT_OBJECT_0 + waitHandlesCount) { switch (waitHandleIds[waitResult - WAIT_OBJECT_0]) { case WaitHandleId::kSessionManagerProcess: return Result::kCompleted; case WaitHandleId::kFirstThread: GetExitCodeThread(firstThread.get(), &lastThreadExitCodeLocal); continue; case WaitHandleId::kModConfigChangeNotification: // Wait for a bit before notifying about the change, in case // more config changes will follow. if (WaitForSingleObject(sessionManagerProcess, 200) == WAIT_OBJECT_0) { return Result::kCompleted; } return Result::kReloadModsAndSettings; } } LOG(L"WaitForMultipleObjects returned %u, last error %u", waitResult, GetLastError()); return Result::kError; } } bool CustomizationSession::MainLoopRunner::ContinueMonitoring() noexcept { if (!m_modConfigChangeNotification) { return false; } try { m_modConfigChangeNotification->ContinueMonitoring(); } catch (const std::exception& e) { LOG(L"ContinueMonitoring failed: %S", e.what()); m_modConfigChangeNotification.reset(); return false; } return true; } bool CustomizationSession::MainLoopRunner::CanRunAcrossThreads() noexcept { if (m_modConfigChangeNotification && !m_modConfigChangeNotification->CanMonitorAcrossThreads()) { return false; } return true; } // static std::optional& CustomizationSession::GetInstance() { // Use NoDestructorIfTerminating not only for performance reasons, but also // because it's not safe to destruct the session when the process // terminates. As part of the mods unloading, we access the mods and call // functions such as Wh_Uninit, but at this point, the mods' global variable // destructors have already run, so we might be accessing destructed // objects. Reference: https://stackoverflow.com/a/67999399 STATIC_INIT_ONCE( NoDestructorIfTerminating>, session); return **session; } wil::unique_private_namespace_close CustomizationSession::OpenSessionPrivateNamespace() { DWORD dwSessionManagerProcessId = GetSessionManagerProcessId(); if (dwSessionManagerProcessId == GetCurrentProcessId()) { // In the session manager process, the session manager creates the // private namespace, so no need to open it here. return nullptr; } return SessionPrivateNamespace::Open(dwSessionManagerProcessId); } void CustomizationSession::StartInitialized( wil::unique_semaphore semaphore, wil::semaphore_release_scope_exit semaphoreLock, bool runningFromAPC) noexcept { m_sessionSemaphore = std::move(semaphore); m_sessionSemaphoreLock = std::move(semaphoreLock); if (!runningFromAPC) { // No need to create a new thread, a dedicated thread was created for us // before injection. m_mainLoopRunner.emplace(); RunMainLoop(); DeleteThis(); return; } m_mainLoopRunner.emplace(); if (!m_mainLoopRunner->CanRunAcrossThreads()) { m_mainLoopRunner.reset(); } // Bump the reference count of the module to ensure that the module will // stay loaded as long as the thread is executing. HMODULE hDllInst; GetModuleHandleEx(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS, reinterpret_cast(g_hDllInst), &hDllInst); // Create a new thread with the THREAD_ATTACH_EXEMPT flag to prevent TLS and // DllMain callbacks from being invoked. Otherwise, they might cause a crash // if invoked too early, e.g. before CRT is initialized. If // threadAttachExempt is set, just keep running with this flag. If // threadAttachExempt isn't set, create a new thread without the flag once // some significant code runs, such as mod/config reload or unload, or any // mod callback. DWORD createThreadFlags = Functions::MY_REMOTE_THREAD_THREAD_ATTACH_EXEMPT; wil::unique_process_handle thread(Functions::MyCreateRemoteThread( GetCurrentProcess(), [](LPVOID pThis) -> DWORD { // Prevent the system from displaying the critical-error-handler // message box. A message box like this was appearing while trying // to load a dll in a process with the ProcessSignaturePolicy // mitigation, and it looked like this: // https://stackoverflow.com/q/38367847 SetThreadErrorMode(SEM_FAILCRITICALERRORS, nullptr); auto* this_ = reinterpret_cast(pThis); if (!this_->m_mainLoopRunner) { this_->m_mainLoopRunner.emplace(); } if (this_->m_threadAttachExempt) { this_->RunMainLoop(); this_->DeleteThis(); } else { this_->RunMainLoopAndDeleteThisWithThreadRecreate(); } FreeLibraryAndExitThread(g_hDllInst, this_->m_lastThreadExitCode); }, this, createThreadFlags)); if (!thread) { LOG(L"Thread creation failed: %u", GetLastError()); FreeLibrary(g_hDllInst); DeleteThis(); return; } Functions::SetThreadDescriptionIfAvailable( thread.get(), L"WindhawkMainLoopThreadAttachExempt"); } void CustomizationSession:: RunMainLoopAndDeleteThisWithThreadRecreate() noexcept { bool modConfigChanged = m_mainLoopRunner->Run(m_scopedStaticSessionManagerProcess, &m_lastThreadExitCode) == MainLoopRunner::Result::kReloadModsAndSettings; if (!m_mainLoopRunner->CanRunAcrossThreads()) { m_mainLoopRunner.reset(); } LPTHREAD_START_ROUTINE routine; if (modConfigChanged) { routine = [](LPVOID pThis) -> DWORD { SetThreadErrorMode(SEM_FAILCRITICALERRORS, nullptr); auto* this_ = reinterpret_cast(pThis); if (this_->m_mainLoopRunner) { this_->m_mainLoopRunner->ContinueMonitoring(); } else { this_->m_mainLoopRunner.emplace(); } if (CurrentProcessHasMitigationPolicy()) { LOG(L"Process prohibits dynamic code, cannot reload mods " L"safely"); } else { try { this_->m_modsManager.ReloadModsAndSettings(); } catch (const std::exception& e) { LOG(L"ReloadModsAndSettings failed: %S", e.what()); } } this_->RunMainLoop(); this_->DeleteThis(); FreeLibraryAndExitThread(g_hDllInst, this_->m_lastThreadExitCode); }; } else { routine = [](LPVOID pThis) -> DWORD { SetThreadErrorMode(SEM_FAILCRITICALERRORS, nullptr); auto* this_ = reinterpret_cast(pThis); this_->DeleteThis(); FreeLibraryAndExitThread(g_hDllInst, this_->m_lastThreadExitCode); }; } // Bump the reference count of the module to ensure that the module will // stay loaded as long as the thread is executing. HMODULE hDllInst; GetModuleHandleEx(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS, reinterpret_cast(g_hDllInst), &hDllInst); wil::unique_process_handle thread( Functions::MyCreateRemoteThread(GetCurrentProcess(), routine, this, 0)); if (!thread) { LOG(L"Thread creation failed: %u", GetLastError()); FreeLibrary(g_hDllInst); DeleteThis(); return; } Functions::SetThreadDescriptionIfAvailable(thread.get(), L"WindhawkMainLoop"); } void CustomizationSession::RunMainLoop() noexcept { while (true) { auto result = m_mainLoopRunner->Run(m_scopedStaticSessionManagerProcess, &m_lastThreadExitCode); if (result != MainLoopRunner::Result::kReloadModsAndSettings) { break; } m_mainLoopRunner->ContinueMonitoring(); if (CurrentProcessHasMitigationPolicy()) { LOG(L"Process prohibits dynamic code, cannot reload mods safely"); } else { try { m_modsManager.ReloadModsAndSettings(); } catch (const std::exception& e) { LOG(L"ReloadModsAndSettings failed: %S", e.what()); } } } VERBOSE(L"Exiting engine thread wait loop"); } void CustomizationSession::DeleteThis() noexcept { // If dynamic code is prohibited, removing hooks isn't possible, and // unloading the dll will cause crashes. As a workaround, leave the thread // hanging while the mitigation is in place. See: // https://github.com/ramensoftware/windhawk-mods/discussions/2084#discussioncomment-13621678 // // A better solution would be for the hooking library to handle this by // having a kill switch allocated in non-executable memory. Then, if the // hooks can't be removed, they can be disabled by setting the kill switch, // and the trampolines can be leaked. DWORD sleepTime = 1000; while (true) { if (!CurrentProcessHasMitigationPolicy()) { break; } LOG(L"Process prohibits dynamic code, cannot unload safely"); Sleep(sleepTime); sleepTime *= 2; if (sleepTime > 60000) { sleepTime = 60000; } } // Make sure the semaphore is only released after the object is destroyed. wil::unique_semaphore semaphore = std::move(m_sessionSemaphore); wil::semaphore_release_scope_exit semaphoreLock = std::move(m_sessionSemaphoreLock); GetInstance().reset(); } ================================================ FILE: src/windhawk/engine/customization_session.h ================================================ #pragma once #include "mods_manager.h" #include "new_process_injector.h" #include "no_destructor.h" #include "storage_manager.h" #include "var_init_once.h" class CustomizationSession { private: // https://devblogs.microsoft.com/oldnewthing/20220721-00/?p=106879 struct ConstructorSecret { explicit ConstructorSecret() = default; }; public: CustomizationSession(const CustomizationSession&) = delete; CustomizationSession& operator=(const CustomizationSession&) = delete; static void Start(bool runningFromAPC, bool threadAttachExempt, wil::unique_process_handle sessionManagerProcess, wil::unique_mutex_nothrow sessionMutex); static DWORD GetSessionManagerProcessId(); static FILETIME GetSessionManagerProcessCreationTime(); static bool IsEndingSoon(); // Must be public for std emplace and destruction, but shouldn't be used // outside of this file. CustomizationSession(ConstructorSecret constructorSecret, bool runningFromAPC, bool threadAttachExempt, wil::unique_process_handle sessionManagerProcess, wil::unique_mutex_nothrow sessionMutex); ~CustomizationSession(); private: // Used to hold a single process handle which can be accessed from static // functions. class ScopedStaticSessionManagerProcess { public: ScopedStaticSessionManagerProcess( const ScopedStaticSessionManagerProcess&) = delete; ScopedStaticSessionManagerProcess& operator=( const ScopedStaticSessionManagerProcess&) = delete; ScopedStaticSessionManagerProcess(wil::unique_process_handle handle) { GetInstance().emplace(std::move(handle)); } ~ScopedStaticSessionManagerProcess() { GetInstance().reset(); } static std::optional& GetInstance() { STATIC_INIT_ONCE(NoDestructorIfTerminating< std::optional>, handle); return **handle; } operator HANDLE() { return GetInstance().value().get(); } }; #ifdef WH_HOOKING_ENGINE_MINHOOK class MinHookScopeInit { public: MinHookScopeInit(const MinHookScopeInit&) = delete; MinHookScopeInit& operator=(const MinHookScopeInit&) = delete; MinHookScopeInit(MH_THREAD_FREEZE_METHOD freezeMethod); ~MinHookScopeInit(); }; class MinHookScopeApply { public: MinHookScopeApply(const MinHookScopeApply&) = delete; MinHookScopeApply& operator=(const MinHookScopeApply&) = delete; MinHookScopeApply(); ~MinHookScopeApply(); }; #endif // WH_HOOKING_ENGINE_MINHOOK class MainLoopRunner { public: MainLoopRunner() noexcept; enum class Result { kReloadModsAndSettings, kCompleted, kError, }; Result Run(HANDLE sessionManagerProcess, DWORD* lastThreadExitCode) noexcept; bool ContinueMonitoring() noexcept; bool CanRunAcrossThreads() noexcept; private: std::optional m_modConfigChangeNotification; }; static std::optional& GetInstance(); wil::unique_private_namespace_close OpenSessionPrivateNamespace(); void StartInitialized(wil::unique_semaphore semaphore, wil::semaphore_release_scope_exit semaphoreLock, bool runningFromAPC) noexcept; void RunMainLoopAndDeleteThisWithThreadRecreate() noexcept; void RunMainLoop() noexcept; void DeleteThis() noexcept; bool m_threadAttachExempt; ScopedStaticSessionManagerProcess m_scopedStaticSessionManagerProcess; wil::unique_mutex_nothrow m_sessionMutex; wil::unique_private_namespace_close m_privateNamespace; #ifdef WH_HOOKING_ENGINE_MINHOOK MinHookScopeInit m_minHookScopeInit; #endif // WH_HOOKING_ENGINE_MINHOOK ModsManager m_modsManager; NewProcessInjector m_newProcessInjector; #ifdef WH_HOOKING_ENGINE_MINHOOK MinHookScopeApply m_minHookScopeApply; #endif // WH_HOOKING_ENGINE_MINHOOK std::optional m_mainLoopRunner; DWORD m_lastThreadExitCode = 0; // Must be released after the singleton object is freed. See the careful // usage in DeleteThis. wil::unique_semaphore m_sessionSemaphore; wil::semaphore_release_scope_exit m_sessionSemaphoreLock; }; ================================================ FILE: src/windhawk/engine/dll_inject.cpp ================================================ #include "stdafx.h" #include "dll_inject.h" #include "functions.h" #include "logger.h" #include "storage_manager.h" #include "var_init_once.h" extern HINSTANCE g_hDllInst; namespace { #define PRE_X32SHELLCODE_ARGS_1_TO_3 \ "\x58" /* pop eax */ \ "\x59" /* pop ecx */ \ "\x6A\x00" /* push 0 */ \ "\x6A\x00" /* push 0 */ \ "\x51" /* push ecx */ \ "\x50" /* push eax */ #define PRE_X32SHELLCODE_VIRTUAL_FREE \ "\xFF\x74\x24\x0C" /* push dword ptr ss:[esp+C] */ \ "\xFF\x74\x24\x0C" /* push dword ptr ss:[esp+C] */ \ "\xFF\x74\x24\x0C" /* push dword ptr ss:[esp+C] */ \ "\xE8\x20\x00\x00\x00" /* call $+20 */ \ "\x85\xC0" /* test eax,eax */ \ "\x75\x03" /* jne $+3 */ \ "\xC2\x0C\x00" /* ret C */ \ "\x59" /* pop ecx */ \ "\x83\xC4\x0C" /* add esp,C */ \ "\x68\x00\x80\x00\x00" /* push 8000 */ \ "\x6A\x00" /* push 0 */ \ "\xE8\x00\x00\x00\x00" /* call $ */ \ "\x66\x81\x24\x24\x00\xF0" /* and word ptr ss:[esp],F000 */ \ "\x51" /* push ecx */ \ "\xFF\xE0" /* jmp eax */ // The 32-bit InjectShellcode function from the project in the inject_shellcode // subfolder. const BYTE x32Shellcode[] = PRE_X32SHELLCODE_ARGS_1_TO_3 PRE_X32SHELLCODE_VIRTUAL_FREE "\x55\x8B\xEC\x81\xEC\xDC\x01\x00\x00\x8B\x45\x08\x32\xD2\xC6\x45\xEA\x00" "\x88\x55\xE9\xA8\x01\x74\x0C\x83\xE0\xFE\xC6\x45\xEA\x01\x89\x45\x08\xEB" "\x0A\x83\x78\x04\x00\x0F\x95\xC2\x88\x55\xE9\x56\x64\x8B\x35\x30\x00\x00" "\x00\x89\xB5\xD4\xFE\xFF\xFF\x83\x7E\x0C\x00\x75\x09\x33\xC0\x5E\x8B\xE5" "\x5D\xC2\x0C\x00\x8D\x85\x70\xFF\xFF\xFF\xC7\x45\x80\x4B\x45\x52\x4E\x89" "\x85\x48\xFE\xFF\xFF\x33\xC9\x8D\x85\x30\xFF\xFF\xFF\xC7\x45\x84\x45\x4C" "\x33\x32\x89\x85\x4C\xFE\xFF\xFF\x8D\x45\x98\x89\x85\x50\xFE\xFF\xFF\x8D" "\x45\xA4\x89\x85\x54\xFE\xFF\xFF\x8D\x85\x60\xFF\xFF\xFF\x89\x85\x58\xFE" "\xFF\xFF\x8D\x85\x08\xFF\xFF\xFF\x89\x85\x5C\xFE\xFF\xFF\x8D\x45\x8C\x89" "\x85\x60\xFE\xFF\xFF\x8D\x85\xF4\xFE\xFF\xFF\x89\x85\x64\xFE\xFF\xFF\x8D" "\x85\x50\xFF\xFF\xFF\x89\x85\x68\xFE\xFF\xFF\x8D\x85\xCC\xFE\xFF\xFF\x89" "\x85\x24\xFE\xFF\xFF\x8D\x85\xC8\xFE\xFF\xFF\x53\x33\xDB\x89\x85\x28\xFE" "\xFF\xFF\x8D\x85\xC4\xFE\xFF\xFF\xC7\x45\x88\x2E\x44\x4C\x4C\x57\x33\xFF" "\x89\x85\x2C\xFE\xFF\xFF\xC7\x85\x70\xFF\xFF\xFF\x4C\x6F\x61\x64\x8D\x45" "\xCC\xC7\x85\x74\xFF\xFF\xFF\x4C\x69\x62\x72\xC7\x85\x78\xFF\xFF\xFF\x61" "\x72\x79\x57\xC6\x85\x7C\xFF\xFF\xFF\x00\xC7\x85\x30\xFF\xFF\xFF\x47\x65" "\x74\x50\xC7\x85\x34\xFF\xFF\xFF\x72\x6F\x63\x41\xC7\x85\x38\xFF\xFF\xFF" "\x64\x64\x72\x65\x66\xC7\x85\x3C\xFF\xFF\xFF\x73\x73\xC6\x85\x3E\xFF\xFF" "\xFF\x00\xC7\x45\x98\x46\x72\x65\x65\xC7\x45\x9C\x4C\x69\x62\x72\xC7\x45" "\xA0\x61\x72\x79\x00\xC7\x45\xA4\x56\x69\x72\x74\xC7\x45\xA8\x75\x61\x6C" "\x46\xC7\x45\xAC\x72\x65\x65\x00\xC7\x85\x60\xFF\xFF\xFF\x47\x65\x74\x4C" "\xC7\x85\x64\xFF\xFF\xFF\x61\x73\x74\x45\xC7\x85\x68\xFF\xFF\xFF\x72\x72" "\x6F\x72\xC6\x85\x6C\xFF\xFF\xFF\x00\xC7\x85\x08\xFF\xFF\xFF\x4F\x75\x74" "\x70\xC7\x85\x0C\xFF\xFF\xFF\x75\x74\x44\x65\xC7\x85\x10\xFF\xFF\xFF\x62" "\x75\x67\x53\xC7\x85\x14\xFF\xFF\xFF\x74\x72\x69\x6E\x66\xC7\x85\x18\xFF" "\xFF\xFF\x67\x41\xC6\x85\x1A\xFF\xFF\xFF\x00\xC7\x45\x8C\x43\x6C\x6F\x73" "\xC7\x45\x90\x65\x48\x61\x6E\xC7\x45\x94\x64\x6C\x65\x00\xC7\x85\xF4\xFE" "\xFF\xFF\x53\x65\x74\x54\xC7\x85\xF8\xFE\xFF\xFF\x68\x72\x65\x61\xC7\x85" "\xFC\xFE\xFF\xFF\x64\x45\x72\x72\xC7\x85\x00\xFF\xFF\xFF\x6F\x72\x4D\x6F" "\x66\xC7\x85\x04\xFF\xFF\xFF\x64\x65\xC6\x85\x06\xFF\xFF\xFF\x00\xC7\x85" "\x50\xFF\xFF\xFF\x43\x72\x65\x61\xC7\x85\x54\xFF\xFF\xFF\x74\x65\x54\x68" "\xC7\x85\x58\xFF\xFF\xFF\x72\x65\x61\x64\xC6\x85\x5C\xFF\xFF\xFF\x00\xC7" "\x85\xCC\xFE\xFF\xFF\x00\x00\x00\x00\xC7\x85\xC8\xFE\xFF\xFF\x00\x00\x00" "\x00\xC7\x85\xC4\xFE\xFF\xFF\x00\x00\x00\x00\xC7\x45\xCC\x00\x00\x00\x00" "\xC7\x85\xDC\xFE\xFF\xFF\x00\x00\x00\x00\x89\x4D\xE4\x89\x4D\xD4\x89\x5D" "\xD8\x89\x9D\xD8\xFE\xFF\xFF\x89\xBD\xE8\xFE\xFF\xFF\x89\x85\x30\xFE\xFF" "\xFF\x8D\x5D\x80\x8D\x85\xDC\xFE\xFF\xFF\x89\x9D\x7C\xFE\xFF\xFF\x89\x85" "\x34\xFE\xFF\xFF\x8D\x45\xE4\x89\x85\x38\xFE\xFF\xFF\x8D\x45\xD4\x89\x85" "\x3C\xFE\xFF\xFF\x8D\x85\xD8\xFE\xFF\xFF\x89\x85\x40\xFE\xFF\xFF\x8D\x85" "\xE8\xFE\xFF\xFF\x89\x85\x44\xFE\xFF\xFF\x8D\x85\x48\xFE\xFF\xFF\x89\x85" "\x84\xFE\xFF\xFF\x8D\x85\x24\xFE\xFF\xFF\x89\x85\x88\xFE\xFF\xFF\x33\xC0" "\x89\x85\x90\xFE\xFF\xFF\x89\x85\x94\xFE\xFF\xFF\x89\x85\x98\xFE\xFF\xFF" "\x89\x85\x9C\xFE\xFF\xFF\x89\x85\xA0\xFE\xFF\xFF\x89\x85\xA4\xFE\xFF\xFF" "\x89\x85\xA8\xFE\xFF\xFF\x89\x85\xAC\xFE\xFF\xFF\x89\x85\xB0\xFE\xFF\xFF" "\x89\x85\xB4\xFE\xFF\xFF\x88\x85\x2C\xFF\xFF\xFF\x8D\x85\x1C\xFF\xFF\xFF" "\x89\x85\x6C\xFE\xFF\xFF\x8D\x85\x40\xFF\xFF\xFF\x89\x85\x70\xFE\xFF\xFF" "\x33\xC0\x89\x45\xE0\x89\x85\xE4\xFE\xFF\xFF\x89\x85\xE0\xFE\xFF\xFF\x8D" "\x85\xE4\xFE\xFF\xFF\x89\x85\x74\xFE\xFF\xFF\x8D\x85\xE0\xFE\xFF\xFF\xC7" "\x85\x80\xFE\xFF\xFF\x0C\x00\x00\x00\xC7\x85\x8C\xFE\xFF\xFF\x09\x00\x00" "\x00\xC7\x45\xBC\x4E\x54\x44\x4C\xC7\x45\xC0\x4C\x2E\x44\x4C\xC6\x45\xC4" "\x4C\xC7\x85\x1C\xFF\xFF\xFF\x4E\x74\x51\x75\xC7\x85\x20\xFF\xFF\xFF\x65" "\x75\x65\x41\xC7\x85\x24\xFF\xFF\xFF\x70\x63\x54\x68\xC7\x85\x28\xFF\xFF" "\xFF\x72\x65\x61\x64\xC7\x85\x40\xFF\xFF\xFF\x4E\x74\x41\x6C\xC7\x85\x44" "\xFF\xFF\xFF\x65\x72\x74\x54\xC7\x85\x48\xFF\xFF\xFF\x68\x72\x65\x61\x66" "\xC7\x85\x4C\xFF\xFF\xFF\x64\x00\x89\x85\x78\xFE\xFF\xFF\x84\xD2\x74\x3B" "\xF6\x46\x28\x02\x74\x35\x8D\x45\xBC\xC7\x85\x94\xFE\xFF\xFF\x09\x00\x00" "\x00\x89\x85\x90\xFE\xFF\xFF\x8D\x85\x6C\xFE\xFF\xFF\x89\x85\x98\xFE\xFF" "\xFF\x8D\x85\x74\xFE\xFF\xFF\x89\x85\x9C\xFE\xFF\xFF\xC7\x85\xA0\xFE\xFF" "\xFF\x02\x00\x00\x00\x8B\x46\x0C\x32\xD2\x83\xC0\x0C\x89\x45\xD0\x8B\x00" "\x3B\x45\xD0\x0F\x84\xE3\x00\x00\x00\x8B\xC8\x8B\xC1\x8B\x09\x89\x45\xC8" "\x89\x8D\xEC\xFE\xFF\xFF\x33\xC9\x89\x4D\xDC\x8B\x50\x30\x66\x8B\x40\x2C" "\x66\xD1\xE8\x89\x55\xE0\x0F\xB7\xF8\x85\xDB\x0F\x84\x63\x02\x00\x00\x8D" "\x9D\x7C\xFE\xFF\xFF\x89\x9D\xF0\xFE\xFF\xFF\x83\x7B\x10\x00\x74\x52\x3B" "\x7B\x04\x75\x4D\x33\xC0\x33\xF6\x66\x3B\xC7\x73\x3B\x8B\x1B\x8D\x49\x00" "\x0F\xB7\xCE\x0F\xB7\x14\x4A\x8D\x42\x9F\x66\x83\xF8\x19\x77\x06\x81\xC2" "\xE0\xFF\x00\x00\x0F\xBE\x0C\x19\x0F\xB7\xC2\x3B\xC1\x75\x09\x8B\x55\xE0" "\x46\x66\x3B\xF7\x72\xD6\x8B\x9D\xF0\xFE\xFF\xFF\x8B\x4D\xDC\x8B\x55\xE0" "\x66\x3B\xF7\x0F\x84\xC7\x00\x00\x00\x41\xC6\x45\xEB\x00\x8D\x9D\x7C\xFE" "\xFF\xFF\x89\x4D\xDC\x8D\x04\x89\x83\x3C\x83\x00\x8D\x1C\x83\x89\x9D\xF0" "\xFE\xFF\xFF\x75\x88\x8A\x55\xEB\x8B\x9D\x7C\xFE\xFF\xFF\x8B\x8D\xEC\xFE" "\xFF\xFF\x3B\x4D\xD0\x0F\x85\x3A\xFF\xFF\xFF\x8B\x85\xD8\xFE\xFF\xFF\x8B" "\x4D\xE4\x8B\xBD\xE8\xFE\xFF\xFF\x89\x45\xD8\x8B\x85\xE4\xFE\xFF\xFF\x89" "\x45\xE0\x80\x7D\xE9\x00\x8B\x45\x08\x8B\x9D\xD4\xFE\xFF\xFF\x8B\x30\x0F" "\x84\x70\x02\x00\x00\xF6\x43\x28\x02\x0F\x84\x66\x02\x00\x00\x8B\x45\x0C" "\x40\x89\x45\x0C\x85\xC9\x0F\x84\x85\x01\x00\x00\x83\xFE\x02\x0F\x8C\x7C" "\x01\x00\x00\x04\x40\xC7\x45\xF0\x5B\x57\x48\x5D\x88\x45\xFC\x8D\x45\xF0" "\x50\xC7\x45\xF4\x20\x41\x50\x43\xC7\x45\xF8\x20\x52\x45\x20\x66\xC7\x45" "\xFD\x0A\x00\xFF\xD1\x8B\x4D\xE4\x8B\xBD\xE8\xFE\xFF\xFF\x8B\x95\xE4\xFE" "\xFF\xFF\x8B\x45\x0C\xE9\x42\x01\x00\x00\x32\xD2\x85\xDB\x0F\x84\x52\xFF" "\xFF\xFF\x8B\x45\xC8\x8B\x70\x18\x89\x75\xDC\x8B\x46\x3C\x8B\x54\x30\x78" "\x8B\x44\x32\x20\x03\xD6\x03\xC6\x89\x95\xD0\xFE\xFF\xFF\x89\x45\xC8\x8B" "\x4A\x24\x8B\x7A\x18\x03\xCE\x89\x4D\xE0\x8B\x4B\x10\x89\x7D\xD8\x85\xFF" "\x74\x53\x8B\x38\x8B\xD1\x03\xFE\x33\xF6\x85\xC9\x74\x2A\x8B\x43\x08\x8A" "\x2F\x8B\x14\xB0\x8A\x0A\x3A\xCD\x75\x14\x8B\xC7\x2B\xD7\x84\xC9\x74\x68" "\x8A\x4C\x02\x01\x40\x3A\x08\x74\xF3\x8B\x43\x08\x8B\x53\x10\x46\x3B\xF2" "\x72\xDB\x8B\x75\xDC\x8B\x45\xC8\x8B\xCA\x8B\x7D\xD8\x83\xC0\x04\x83\x45" "\xE0\x02\x4F\x89\x45\xC8\x89\x7D\xD8\x85\xD2\x75\xA9\x8B\x9D\x7C\xFE\xFF" "\xFF\x33\xC9\xB2\x01\x85\xDB\x0F\x84\xCC\xFE\xFF\xFF\x33\xC0\x8D\x49\x00" "\x83\xBC\x05\x8C\xFE\xFF\xFF\x00\x77\x7D\x41\x8D\x04\x89\xC1\xE0\x02\x83" "\xBC\x05\x7C\xFE\xFF\xFF\x00\x75\xE5\xE9\xA7\xFE\xFF\xFF\x8B\x4B\x10\x8B" "\x43\x0C\x8B\x3C\xB0\x8D\x04\xB0\x89\x85\xB8\xFE\xFF\xFF\x8D\x51\xFF\x3B" "\xF2\x73\x22\x8B\x43\x08\x8B\x5B\x08\x8B\x44\x88\xFC\x89\x04\xB3\x8B\x9D" "\xF0\xFE\xFF\xFF\x8B\x43\x0C\x8B\x44\x88\xFC\x8B\x8D\xB8\xFE\xFF\xFF\x89" "\x01\x8B\x75\xDC\x89\x53\x10\x85\xFF\x0F\x84\x66\xFF\xFF\xFF\x8B\x45\xE0" "\x0F\xB7\x08\x8B\x85\xD0\xFE\xFF\xFF\x8B\x40\x1C\x8D\x04\x88\x8B\x04\x30" "\x03\xC6\x89\x07\xE9\x48\xFF\xFF\xFF\x32\xD2\xE9\x2A\xFE\xFF\xFF\x8A\xD1" "\xE9\x23\xFE\xFF\xFF\x8B\x55\xE0\x32\xDB\x83\xF8\x0A\x72\x46\x85\xFF\x74" "\x3E\x83\x7D\xD4\x00\x74\x38\x8B\x4D\x08\x8B\xC1\x6A\x00\x6A\x00\x83\xC8" "\x01\x50\xFF\x71\x20\x6A\x00\x6A\x00\xFF\xD7\x85\xC0\x74\x1A\x50\xFF\x55" "\xD4\x33\xC0\xB3\x01\x84\xDB\x5F\x0F\x95\xC0\x48\x23\x45\xCC\x5B\x5E\x8B" "\xE5\x5D\xC2\x0C\x00\xB0\x34\xEB\x3A\xB0\x31\xEB\x39\x85\xD2\x74\x3B\x83" "\xBD\xE0\xFE\xFF\xFF\x00\x74\x32\x6A\x00\x50\x8B\x45\x08\x50\xFF\x70\x28" "\x6A\xFE\xFF\xD2\x85\xC0\x78\x15\x6A\xFE\xB3\x01\xFF\x95\xE0\xFE\xFF\xFF" "\x85\xC0\x0F\x99\xC0\xFE\xC8\x24\x33\xEB\x02\xB0\x32\x8B\x4D\xE4\x84\xC0" "\x74\x31\xEB\x02\xB0\x31\x85\xC9\x74\x29\x83\xFE\x01\x7C\x24\x88\x45\xFC" "\x8D\x45\xF0\x50\xC7\x45\xF0\x5B\x57\x48\x5D\xC7\x45\xF4\x20\x41\x50\x43" "\xC7\x45\xF8\x20\x45\x52\x52\x66\xC7\x45\xFD\x0A\x00\xFF\xD1\x33\xC0\x84" "\xDB\x5F\x0F\x95\xC0\x48\x23\x45\xCC\x5B\x5E\x8B\xE5\x5D\xC2\x0C\x00\x84" "\xD2\x75\x36\x85\xC9\x74\x1F\x83\xFE\x01\x7C\x1A\x8D\x45\xF4\xC7\x45\xF4" "\x5B\x57\x48\x5D\x50\xC7\x45\xF8\x20\x45\x58\x50\x66\xC7\x45\xFC\x0A\x00" "\xFF\xD1\x80\x7D\xEA\x00\x0F\x84\x58\x02\x00\x00\xF6\x43\x28\x02\xE9\x42" "\x02\x00\x00\x8D\x85\xBC\xFE\xFF\xFF\xC7\x45\xB0\x49\x6E\x6A\x65\x50\x6A" "\x01\xC7\x45\xB4\x63\x74\x49\x6E\x33\xFF\x66\xC7\x45\xB8\x69\x74\x33\xDB" "\xC6\x45\xBA\x00\xC7\x45\x0C\x00\x00\x00\x00\xFF\x55\xD8\x83\xFE\x02\x7C" "\x18\x8D\x45\xF4\xC7\x45\xF4\x5B\x57\x48\x5D\x50\xC7\x45\xF8\x20\x4C\x4C" "\x0A\x88\x5D\xFC\xFF\x55\xE4\x8B\x45\x08\x8B\x8D\xCC\xFE\xFF\xFF\x83\xC0" "\x30\x50\xFF\xD1\x89\x45\xD0\x85\xC0\x0F\x84\xC4\x00\x00\x00\x83\xFE\x02" "\x7C\x1B\x8D\x45\xF4\xC7\x45\xF4\x5B\x57\x48\x5D\x50\xC7\x45\xF8\x20\x47" "\x50\x41\x66\xC7\x45\xFC\x0A\x00\xFF\x55\xE4\x8B\x85\xC8\xFE\xFF\xFF\x8D" "\x4D\xB0\x51\xFF\x75\xD0\xFF\xD0\x89\x85\xD0\xFE\xFF\xFF\x85\xC0\x74\x70" "\x83\xFE\x02\x7C\x58\x8D\x45\xF4\xC7\x45\xF4\x5B\x57\x48\x5D\x50\xC7\x45" "\xF8\x20\x49\x49\x0A\x88\x5D\xFC\xFF\x55\xE4\xFF\x75\x08\xC7\x45\x0C\x01" "\x00\x00\x00\xFF\x95\xD0\xFE\xFF\xFF\x8B\xF8\xC7\x45\xF4\x5B\x57\x48\x5D" "\x83\xC4\x04\xC7\x45\xF8\x20\x49\x49\x3A\x85\xFF\xC6\x45\xFC\x20\x66\xC7" "\x45\xFE\x0A\x00\x0F\x95\xC0\x04\x30\x88\x45\xFD\x8D\x45\xF4\x50\xFF\x55" "\xE4\xEB\x1B\xFF\x75\x08\xC7\x45\x0C\x01\x00\x00\x00\xFF\xD0\x83\xC4\x04" "\x8B\xF8\xEB\x08\xFF\x95\xDC\xFE\xFF\xFF\x8B\xD8\xFF\x75\xD0\xFF\x95\xC4" "\xFE\xFF\xFF\x85\xFF\x0F\x85\xFC\x00\x00\x00\xEB\x08\xFF\x95\xDC\xFE\xFF" "\xFF\x8B\xD8\x8B\x7D\x08\x8B\x47\x18\x85\xC0\x74\x04\x50\xFF\x55\xD4\xFF" "\x77\x10\xFF\x55\xD4\x83\x7D\x0C\x00\x0F\x85\xD4\x00\x00\x00\x83\xFE\x01" "\x0F\x8C\xCB\x00\x00\x00\x8B\xC3\xC7\x45\xEC\x5B\x57\x48\x5D\x83\xE0\x0F" "\xC7\x45\xF0\x20\x45\x52\x52\x66\xC7\x45\xF4\x3A\x20\x66\xC7\x45\xFE\x0A" "\x00\x83\xF8\x0A\x73\x04\x04\x30\xEB\x02\x04\x37\xC1\xEB\x04\x88\x45\xFD" "\x8B\xC3\x83\xE0\x0F\x83\xF8\x0A\x73\x04\x04\x30\xEB\x02\x04\x37\xC1\xEB" "\x04\x88\x45\xFC\x8B\xC3\x83\xE0\x0F\x83\xF8\x0A\x73\x04\x04\x30\xEB\x02" "\x04\x37\xC1\xEB\x04\x88\x45\xFB\x8B\xC3\x83\xE0\x0F\x83\xF8\x0A\x73\x04" "\x04\x30\xEB\x02\x04\x37\xC1\xEB\x04\x88\x45\xFA\x8B\xC3\x83\xE0\x0F\x83" "\xF8\x0A\x73\x04\x04\x30\xEB\x02\x04\x37\xC1\xEB\x04\x88\x45\xF9\x8B\xC3" "\x83\xE0\x0F\x83\xF8\x0A\x73\x04\x04\x30\xEB\x02\x04\x37\xC1\xEB\x04\x88" "\x45\xF8\x8B\xC3\x83\xE0\x0F\x83\xF8\x0A\x73\x04\x04\x30\xEB\x02\x04\x37" "\xC1\xEB\x04\x88\x45\xF7\x83\xFB\x0A\x73\x05\x80\xC3\x30\xEB\x03\x80\xC3" "\x37\x8D\x45\xEC\x88\x5D\xF6\x50\xFF\x55\xE4\x6A\x00\xFF\xB5\xBC\xFE\xFF" "\xFF\xFF\x95\xD8\xFE\xFF\xFF\x80\x7D\xEA\x00\x74\x17\x8B\x85\xD4\xFE\xFF" "\xFF\xF6\x40\x28\x02\x74\x0B\x5F\x5B\x33\xC0\x5E\x8B\xE5\x5D\xC2\x0C\x00" "\x8B\x45\xCC\x5F\x5B\x5E\x8B\xE5\x5D\xC2\x0C\x00"; constexpr size_t x32ShellcodeSize = sizeof(x32Shellcode) - 1; #define PRE_X64SHELLCODE_VIRTUAL_FREE \ "\x48\x83\xEC\x28" /* sub rsp,28 */ \ "\xE8\x20\x00\x00\x00" /* call $+20 */ \ "\x48\x83\xC4\x28" /* add rsp,28 */ \ "\x48\x85\xC0" /* test rax,rax */ \ "\x75\x01" /* jne $+1 */ \ "\xC3" /* ret */ \ "\x48\x8D\x0D\x00\x00\x00\x00" /* lea rcx,qword ptr ds:[$] */ \ "\x66\x81\xE1\x00\xF0" /* and cx,F000 */ \ "\x33\xD2" /* xor edx,edx */ \ "\x41\xB8\x00\x80\x00\x00" /* mov r8d,8000 */ \ "\xFF\xE0" /* jmp rax */ // The 64-bit InjectShellcode function from the project in the inject_shellcode // subfolder. const BYTE x64Shellcode[] = PRE_X64SHELLCODE_VIRTUAL_FREE "\x48\x89\x54\x24\x10\x48\x89\x4C\x24\x08\x55\x56\x41\x54\x41\x56\x48\x8D" "\xAC\x24\xE8\xFD\xFF\xFF\x48\x81\xEC\x18\x03\x00\x00\x4C\x8B\xF1\xC6\x85" "\x58\x02\x00\x00\x00\x32\xC9\x4C\x8B\xE2\x88\x4C\x24\x48\x41\xF6\xC6\x01" "\x74\x14\x49\x83\xE6\xFE\xC6\x85\x58\x02\x00\x00\x01\x4C\x89\xB5\x40\x02" "\x00\x00\xEB\x0C\x41\x83\x7E\x04\x00\x0F\x95\xC1\x88\x4C\x24\x48\x65\x48" "\x8B\x34\x25\x60\x00\x00\x00\x48\x89\xB5\xB0\x00\x00\x00\x48\x83\x7E\x18" "\x00\x75\x10\x33\xC0\x48\x81\xC4\x18\x03\x00\x00\x41\x5E\x41\x5C\x5E\x5D" "\xC3\xC7\x45\xA8\x4B\x45\x52\x4E\x48\x8D\x45\xB8\xC7\x45\xAC\x45\x4C\x33" "\x32\xC7\x45\xB0\x2E\x44\x4C\x4C\xC7\x45\xB8\x4C\x6F\x61\x64\xC7\x45\xBC" "\x4C\x69\x62\x72\xC7\x45\xC0\x61\x72\x79\x57\xC6\x45\xC4\x00\xC7\x45\xF8" "\x47\x65\x74\x50\xC7\x45\xFC\x72\x6F\x63\x41\xC7\x45\x00\x64\x64\x72\x65" "\x66\xC7\x45\x04\x73\x73\xC6\x45\x06\x00\xC7\x44\x24\x78\x46\x72\x65\x65" "\xC7\x44\x24\x7C\x4C\x69\x62\x72\xC7\x45\x80\x61\x72\x79\x00\xC7\x45\x88" "\x56\x69\x72\x74\xC7\x45\x8C\x75\x61\x6C\x46\xC7\x45\x90\x72\x65\x65\x00" "\xC7\x45\xC8\x47\x65\x74\x4C\xC7\x45\xCC\x61\x73\x74\x45\xC7\x45\xD0\x72" "\x72\x6F\x72\xC6\x45\xD4\x00\xC6\x45\x20\x4F\xC6\x45\x21\x75\xC6\x45\x22" "\x74\xC6\x45\x23\x70\xC6\x45\x24\x75\xC6\x45\x25\x74\xC6\x45\x26\x44\xC6" "\x45\x27\x65\xC6\x45\x28\x62\xC6\x45\x29\x75\xC6\x45\x2A\x67\xC6\x45\x2B" "\x53\xC6\x45\x2C\x74\xC6\x45\x2D\x72\xC6\x45\x2E\x69\xC6\x45\x2F\x6E\xC6" "\x45\x30\x67\xC6\x45\x31\x41\xC6\x45\x32\x00\xC7\x45\x98\x43\x6C\x6F\x73" "\xC7\x45\x9C\x65\x48\x61\x6E\xC7\x45\xA0\x64\x6C\x65\x00\xC6\x45\x38\x53" "\xC6\x45\x39\x65\xC6\x45\x3A\x74\xC6\x45\x3B\x54\xC6\x45\x3C\x68\xC6\x45" "\x3D\x72\xC6\x45\x3E\x65\xC6\x45\x3F\x61\xC6\x45\x40\x64\xC6\x45\x41\x45" "\xC6\x45\x42\x72\xC6\x45\x43\x72\xC6\x45\x44\x6F\xC6\x45\x45\x72\x48\x89" "\x85\x60\x01\x00\x00\x48\x8D\x45\xF8\xC6\x45\x46\x4D\x48\x89\x85\x68\x01" "\x00\x00\x48\x8D\x44\x24\x78\xC6\x45\x47\x6F\x48\x89\x9C\x24\x50\x03\x00" "\x00\x48\x89\x85\x70\x01\x00\x00\x48\x8D\x45\x88\xC6\x45\x48\x64\x48\x89" "\xBC\x24\x10\x03\x00\x00\x48\x89\x85\x78\x01\x00\x00\x48\x8D\x45\xC8\xC6" "\x45\x49\x65\x4C\x89\xAC\x24\x08\x03\x00\x00\xC6\x45\x4A\x00\x4C\x89\xBC" "\x24\x00\x03\x00\x00\xC7\x45\xD8\x43\x72\x65\x61\xC7\x45\xDC\x74\x65\x54" "\x68\xC7\x45\xE0\x72\x65\x61\x64\xC6\x45\xE4\x00\x48\x89\x85\x80\x01\x00" "\x00\xC7\x44\x24\x58\x4E\x54\x44\x4C\x48\x8D\x95\x98\x00\x00\x00\x48\x89" "\x95\xB0\x01\x00\x00\x48\x8D\x45\x20\x48\x89\x85\x88\x01\x00\x00\x48\x8D" "\x95\xA0\x00\x00\x00\x48\x89\x95\xB8\x01\x00\x00\x48\x8D\x45\x98\x48\x89" "\x85\x90\x01\x00\x00\x48\x8D\x95\xA8\x00\x00\x00\x48\x89\x95\xC0\x01\x00" "\x00\x48\x8D\x45\x38\x48\x89\x85\x98\x01\x00\x00\x48\x8D\x55\x60\x48\x89" "\x95\xC8\x01\x00\x00\x48\x8D\x45\xD8\x48\x89\x85\xA0\x01\x00\x00\x48\x8D" "\x95\x88\x00\x00\x00\x48\x89\x95\xD0\x01\x00\x00\x4C\x8D\x7D\xA8\xC7\x44" "\x24\x5C\x4C\x2E\x44\x4C\x48\x8D\x54\x24\x50\x48\x89\x95\xD8\x01\x00\x00" "\x45\x33\xED\x4C\x89\xAD\x98\x00\x00\x00\x48\x8D\x55\x50\x48\x89\x95\xE0" "\x01\x00\x00\x0F\x57\xC0\x4C\x89\xAD\xA0\x00\x00\x00\x48\x8D\x95\x90\x00" "\x00\x00\x48\x89\x95\xE8\x01\x00\x00\x41\x8B\xC5\x4C\x89\xAD\xA8\x00\x00" "\x00\x48\x8D\x55\x70\x48\x89\x95\xF0\x01\x00\x00\x45\x8B\xC5\x4C\x89\x6D" "\x60\x48\x8D\x95\x60\x01\x00\x00\x4C\x89\xAD\x88\x00\x00\x00\x45\x8B\xCD" "\x4C\x89\x6C\x24\x50\x4C\x89\x6D\x50\x4C\x89\xAD\x90\x00\x00\x00\x48\x89" "\x45\x70\xC6\x44\x24\x60\x4C\xC6\x45\x08\x4E\xC6\x45\x09\x74\xC6\x45\x0A" "\x51\xC6\x45\x0B\x75\xC6\x45\x0C\x65\xC6\x45\x0D\x75\xC6\x45\x0E\x65\xC6" "\x45\x0F\x41\xC6\x45\x10\x70\xC6\x45\x11\x63\xC6\x45\x12\x54\xC6\x45\x13" "\x68\xC6\x45\x14\x72\x48\x89\x95\xF0\x00\x00\x00\x48\x8D\x95\xB0\x01\x00" "\x00\xC6\x45\x15\x65\x48\x89\x95\xF8\x00\x00\x00\x48\x8D\x55\x08\xC6\x45" "\x16\x61\x48\x89\x95\xC0\x00\x00\x00\x48\x8D\x55\xE8\xC6\x45\x17\x64\x88" "\x45\x18\x4C\x89\xBD\xE0\x00\x00\x00\x48\xC7\x85\xE8\x00\x00\x00\x0C\x00" "\x00\x00\x48\xC7\x85\x00\x01\x00\x00\x09\x00\x00\x00\x0F\x11\x85\x08\x01" "\x00\x00\xC7\x45\xE8\x4E\x74\x41\x6C\x0F\x11\x85\x18\x01\x00\x00\xC7\x45" "\xEC\x65\x72\x74\x54\x0F\x11\x85\x28\x01\x00\x00\xC7\x45\xF0\x68\x72\x65" "\x61\x0F\x11\x85\x38\x01\x00\x00\x66\xC7\x45\xF4\x64\x00\x0F\x11\x85\x48" "\x01\x00\x00\x48\x89\x95\xC8\x00\x00\x00\x4C\x89\x6D\x78\x48\x8D\x55\x78" "\x48\x89\x95\xD0\x00\x00\x00\x48\x8D\x95\x80\x00\x00\x00\x48\x89\x95\xD8" "\x00\x00\x00\x45\x8B\xD5\x4C\x89\xAD\x80\x00\x00\x00\x84\xC9\x74\x44\xF6" "\x46\x50\x02\x74\x3E\x48\x8D\x54\x24\x58\x48\xC7\x85\x10\x01\x00\x00\x09" "\x00\x00\x00\x48\x89\x95\x08\x01\x00\x00\x48\x8D\x95\xC0\x00\x00\x00\x48" "\x89\x95\x18\x01\x00\x00\x48\x8D\x95\xD0\x00\x00\x00\x48\x89\x95\x20\x01" "\x00\x00\x48\xC7\x85\x28\x01\x00\x00\x02\x00\x00\x00\x48\x8B\x7E\x18\x32" "\xD2\x48\x83\xC7\x10\x48\x89\x7D\x58\x4C\x8B\x1F\x4C\x3B\xDF\x0F\x84\x29" "\x02\x00\x00\x48\x8B\xC7\x66\x66\x0F\x1F\x84\x00\x00\x00\x00\x00\x49\x8B" "\x73\x60\x4D\x8D\x33\x4D\x8B\x1B\x32\xD2\x4C\x89\x5C\x24\x30\x49\x8B\xFD" "\x45\x0F\xB7\x5E\x58\x66\x41\xD1\xEB\x4D\x85\xFF\x0F\x84\xBA\x01\x00\x00" "\x4C\x8D\x95\xE0\x00\x00\x00\x0F\x1F\x00\x4D\x39\x6A\x20\x74\x4D\x41\x0F" "\xB7\xC3\x49\x3B\x42\x08\x75\x43\x45\x0F\xB7\xCD\x66\x45\x3B\xEB\x73\x33" "\x49\x8B\x1A\x0F\x1F\x00\x45\x0F\xB7\xC1\x42\x0F\xB7\x14\x46\x8D\x42\x9F" "\x66\x83\xF8\x19\x8D\x4A\xE0\x41\x0F\xBE\x04\x18\x66\x0F\x47\xCA\x0F\xB7" "\xC9\x3B\xC8\x75\x0A\x66\x41\xFF\xC1\x66\x45\x3B\xCB\x72\xD3\x66\x45\x3B" "\xCB\x74\x1F\x48\xFF\xC7\x4C\x8D\x95\xE0\x00\x00\x00\x32\xD2\x48\x8D\x04" "\xBF\x4D\x39\x2C\xC2\x4D\x8D\x14\xC2\x75\x93\xE9\x3A\x01\x00\x00\x32\xD2" "\x4D\x85\xD2\x0F\x84\x2F\x01\x00\x00\x49\x8B\x76\x30\x49\x8B\x4A\x20\x48" "\x63\x46\x3C\x8B\x84\x30\x88\x00\x00\x00\x48\x03\xC6\x48\x89\x85\xB8\x00" "\x00\x00\x44\x8B\x70\x20\x44\x8B\x78\x24\x4C\x03\xF6\x44\x8B\x60\x18\x4C" "\x03\xFE\x0F\x1F\x40\x00\x45\x85\xE4\x0F\x84\xB8\x00\x00\x00\x45\x8B\x1E" "\x4D\x8B\xCD\x4C\x03\xDE\x4C\x8B\xC1\x48\x85\xC9\x0F\x84\x8C\x00\x00\x00" "\x49\x8B\x5A\x10\x41\x0F\xB6\x3B\x66\x66\x0F\x1F\x84\x00\x00\x00\x00\x00" "\x4A\x8B\x14\xCB\x0F\xB6\x0A\x40\x3A\xCF\x75\x16\x49\x8B\xC3\x49\x2B\xD3" "\x84\xC9\x74\x1A\x0F\xB6\x4C\x02\x01\x48\xFF\xC0\x3A\x08\x74\xF0\x4D\x8B" "\x42\x20\x49\xFF\xC1\x4D\x3B\xC8\x72\xD2\xEB\x4A\x49\x8B\x4A\x20\x49\x8B" "\x52\x18\x4C\x8D\x41\xFF\x4E\x8B\x1C\xCA\x4D\x3B\xC8\x73\x12\x48\x8B\x44" "\xCB\xF8\x4A\x89\x04\xCB\x48\x8B\x44\xCA\xF8\x4A\x89\x04\xCA\x4D\x89\x42" "\x20\x4D\x85\xDB\x74\x1A\x41\x0F\xB7\x0F\x48\x8B\x85\xB8\x00\x00\x00\x8B" "\x40\x1C\x48\x03\xC6\x8B\x14\x88\x48\x03\xD6\x49\x89\x13\x49\x83\xC6\x04" "\x49\x83\xC7\x02\x41\xFF\xCC\x49\x8B\xC8\x4D\x85\xC0\x0F\x85\x3F\xFF\xFF" "\xFF\x4C\x8B\xBD\xE0\x00\x00\x00\xB2\x01\x49\x8B\xCD\x4D\x85\xFF\x74\x3C" "\x49\x8B\xC5\x4C\x39\xAC\x05\x00\x01\x00\x00\x77\x1B\x48\xFF\xC1\x48\x8D" "\x04\x89\x48\x8D\x04\xC5\x00\x00\x00\x00\x4C\x39\xAC\x05\xE0\x00\x00\x00" "\x75\xDD\xEB\x14\x32\xD2\x48\x8B\x45\x58\x4C\x8B\x5C\x24\x30\x4C\x3B\xD8" "\x0F\x85\x12\xFE\xFF\xFF\x4C\x8B\x44\x24\x50\x4C\x8B\x8D\x90\x00\x00\x00" "\x48\x8B\x45\x70\x4C\x8B\x55\x78\x4C\x8B\xB5\x40\x02\x00\x00\x48\x8B\xB5" "\xB0\x00\x00\x00\x0F\xB6\x4C\x24\x48\x4C\x8B\xA5\x48\x02\x00\x00\x41\x8B" "\x1E\x84\xC9\x0F\x84\x44\x01\x00\x00\xF6\x46\x50\x02\x0F\x84\x3A\x01\x00" "\x00\x41\x8D\x74\x24\x01\x4D\x85\xC0\x74\x40\x83\xFB\x02\x7C\x3B\x8D\x46" "\x40\xC7\x44\x24\x30\x5B\x57\x48\x5D\x48\x8D\x4C\x24\x30\x88\x44\x24\x3C" "\xC7\x44\x24\x34\x20\x41\x50\x43\xC7\x44\x24\x38\x20\x52\x45\x20\x66\xC7" "\x44\x24\x3D\x0A\x00\x41\xFF\xD0\x4C\x8B\x44\x24\x50\x48\x8B\x45\x70\x4C" "\x8B\x55\x78\x40\x32\xFF\x83\xFE\x0A\x72\x4C\x48\x85\xC0\x74\x43\x4C\x39" "\x6D\x50\x74\x3D\x4D\x8B\x46\x20\x4D\x8B\xCE\x49\x83\xC9\x01\x4C\x89\x6C" "\x24\x28\x33\xD2\x44\x89\x6C\x24\x20\x33\xC9\xFF\xD0\x48\x85\xC0\x74\x19" "\x48\x8B\xC8\xFF\x55\x50\x48\x8B\x45\x60\x40\xB7\x01\x40\x84\xFF\x49\x0F" "\x45\xC5\xE9\x65\x03\x00\x00\xB1\x34\xEB\x4E\xB1\x31\xEB\x4F\x4D\x85\xD2" "\x74\x50\x4C\x39\xAD\x80\x00\x00\x00\x74\x47\x49\x8B\x56\x28\x4D\x8B\xC6" "\x44\x8B\xCE\x48\xC7\xC1\xFE\xFF\xFF\xFF\x4C\x89\x6C\x24\x20\x41\xFF\xD2" "\x85\xC0\x78\x1D\x48\xC7\xC1\xFE\xFF\xFF\xFF\x40\xB7\x01\xFF\x95\x80\x00" "\x00\x00\x85\xC0\xB9\x33\x00\x00\x00\x41\x0F\x49\xCD\xEB\x02\xB1\x32\x4C" "\x8B\x44\x24\x50\x84\xC9\x74\x39\xEB\x02\xB1\x31\x4D\x85\xC0\x74\x30\x83" "\xFB\x01\x7C\x2B\x88\x4C\x24\x3C\x48\x8D\x4C\x24\x30\xC7\x44\x24\x30\x5B" "\x57\x48\x5D\xC7\x44\x24\x34\x20\x41\x50\x43\xC7\x44\x24\x38\x20\x45\x52" "\x52\x66\xC7\x44\x24\x3D\x0A\x00\x41\xFF\xD0\x48\x8B\x45\x60\x40\x84\xFF" "\x49\x0F\x45\xC5\xE9\xC1\x02\x00\x00\x84\xD2\x75\x3F\x4D\x85\xC0\x74\x24" "\x83\xFB\x01\x7C\x1F\x48\x8D\x4C\x24\x30\xC7\x44\x24\x30\x5B\x57\x48\x5D" "\xC7\x44\x24\x34\x20\x45\x58\x50\x66\xC7\x44\x24\x38\x0A\x00\x41\xFF\xD0" "\x44\x38\xAD\x58\x02\x00\x00\x0F\x84\x80\x02\x00\x00\xF6\x46\x50\x02\xE9" "\x75\x02\x00\x00\x48\x8D\x55\x68\xC7\x44\x24\x68\x49\x6E\x6A\x65\xB9\x01" "\x00\x00\x00\xC7\x44\x24\x6C\x63\x74\x49\x6E\x66\xC7\x44\x24\x70\x69\x74" "\x45\x8B\xE5\x44\x88\x6C\x24\x72\x41\x8B\xF5\x41\x8B\xFD\x41\xFF\xD1\x83" "\xFB\x02\x7C\x1E\x48\x8D\x4C\x24\x30\xC7\x44\x24\x30\x5B\x57\x48\x5D\xC7" "\x44\x24\x34\x20\x4C\x4C\x0A\x40\x88\x74\x24\x38\xFF\x54\x24\x50\x48\x8B" "\x85\x98\x00\x00\x00\x49\x8D\x4E\x30\xFF\xD0\x4C\x8B\xF0\x48\x85\xC0\x0F" "\x84\xD4\x00\x00\x00\x83\xFB\x02\x7C\x20\x48\x8D\x4C\x24\x30\xC7\x44\x24" "\x30\x5B\x57\x48\x5D\xC7\x44\x24\x34\x20\x47\x50\x41\x66\xC7\x44\x24\x38" "\x0A\x00\xFF\x54\x24\x50\x48\x8B\x85\xA0\x00\x00\x00\x48\x8D\x54\x24\x68" "\x49\x8B\xCE\xFF\xD0\x4C\x8B\xF8\x48\x85\xC0\x74\x7B\x83\xFB\x02\x7C\x62" "\x48\x8D\x4C\x24\x30\xC7\x44\x24\x30\x5B\x57\x48\x5D\xC7\x44\x24\x34\x20" "\x49\x49\x0A\x40\x88\x74\x24\x38\xFF\x54\x24\x50\x48\x8B\x8D\x40\x02\x00" "\x00\x41\xBC\x01\x00\x00\x00\x41\xFF\xD7\x85\xC0\xC7\x44\x24\x30\x5B\x57" "\x48\x5D\x8B\xF0\xC7\x44\x24\x34\x20\x49\x49\x3A\x0F\x95\xC0\xC6\x44\x24" "\x38\x20\x04\x30\x66\xC7\x44\x24\x3A\x0A\x00\x48\x8D\x4C\x24\x30\x88\x44" "\x24\x39\xFF\x54\x24\x50\xEB\x1C\x48\x8B\x8D\x40\x02\x00\x00\x41\xBC\x01" "\x00\x00\x00\x41\xFF\xD7\x8B\xF0\xEB\x08\xFF\x95\x88\x00\x00\x00\x8B\xF8" "\x49\x8B\xCE\xFF\x95\xA8\x00\x00\x00\x85\xF6\x0F\x85\x17\x01\x00\x00\xEB" "\x08\xFF\x95\x88\x00\x00\x00\x8B\xF8\x48\x8B\xB5\x40\x02\x00\x00\x48\x8B" "\x4E\x18\x48\x85\xC9\x74\x03\xFF\x55\x50\x48\x8B\x4E\x10\xFF\x55\x50\x45" "\x85\xE4\x0F\x85\xEA\x00\x00\x00\x83\xFB\x01\x0F\x8C\xE1\x00\x00\x00\x8B" "\xCF\xC7\x44\x24\x30\x5B\x57\x48\x5D\x83\xE1\x0F\xC7\x44\x24\x34\x20\x45" "\x52\x52\x83\xF9\x0A\x66\xC7\x44\x24\x38\x3A\x20\xBA\x30\x00\x00\x00\x66" "\xC7\x44\x24\x42\x0A\x00\x41\xB8\x37\x00\x00\x00\x8B\xC2\x41\x0F\x43\xC0" "\xC1\xEF\x04\x02\xC1\x8B\xCF\x83\xE1\x0F\x88\x44\x24\x41\x83\xF9\x0A\x8B" "\xC2\x41\x0F\x43\xC0\xC1\xEF\x04\x02\xC1\x8B\xCF\x83\xE1\x0F\x88\x44\x24" "\x40\x83\xF9\x0A\x8B\xC2\x41\x0F\x43\xC0\xC1\xEF\x04\x02\xC1\x8B\xCF\x83" "\xE1\x0F\x88\x44\x24\x3F\x83\xF9\x0A\x8B\xC2\x41\x0F\x43\xC0\xC1\xEF\x04" "\x02\xC1\x8B\xCF\x83\xE1\x0F\x88\x44\x24\x3E\x83\xF9\x0A\x8B\xC2\x41\x0F" "\x43\xC0\xC1\xEF\x04\x02\xC1\x8B\xCF\x83\xE1\x0F\x88\x44\x24\x3D\x83\xF9" "\x0A\x8B\xC2\x41\x0F\x43\xC0\xC1\xEF\x04\x02\xC1\x8B\xCF\x83\xE1\x0F\x88" "\x44\x24\x3C\x83\xF9\x0A\x8B\xC2\x41\x0F\x43\xC0\xC1\xEF\x04\x02\xC1\x48" "\x8D\x4C\x24\x30\x83\xFF\x0A\x88\x44\x24\x3B\x41\x0F\x43\xD0\x40\x02\xD7" "\x88\x54\x24\x3A\xFF\x54\x24\x50\x8B\x4D\x68\x33\xD2\xFF\x95\x90\x00\x00" "\x00\x44\x38\xAD\x58\x02\x00\x00\x74\x0D\x48\x8B\x85\xB0\x00\x00\x00\xF6" "\x40\x50\x02\x75\x04\x4C\x8B\x6D\x60\x49\x8B\xC5\x4C\x8B\xAC\x24\x08\x03" "\x00\x00\x48\x8B\xBC\x24\x10\x03\x00\x00\x48\x8B\x9C\x24\x50\x03\x00\x00" "\x4C\x8B\xBC\x24\x00\x03\x00\x00\x48\x81\xC4\x18\x03\x00\x00\x41\x5E\x41" "\x5C\x5E\x5D\xC3"; constexpr size_t x64ShellcodeSize = sizeof(x64Shellcode) - 1; #define PRE_ARM64SHELLCODE_VIRTUAL_FREE \ "\xfd\x7b\xbf\xa9" /* stp fp, lr, [sp, #-0x10]! */ \ "\xfd\x03\x00\x91" /* mov fp, sp */ \ "\x0a\x00\x00\x94" /* bl $+#0x28 */ \ "\xe3\x03\x00\xaa" /* mov x3, x0 */ \ "\xc3\x00\x00\xb4" /* cbz x3, $+#0x18 */ \ "\x00\x00\x00\x90" /* adrp x0, $ */ \ "\xfd\x7b\xc1\xa8" /* ldp fp, lr, [sp], #0x10 */ \ "\x02\x00\x90\x52" /* mov w2, #0x8000 */ \ "\x01\x00\x80\xd2" /* mov x1, #0 */ \ "\x60\x00\x1f\xd6" /* br x3 */ \ "\xfd\x7b\xc1\xa8" /* ldp fp, lr, [sp], #0x10 */ \ "\xc0\x03\x5f\xd6" /* ret */ // The ARM64 InjectShellcode function from the project in the inject_shellcode // subfolder. const BYTE arm64Shellcode[] = PRE_ARM64SHELLCODE_VIRTUAL_FREE "\xF3\x53\xBB\xA9\xF5\x5B\x01\xA9\xF7\x63\x02\xA9\xF9\x6B\x03\xA9\xFB\x23" "\x00\xF9\xFF\x83\x0B\xD1\xFD\x7B\x00\xA9\xFD\x03\x00\x91\xF7\x03\x00\xAA" "\x19\x00\x80\x52\x0D\x00\x80\x52\x80\x00\x00\x36\x17\xF8\x7F\x92\x39\x00" "\x80\x52\x04\x00\x00\x14\x08\x04\x40\xB9\x1F\x01\x00\x71\xED\x07\x9F\x1A" "\xE8\x03\x12\xAA\x18\x31\x40\xF9\x08\x0F\x40\xF9\x08\x30\x00\xB4\x88\x45" "\x00\x58\xFF\x53\x03\x39\xE8\x97\x00\xF9\xC8\x85\x88\x52\x88\x89\xA9\x72" "\xE8\x33\x01\xB9\x08\x45\x00\x58\xE8\x67\x00\xF9\x28\x4C\x8E\x52\x28\xEF" "\xAA\x72\xE8\xD3\x00\xB9\xA8\x44\x00\x58\xE8\x5F\x00\xF9\x88\x8C\x8C\x52" "\x48\xAE\xAC\x72\xE8\xC3\x00\xB9\x68\x6E\x8E\x52\xE8\x8B\x01\x79\x08\x44" "\x00\x58\xE8\x9F\x00\xF9\x28\x4C\x8E\x52\x28\x0F\xA0\x72\xE8\x43\x01\xB9" "\xA8\x43\x00\x58\xE8\x87\x00\xF9\x48\xAE\x8C\x52\xA8\x0C\xA0\x72\xE8\x13" "\x01\xB9\x48\x43\x00\x58\xE8\x6F\x00\xF9\x48\x4E\x8E\x52\xE8\x4D\xAE\x72" "\xE8\xE3\x00\xB9\xE8\x2C\x88\x52\xE8\xC3\x02\x79\xA8\x42\x00\x58\xE8\x8F" "\x00\xF9\x88\x8C\x8D\x52\xA8\x0C\xA0\x72\xE8\x23\x01\xB9\x88\xAC\x8C\x52" "\xE8\x03\x03\x79\x08\x42\x00\x58\xE8\x77\x00\xF9\x48\xAE\x8C\x52\x28\x8C" "\xAC\x72\xE8\xF3\x00\xB9\xE9\x23\x03\x91\xE8\xE3\x02\x91\xFF\x1B\x03\x39" "\xF1\x23\x09\x91\x29\x22\x00\xA9\xE9\xE3\x04\x91\xE8\x23\x04\x91\xFF\x93" "\x03\x39\xF1\x63\x09\x91\x29\x22\x00\xA9\xE9\x63\x03\x91\xE8\x43\x05\x91" "\xFF\x8B\x05\x39\xF1\xA3\x09\x91\x29\x22\x00\xA9\xE9\x63\x04\x91\xE8\xC3" "\x05\x91\xFF\x0B\x06\x39\xF1\xE3\x09\x91\x29\x22\x00\xA9\xEA\x03\x02\x91" "\xE9\x23\x02\x91\xFF\xD3\x03\x39\x50\x3B\x00\x9C\xF1\x43\x0A\x91\x2A\x26" "\x00\xA9\xEA\x43\x02\x91\xF0\x57\x80\x3D\xE9\xC3\x00\x91\xFF\x7F\x08\xA9" "\xE8\xA3\x03\x91\xF1\x83\x0A\x91\x2A\x26\x00\xA9\x90\x3A\x00\x9C\xE8\x47" "\x01\xF9\xEA\xC3\x01\x91\xF0\x5F\x80\x3D\xE9\xA3\x00\x91\xFF\x4B\x00\xF9" "\x00\x00\x80\xD2\xFF\xFF\x02\xA9\x06\x00\x80\xD2\xFF\x1F\x00\xF9\x07\x00" "\x80\xD2\xFF\x7F\x07\xA9\x0F\x00\x80\xD2\x08\x00\x80\xD2\xF1\xC3\x0A\x91" "\x2A\x26\x00\xA9\xEA\xE3\x00\x91\x10\xE4\x00\x4F\xE9\xE3\x01\x91\xFF\x03" "\x07\x39\xF1\x03\x0B\x91\x2A\x26\x00\xA9\xE9\x83\x01\x91\xE9\x6B\x01\xF9" "\xEA\xA3\x04\x91\x89\x01\x80\xD2\xFF\x7F\x06\xA9\xEA\x27\x1D\xA9\xEA\x23" "\x09\x91\xE9\x43\x0A\x91\xFF\x2B\x00\xF9\xEA\x27\x1E\xA9\x2C\x01\x80\xD2" "\xE9\xE3\x07\x91\xEC\xFB\x00\xF9\x30\x41\x00\xAD\xEA\xC3\x06\x91\x30\x41" "\x01\xAD\x0E\x00\x80\xD2\x30\x11\x80\x3D\x89\x38\x00\x58\xE9\x57\x00\xF9" "\x89\x09\x80\x52\xE9\xC3\x02\x39\x49\x38\x00\x58\xE9\x7F\x00\xF9\x09\x4D" "\x8E\x52\xA9\x2C\xAC\x72\xE9\x03\x01\xB9\x89\x0C\x80\x52\xE9\x0B\x02\x79" "\xE9\xE3\x03\x91\xEA\x27\x19\xA9\x70\x34\x00\x9C\xEA\xA3\x01\x91\xF0\x6F" "\x80\x3D\xE9\x43\x01\x91\xEA\x27\x1A\xA9\x0B\x00\x80\xD2\x6D\x01\x00\x34" "\x09\x53\x40\xB9\x29\x01\x08\x36\xE9\xA3\x02\x91\xE9\xB3\x1F\xA9\xE9\x83" "\x06\x91\xEA\x43\x06\x91\xF1\x23\x08\x91\x2A\x26\x00\xA9\x49\x00\x80\xD2" "\xE9\x0F\x01\xF9\x09\x0F\x40\xF9\x0A\x00\x80\x52\x2C\x41\x00\x91\x89\x01" "\x40\xF9\x3F\x01\x0C\xEB\x20\x10\x00\x54\xEB\xEB\x40\xF9\x08\x05\x80\xD2" "\xE5\x03\x09\xAA\x29\x01\x40\xF9\xA3\xB0\x40\x79\x13\x00\x80\xD2\xAA\x30" "\x40\xF9\x6E\x40\x01\x53\x03\x00\x80\xD2\xAB\x04\x00\xB4\xE2\x43\x07\x91" "\xEE\x03\x0E\xAA\xF5\xF3\x9F\x52\x14\xFC\x9F\x52\xE7\x43\x07\x91\x4F\x10" "\x40\xF9\x0F\x03\x00\xB4\x4F\x04\x40\xF9\xDF\x01\x0F\xEB\xA1\x02\x00\x54" "\x0F\x00\x80\x52\x2E\x02\x00\x34\x56\x00\x40\xF9\xEF\x03\x0F\xAA\x46\x79" "\x6F\x78\xC4\x00\x15\x0B\x84\x3C\x00\x53\xC0\x00\x14\x0B\x9F\x64\x00\x71" "\x00\x3C\x00\x53\xC4\x80\x80\x1A\xE6\x69\xF6\x38\x9F\x00\x06\x6B\xA1\x00" "\x00\x54\xEF\x05\x00\x11\xEF\x3D\x00\x53\xFF\x01\x0E\x6B\x43\xFE\xFF\x54" "\xFF\x01\x0E\x6B\xC0\x00\x00\x54\x73\x06\x00\x91\x62\x1E\x08\x9B\x4F\x00" "\x40\xF9\x8F\xFC\xFF\xB5\x02\x00\x00\x14\xE3\x03\x02\xAA\x0A\x00\x80\x52" "\x43\x09\x00\xB4\xA2\x18\x40\xF9\x53\x3C\x40\xB9\x53\xC0\x33\x8B\x73\x8A" "\x40\xB9\x55\x40\x33\x8B\xAE\x4E\x44\x29\x54\x40\x2E\x8B\x6E\x10\x40\xF9" "\x4A\x40\x33\x8B\xB3\x1A\x40\xB9\x2E\x06\x00\xB4\xF3\x05\x00\x34\x8F\x46" "\x40\xB8\x44\x40\x2F\x8B\x0F\x00\x80\xD2\x0E\x05\x00\xB4\x76\x08\x40\xF9" "\x80\x00\xC0\x39\xE6\x03\x16\xAA\xC5\x84\x40\xF8\xAB\x00\xC0\x39\x7F\x01" "\x00\x6B\x21\x01\x00\x54\xEE\x03\x04\xAA\xA5\x00\x04\xCB\x6B\x01\x00\x34" "\xCE\x05\x00\x91\xCB\x69\xE5\x38\xC7\x01\xC0\x39\x7F\x01\x07\x6B\x60\xFF" "\xFF\x54\x6E\x10\x40\xF9\xEF\x05\x00\x91\xFF\x01\x0E\xEB\x23\xFE\xFF\x54" "\x14\x00\x00\x14\x60\xAC\x41\xA9\x6E\x05\x00\xD1\x06\x78\x6F\xF8\xFF\x01" "\x0E\xEB\xE2\x00\x00\x54\xC4\x0E\x0B\x8B\x84\x80\x5F\xF8\xC4\x7A\x2F\xF8" "\x04\x0C\x0B\x8B\x84\x80\x5F\xF8\x04\x78\x2F\xF8\x6E\x10\x00\xF9\xE6\x00" "\x00\xB4\x4F\x01\x40\x79\xA4\x1E\x40\xB9\x8F\x08\x0F\x8B\xEF\x69\x62\xB8" "\x4F\x40\x2F\x8B\xCF\x00\x00\xF9\x4A\x09\x00\x91\x73\x06\x00\x51\x4E\xFA" "\xFF\xB5\xEB\xEB\x40\xF9\x2A\x00\x80\x52\x02\x00\x80\xD2\xCB\x01\x00\xB4" "\x03\x00\x80\xD2\xF3\xC3\x07\x91\xEE\x43\x07\x91\x63\x68\x73\xF8\xC3\x00" "\x00\xB5\x42\x04\x00\x91\x43\x7C\x08\x9B\x6F\x68\x6E\xF8\x6F\xFF\xFF\xB5" "\x04\x00\x00\x14\x0A\x00\x80\x52\x3F\x01\x0C\xEB\x01\xF1\xFF\x54\xE6\x83" "\x42\xA9\xE7\x1F\x40\xF9\xEF\x3F\x40\xF9\xE8\x3B\x46\xA9\xEB\x2B\x40\xF9" "\xF4\x02\x40\xB9\x8D\x0A\x00\x34\x09\x53\x40\xB9\x49\x0A\x08\x36\xDF\x00" "\x00\xF1\x8A\x1A\x42\x7A\x36\x04\x00\x11\xB9\x23\x00\x58\x53\x01\x80\x52" "\xCB\x01\x00\x54\x08\x44\x8A\x52\xA8\x08\xA4\x72\xF9\x0B\x00\xF9\xE8\x1B" "\x00\xB9\xC8\x02\x01\x11\xE0\x43\x00\x91\xE8\x73\x00\x39\xF3\xD3\x01\x78" "\xC0\x00\x3F\xD6\xE6\x83\x42\xA9\xE7\x1F\x40\xF9\xE8\x3B\x46\xA9\xEB\x2B" "\x40\xF9\x18\x00\x80\x52\xDF\x2A\x00\x71\xC3\x02\x00\x54\x68\x02\x00\xB4" "\x47\x02\x00\xB4\xE2\x12\x40\xF9\xE3\x02\x40\xB2\x05\x00\x80\xD2\x04\x00" "\x80\x52\x01\x00\x80\xD2\x00\x00\x80\xD2\x00\x01\x3F\xD6\x00\x01\x00\xB4" "\xE8\x1F\x40\xF9\x00\x01\x3F\xD6\xE0\x1B\x40\xF9\x38\x00\x80\x52\x1F\x03" "\x00\x71\xE0\x13\x80\x9A\xD1\x00\x00\x14\x89\x06\x80\x52\x15\x00\x00\x14" "\x29\x06\x80\x52\x14\x00\x00\x14\xCE\x02\x00\xB4\xAB\x02\x00\xB4\xE1\x16" "\x40\xF9\x04\x00\x80\xD2\xE3\x03\x16\xAA\xE2\x03\x17\xAA\x20\x00\x80\x92" "\xC0\x01\x3F\xD6\x20\x01\xF8\x37\xE8\x2B\x40\xF9\x20\x00\x80\x92\x38\x00" "\x80\x52\x00\x01\x3F\xD6\x68\x06\x80\x52\x1F\x00\x00\x71\xE9\xA3\x88\x1A" "\x02\x00\x00\x14\x49\x06\x80\x52\xE6\x83\x42\xA9\x28\x1D\x00\x53\xE8\x01" "\x00\x34\x02\x00\x00\x14\x29\x06\x80\x52\xDF\x00\x00\xF1\x8A\x1A\x41\x7A" "\x4B\x01\x00\x54\x08\xA4\x88\x52\x48\x4A\xAA\x72\xF9\x0B\x00\xF9\xE0\x43" "\x00\x91\xE8\x1B\x00\xB9\xE9\x73\x00\x39\xF3\xD3\x01\x78\xC0\x00\x3F\xD6" "\xE0\x1B\x40\xF9\x1F\x03\x00\x71\xE0\x13\x80\x9A\xA7\x00\x00\x14\x0A\x02" "\x00\x35\xDF\x00\x00\xF1\x8A\x1A\x41\x7A\x0B\x01\x00\x54\xA8\x19\x00\x58" "\xE8\x0B\x00\xF9\x48\x01\x80\x52\xE0\x43\x00\x91\xE8\x33\x00\x79\xC0\x00" "\x3F\xD6\xE0\x1B\x40\xF9\x79\x13\x00\x34\x08\x53\x40\xB9\x28\x13\x08\x36" "\x00\x00\x80\xD2\x97\x00\x00\x14\x68\x18\x00\x58\xFF\x8B\x02\x39\xE8\x4F" "\x00\xF9\x28\x8D\x8E\x52\xE1\x63\x01\x91\xE8\x43\x01\x79\x20\x00\x80\x52" "\x1B\x00\x80\x52\x1A\x00\x80\x52\x15\x00\x80\x52\xE0\x01\x3F\xD6\x9F\x0A" "\x00\x71\xEB\x00\x00\x54\x08\x17\x00\x58\xFF\x23\x01\x39\xE8\x23\x00\xF9" "\xE8\x17\x40\xF9\xE0\x03\x01\x91\x00\x01\x3F\xD6\xE8\x43\x40\xF9\xE0\xC2" "\x00\x91\x00\x01\x3F\xD6\x53\x01\x80\x52\xF6\x03\x00\xAA\xF6\x06\x00\xB4" "\x9F\x0A\x00\x71\xEB\x00\x00\x54\x88\x15\x00\x58\xF3\x33\x00\x79\xE8\x0B" "\x00\xF9\xE8\x17\x40\xF9\xE0\x43\x00\x91\x00\x01\x3F\xD6\xE8\x47\x40\xF9" "\xE1\x63\x02\x91\xE0\x03\x16\xAA\x00\x01\x3F\xD6\xE0\x23\x00\xF9\x20\x04" "\x00\xB4\x9F\x0A\x00\x71\x2B\x03\x00\x54\x08\x14\x00\x58\xFF\x63\x00\x39" "\xE8\x0B\x00\xF9\xE8\x17\x40\xF9\xE0\x43\x00\x91\x00\x01\x3F\xD6\xE8\x23" "\x40\xF9\xE0\x03\x17\xAA\x3B\x00\x80\x52\x00\x01\x3F\xD6\x08\x13\x00\x58" "\xF3\x37\x00\x79\xE8\x0B\x00\xF9\x08\x04\x80\x52\xFA\x03\x00\x2A\xE8\x63" "\x00\x39\x5F\x03\x00\x71\x09\x06\x80\x52\x28\x05\x89\x1A\xE8\x67\x00\x39" "\xE8\x17\x40\xF9\xE0\x43\x00\x91\x00\x01\x3F\xD6\x0A\x00\x00\x14\xE8\x23" "\x40\xF9\xE0\x03\x17\xAA\x3B\x00\x80\x52\x00\x01\x3F\xD6\xFA\x03\x00\x2A" "\x04\x00\x00\x14\xE8\x3B\x40\xF9\x00\x01\x3F\xD6\xF5\x03\x00\x2A\xE8\x4B" "\x40\xF9\xE0\x03\x16\xAA\x00\x01\x3F\xD6\x3A\x08\x00\x35\x04\x00\x00\x14" "\xE8\x3B\x40\xF9\x00\x01\x3F\xD6\xF5\x03\x00\x2A\xE0\x0E\x40\xF9\x60\x00" "\x00\xB4\xE8\x1F\x40\xF9\x00\x01\x3F\xD6\xE0\x0A\x40\xF9\xE8\x1F\x40\xF9" "\x00\x01\x3F\xD6\xBB\x06\x00\x35\x9F\x06\x00\x71\x6B\x06\x00\x54\x28\x0E" "\x00\x58\xF3\x47\x00\x79\xE8\x0B\x00\xF9\x48\x07\x84\x52\xAB\x0E\x00\x12" "\xE8\x33\x00\x79\xE8\x06\x80\x52\x7F\x29\x00\x71\x09\x06\x80\x52\x0A\x21" "\x89\x1A\x4A\x81\x2B\x0B\xAB\x1E\x04\x53\x7F\x29\x00\x71\xEA\x87\x00\x39" "\x0A\x21\x89\x1A\x4A\x81\x2B\x0B\xAB\x2E\x08\x53\x7F\x29\x00\x71\xEA\x83" "\x00\x39\x0A\x21\x89\x1A\x4A\x81\x2B\x0B\xAB\x3E\x0C\x53\x7F\x29\x00\x71" "\xEA\x7F\x00\x39\x0A\x21\x89\x1A\x4A\x81\x2B\x0B\xAB\x4E\x10\x53\x7F\x29" "\x00\x71\xEA\x7B\x00\x39\x0A\x21\x89\x1A\x4A\x81\x2B\x0B\xAB\x5E\x14\x53" "\x7F\x29\x00\x71\xEA\x77\x00\x39\x0A\x21\x89\x1A\x4A\x81\x2B\x0B\xAB\x6E" "\x18\x53\x7F\x29\x00\x71\xEA\x73\x00\x39\x0A\x21\x89\x1A\x4A\x81\x2B\x0B" "\xE0\x43\x00\x91\xEA\x6F\x00\x39\xAA\x7E\x1C\x53\x5F\x29\x00\x71\x08\x21" "\x89\x1A\x08\x81\x2A\x0B\xE8\x6B\x00\x39\xE8\x17\x40\xF9\x00\x01\x3F\xD6" "\xE0\x5B\x40\xB9\x01\x00\x80\xD2\xE8\x3F\x40\xF9\x00\x01\x3F\xD6\x79\x00" "\x00\x34\x08\x53\x40\xB9\x48\xED\x0F\x37\xE0\x1B\x40\xF9\xFD\x7B\x40\xA9" "\xFF\x83\x0B\x91\xFB\x23\x40\xF9\xF9\x6B\x43\xA9\xF7\x63\x42\xA9\xF5\x5B" "\x41\xA9\xF3\x53\xC5\xA8\xC0\x03\x5F\xD6\x1F\x20\x03\xD5\x4F\x75\x74\x70" "\x75\x74\x44\x65\x62\x75\x67\x53\x74\x72\x69\x6E\x53\x65\x74\x54\x68\x72" "\x65\x61\x64\x45\x72\x72\x6F\x72\x4D\x6F\x4E\x74\x51\x75\x65\x75\x65\x41" "\x70\x63\x54\x68\x72\x65\x61\x64\x4B\x45\x52\x4E\x45\x4C\x33\x32\x4C\x6F" "\x61\x64\x4C\x69\x62\x72\x47\x65\x74\x50\x72\x6F\x63\x41\x46\x72\x65\x65" "\x4C\x69\x62\x72\x56\x69\x72\x74\x75\x61\x6C\x46\x47\x65\x74\x4C\x61\x73" "\x74\x45\x43\x6C\x6F\x73\x65\x48\x61\x6E\x43\x72\x65\x61\x74\x65\x54\x68" "\x4E\x54\x44\x4C\x4C\x2E\x44\x4C\x4E\x74\x41\x6C\x65\x72\x74\x54\x5B\x57" "\x48\x5D\x20\x41\x50\x43\x5B\x57\x48\x5D\x20\x45\x58\x50\x49\x6E\x6A\x65" "\x63\x74\x49\x6E\x5B\x57\x48\x5D\x20\x4C\x4C\x0A\x5B\x57\x48\x5D\x20\x47" "\x50\x41\x5B\x57\x48\x5D\x20\x49\x49\x0A\x5B\x57\x48\x5D\x20\x49\x49\x3A" "\x5B\x57\x48\x5D\x20\x45\x52\x52"; constexpr size_t arm64ShellcodeSize = sizeof(arm64Shellcode) - 1; using PPS_APC_ROUTINE = VOID(NTAPI*)(_In_opt_ PVOID ApcArgument1, _In_opt_ PVOID ApcArgument2, _In_opt_ PVOID ApcArgument3); #ifdef _M_IX86 void NtQueueApcThread64(_In_ HANDLE ThreadHandle, _In_ PPS_APC_ROUTINE ApcRoutine, _In_opt_ PVOID ApcArgument1, _In_opt_ PVOID ApcArgument2, _In_opt_ PVOID ApcArgument3) { STATIC_INIT_ONCE_TRIVIAL(DWORD64, pNtQueueApcThread, []() { auto ntdll = wow64pp::module_handle("ntdll.dll"); return wow64pp::import(ntdll, "NtQueueApcThread"); }()); auto result64 = wow64pp::call_function( pNtQueueApcThread, wow64pp::handle_to_uint64(ThreadHandle), wow64pp::ptr_to_uint64(ApcRoutine), wow64pp::ptr_to_uint64(ApcArgument1), wow64pp::ptr_to_uint64(ApcArgument2), wow64pp::ptr_to_uint64(ApcArgument3)); NTSTATUS result = static_cast(result64); THROW_IF_NTSTATUS_FAILED(result); } using PUSER_THREAD_START_ROUTINE = NTSTATUS(NTAPI*)(_In_ PVOID ThreadParameter); void NtCreateThreadEx64(_Out_ PHANDLE ThreadHandle, _In_ ACCESS_MASK DesiredAccess, _In_opt_ void* ObjectAttributes, // PCOBJECT_ATTRIBUTES _In_ HANDLE ProcessHandle, _In_ PUSER_THREAD_START_ROUTINE StartRoutine, _In_opt_ PVOID Argument, _In_ ULONG CreateFlags, // THREAD_CREATE_FLAGS_* _In_ DWORD64 ZeroBits, _In_ DWORD64 StackSize, _In_ DWORD64 MaximumStackSize, _In_opt_ void* AttributeList) { // PPS_ATTRIBUTE_LIST STATIC_INIT_ONCE_TRIVIAL(DWORD64, pNtCreateThreadEx, []() { auto ntdll = wow64pp::module_handle("ntdll.dll"); return wow64pp::import(ntdll, "NtCreateThreadEx"); }()); DWORD64 threadHandle64 = 0; auto result64 = wow64pp::call_function( pNtCreateThreadEx, wow64pp::ptr_to_uint64(&threadHandle64), DesiredAccess, wow64pp::ptr_to_uint64(ObjectAttributes), wow64pp::handle_to_uint64(ProcessHandle), wow64pp::ptr_to_uint64(StartRoutine), wow64pp::ptr_to_uint64(Argument), CreateFlags, ZeroBits, StackSize, MaximumStackSize, wow64pp::ptr_to_uint64(AttributeList)); NTSTATUS result = static_cast(result64); THROW_IF_NTSTATUS_FAILED(result); *ThreadHandle = reinterpret_cast(threadHandle64); } #endif // _M_IX86 #ifdef _WIN64 // // Reference: https://repnz.github.io/posts/apc/wow64-user-apc/ // ULONG64 EncodeWow64ApcRoutine(ULONG64 ApcRoutine) { return (ULONG64)((-(INT64)ApcRoutine) << 2); } #endif // _WIN64 // // Reference: https://repnz.github.io/posts/apc/user-apc/ // void MyQueueUserAPC(PPS_APC_ROUTINE pfnAPC, HANDLE hThread, void* data, USHORT targetProcessArch) { #ifndef _WIN64 if (targetProcessArch == IMAGE_FILE_MACHINE_AMD64 || targetProcessArch == IMAGE_FILE_MACHINE_ARM64) { // WOW64 to x64 native, use heaven's gate. // // "Microsoft added a validation to prevent a programming error: // If you try to queue an APC from a 32 bit process to a 64 bit // process and you use a 32 bit address, you'll get this status code: // [...] STATUS_INVALID_HANDLE" // https://repnz.github.io/posts/apc/wow64-user-apc/ NtQueueApcThread64(hThread, pfnAPC, data, nullptr, nullptr); return; } #endif // _WIN64 using NtQueueApcThread_t = NTSTATUS(NTAPI*)( _In_ HANDLE ThreadHandle, _In_ PPS_APC_ROUTINE ApcRoutine, _In_opt_ PVOID ApcArgument1, _In_opt_ PVOID ApcArgument2, _In_opt_ PVOID ApcArgument3); GET_PROC_ADDRESS_ONCE(NtQueueApcThread_t, pNtQueueApcThread, L"ntdll.dll", "NtQueueApcThread"); if (!pNtQueueApcThread) { throw std::runtime_error("NtQueueApcThread not found"); } #ifdef _WIN64 if (targetProcessArch == IMAGE_FILE_MACHINE_I386) { // x64 native to WOW64, encode address. pfnAPC = (PPS_APC_ROUTINE)EncodeWow64ApcRoutine((ULONG64)pfnAPC); } #endif // _WIN64 THROW_IF_NTSTATUS_FAILED( pNtQueueApcThread(hThread, pfnAPC, data, nullptr, nullptr)); } USHORT GetProcessArch(HANDLE hProcess) { using GetProcessInformation_t = BOOL(WINAPI*)( HANDLE hProcess, PROCESS_INFORMATION_CLASS ProcessInformationClass, LPVOID ProcessInformation, DWORD ProcessInformationSize); GET_PROC_ADDRESS_ONCE(GetProcessInformation_t, pGetProcessInformation, L"kernel32.dll", "GetProcessInformation"); if (pGetProcessInformation) { PROCESS_MACHINE_INFORMATION pmi; if (pGetProcessInformation(hProcess, ProcessMachineTypeInfo, &pmi, sizeof(pmi))) { return pmi.ProcessMachine; } THROW_LAST_ERROR_IF(GetLastError() != ERROR_INVALID_PARAMETER); } // If GetProcessInformation(ProcessMachineTypeInfo) isn't supported, assume // only IMAGE_FILE_MACHINE_I386 and IMAGE_FILE_MACHINE_AMD64. #ifndef _WIN64 SYSTEM_INFO siSystemInfo; GetNativeSystemInfo(&siSystemInfo); if (siSystemInfo.wProcessorArchitecture == PROCESSOR_ARCHITECTURE_INTEL) { // 32-bit machine, only one option. return IMAGE_FILE_MACHINE_I386; } #endif // _WIN64 BOOL bIsWow64Process; if (IsWow64Process(hProcess, &bIsWow64Process) && bIsWow64Process) { return IMAGE_FILE_MACHINE_I386; } return IMAGE_FILE_MACHINE_AMD64; } } // namespace namespace DllInject { void DllInject(HANDLE hProcess, HANDLE hThreadForAPC, HANDLE hSessionManagerProcess, HANDLE hSessionMutex, bool threadAttachExempt) { const BYTE* shellcode; size_t shellcodeSize; size_t shellcodeThreadOffset = 0; size_t shellcodeAPCOffset = 0; USHORT targetProcessArch = GetProcessArch(hProcess); switch (targetProcessArch) { case IMAGE_FILE_MACHINE_I386: shellcode = x32Shellcode; shellcodeSize = x32ShellcodeSize; shellcodeAPCOffset = sizeof(PRE_X32SHELLCODE_ARGS_1_TO_3) - 1; break; case IMAGE_FILE_MACHINE_AMD64: shellcode = x64Shellcode; shellcodeSize = x64ShellcodeSize; break; case IMAGE_FILE_MACHINE_ARM64: shellcode = arm64Shellcode; shellcodeSize = arm64ShellcodeSize; break; default: throw std::logic_error("Invalid architecture value"); } std::wstring dllPath = StorageManager::GetInstance().GetEnginePath(targetProcessArch) / L"windhawk.dll"; size_t dllPathBytes = (dllPath.length() + 1) * sizeof(WCHAR); HANDLE hRemoteSessionManagerProcess; THROW_IF_WIN32_BOOL_FALSE(DuplicateHandle( GetCurrentProcess(), hSessionManagerProcess, hProcess, &hRemoteSessionManagerProcess, PROCESS_QUERY_LIMITED_INFORMATION | SYNCHRONIZE, FALSE, 0)); auto remoteSessionManagerProcessCleanup = wil::scope_exit([hProcess, hRemoteSessionManagerProcess] { DuplicateHandle(hProcess, hRemoteSessionManagerProcess, nullptr, nullptr, 0, FALSE, DUPLICATE_CLOSE_SOURCE); }); HANDLE hRemoteSessionMutex = nullptr; if (hSessionMutex) { THROW_IF_WIN32_BOOL_FALSE(DuplicateHandle( GetCurrentProcess(), hSessionMutex, hProcess, &hRemoteSessionMutex, PROCESS_QUERY_LIMITED_INFORMATION, FALSE, 0)); } auto remoteSessionMutexCleanup = wil::scope_exit([hProcess, hRemoteSessionMutex] { if (hRemoteSessionMutex) { DuplicateHandle(hProcess, hRemoteSessionMutex, nullptr, nullptr, 0, FALSE, DUPLICATE_CLOSE_SOURCE); } }); size_t shellcodeDataSize = offsetof(LOAD_LIBRARY_REMOTE_DATA, szDllName) + dllPathBytes; auto shellcodeDataVector = std::vector(shellcodeDataSize); auto shellcodeData = reinterpret_cast(shellcodeDataVector.data()); shellcodeData->nLogVerbosity = static_cast(Logger::GetInstance().GetVerbosity()); shellcodeData->bRunningFromAPC = !!hThreadForAPC; shellcodeData->bThreadAttachExempt = threadAttachExempt; shellcodeData->hSessionManagerProcess = hRemoteSessionManagerProcess; shellcodeData->hSessionMutex = hRemoteSessionMutex; memcpy(shellcodeData->szDllName, dllPath.c_str(), dllPathBytes); size_t shellcodeSizeAligned = (shellcodeSize + (sizeof(LONG_PTR) - 1)) & ~(sizeof(LONG_PTR) - 1); // Allocate enough memory in the remote process's address space // to hold the shellcode and the data struct. void* pRemoteCode = VirtualAllocEx( hProcess, nullptr, shellcodeSizeAligned + shellcodeDataSize, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE); THROW_LAST_ERROR_IF_NULL(pRemoteCode); auto remoteCodeCleanup = wil::scope_exit([hProcess, pRemoteCode] { VirtualFreeEx(hProcess, pRemoteCode, 0, MEM_RELEASE); }); LPTHREAD_START_ROUTINE pRemoteThreadAddress = reinterpret_cast( reinterpret_cast(pRemoteCode) + shellcodeThreadOffset); PPS_APC_ROUTINE pRemoteAPCAddress = reinterpret_cast( reinterpret_cast(pRemoteCode) + shellcodeAPCOffset); shellcodeData->pThreadShellcodeAddress = pRemoteThreadAddress; shellcodeData->pAPCShellcodeAddress = pRemoteAPCAddress; // Write our shellcode into the remote process. THROW_IF_WIN32_BOOL_FALSE(WriteProcessMemory( hProcess, pRemoteCode, shellcode, shellcodeSize, nullptr)); // Write a copy of our struct to the remote process. void* pRemoteData = reinterpret_cast(pRemoteCode) + shellcodeSizeAligned; THROW_IF_WIN32_BOOL_FALSE(WriteProcessMemory( hProcess, pRemoteData, shellcodeData, shellcodeDataSize, nullptr)); // Mark shellcode as executable. DWORD oldProtect; THROW_IF_WIN32_BOOL_FALSE(VirtualProtectEx( hProcess, pRemoteCode, shellcodeSize, PAGE_EXECUTE_READ, &oldProtect)); if (hThreadForAPC) { MyQueueUserAPC(pRemoteAPCAddress, hThreadForAPC, pRemoteData, targetProcessArch); } else { DWORD createThreadFlags = 0; if (threadAttachExempt) { createThreadFlags |= Functions::MY_REMOTE_THREAD_THREAD_ATTACH_EXEMPT; } #ifndef _WIN64 if (targetProcessArch == IMAGE_FILE_MACHINE_AMD64 || targetProcessArch == IMAGE_FILE_MACHINE_ARM64) { // WOW64 to x64 native, use heaven's gate. wil::unique_process_handle remoteThread; NtCreateThreadEx64( &remoteThread, THREAD_ALL_ACCESS, nullptr, hProcess, reinterpret_cast( pRemoteThreadAddress), pRemoteData, createThreadFlags, 0, 0, 0, nullptr); Functions::SetThreadDescriptionIfAvailable( remoteThread.get(), L"WindhawkInjectedFromWow64"); } else #endif // _WIN64 { wil::unique_process_handle remoteThread( Functions::MyCreateRemoteThread(hProcess, pRemoteThreadAddress, pRemoteData, createThreadFlags)); THROW_LAST_ERROR_IF_NULL(remoteThread); Functions::SetThreadDescriptionIfAvailable(remoteThread.get(), L"WindhawkInjected"); } } remoteSessionManagerProcessCleanup.release(); remoteSessionMutexCleanup.release(); remoteCodeCleanup.release(); } } // namespace DllInject ================================================ FILE: src/windhawk/engine/dll_inject.h ================================================ #pragma once namespace DllInject { constexpr ACCESS_MASK kProcessAccess = PROCESS_CREATE_THREAD | PROCESS_VM_OPERATION | PROCESS_VM_READ | PROCESS_VM_WRITE | PROCESS_DUP_HANDLE | PROCESS_QUERY_INFORMATION | SYNCHRONIZE; constexpr ACCESS_MASK kApcThreadsAccess = THREAD_SET_CONTEXT; struct LOAD_LIBRARY_REMOTE_DATA { INT32 nLogVerbosity; BOOL bRunningFromAPC; BOOL bThreadAttachExempt; union { HANDLE hSessionManagerProcess; // Make sure 32-bit/64-bit layouts are the same. DWORD64 dw64SessionManagerProcess; }; union { HANDLE hSessionMutex; // Make sure 32-bit/64-bit layouts are the same. DWORD64 dw64SessionMutex; }; union { void* pThreadShellcodeAddress; // Make sure 32-bit/64-bit layouts are the same. DWORD64 dw64ThreadShellcodeAddress; }; union { void* pAPCShellcodeAddress; // Make sure 32-bit/64-bit layouts are the same. DWORD64 dw64APCShellcodeAddress; }; WCHAR szDllName[1]; // flexible array member }; void DllInject(HANDLE hProcess, HANDLE hThreadForAPC, HANDLE hSessionManagerProcess, HANDLE hSessionMutex, bool threadAttachExempt); } // namespace DllInject ================================================ FILE: src/windhawk/engine/engine.vcxproj ================================================  Debug ARM64 Debug Win32 Debug x64 Release ARM64 Release Win32 Release x64 {16DD1B11-BB29-4015-8BFD-AACD0AF63A79} Win32Proj engine engine 10.0 DynamicLibrary true v143 Unicode DynamicLibrary true v143 Unicode DynamicLibrary true v143 Unicode DynamicLibrary false v143 true Unicode DynamicLibrary false v143 true Unicode DynamicLibrary false v143 true Unicode false $(SolutionDir)$(Configuration)\32\ $(ProjectDir)libraries;$(ProjectDir)..\shared;$(ProjectDir)..\shared\libraries;$(IncludePath) $(ProjectDir)libraries;$(ProjectDir)..\shared\libraries;$(LibraryPath) false windhawk false $(SolutionDir)$(Configuration)\64\ $(ProjectDir)libraries;$(ProjectDir)..\shared;$(ProjectDir)..\shared\libraries;$(IncludePath) $(ProjectDir)libraries;$(ProjectDir)..\shared\libraries;$(LibraryPath) false windhawk $(ProjectDir)libraries;$(ProjectDir)..\shared;$(ProjectDir)..\shared\libraries;$(IncludePath) $(ProjectDir)libraries;$(ProjectDir)..\shared\libraries;$(LibraryPath) false windhawk false $(SolutionDir)$(Configuration)\arm64\ false $(SolutionDir)$(Configuration)\32\ $(ProjectDir)libraries;$(ProjectDir)..\shared;$(ProjectDir)..\shared\libraries;$(IncludePath) $(ProjectDir)libraries;$(ProjectDir)..\shared\libraries;$(LibraryPath) false windhawk false $(SolutionDir)$(Configuration)\64\ $(ProjectDir)libraries;$(ProjectDir)..\shared;$(ProjectDir)..\shared\libraries;$(IncludePath) $(ProjectDir)libraries;$(ProjectDir)..\shared\libraries;$(LibraryPath) false windhawk $(ProjectDir)libraries;$(ProjectDir)..\shared;$(ProjectDir)..\shared\libraries;$(IncludePath) $(ProjectDir)libraries;$(ProjectDir)..\shared\libraries;$(LibraryPath) false windhawk false $(SolutionDir)$(Configuration)\arm64\ Use Level3 Disabled WIN32;ZYDIS_STATIC_BUILD;_DEBUG;_WINDOWS;%(PreprocessorDefinitions) true MultiThreadedDebug stdcpplatest /d1trimfile:"$(SolutionDir)\" %(AdditionalOptions) ProgramDatabase $(IntDir)obj\%(RelativeDir) Windows true ntdll.lib;dia/lib/diaguids.lib;%(AdditionalDependencies);version.lib _exports.def Use Level3 Disabled WIN32;ZYDIS_STATIC_BUILD;_DEBUG;_WINDOWS;%(PreprocessorDefinitions) true MultiThreadedDebug stdcpplatest /d1trimfile:"$(SolutionDir)\" %(AdditionalOptions) ProgramDatabase $(IntDir)obj\%(RelativeDir) Windows true ntdll.lib;dia/lib/amd64/diaguids.lib;%(AdditionalDependencies);version.lib _exports.def Use Level3 Disabled WIN32;ZYDIS_STATIC_BUILD;_DEBUG;_WINDOWS;%(PreprocessorDefinitions) true MultiThreadedDebug stdcpplatest /d1trimfile:"$(SolutionDir)\" %(AdditionalOptions) ProgramDatabase $(IntDir)obj\%(RelativeDir) Windows true ntdll.lib;dia/lib/arm64/diaguids.lib;ntdll.lib;%(AdditionalDependencies);version.lib _exports.def Level3 Use MaxSpeed true true WIN32;ZYDIS_STATIC_BUILD;NDEBUG;_WINDOWS;%(PreprocessorDefinitions) true MultiThreaded stdcpplatest /d1trimfile:"$(SolutionDir)\" %(AdditionalOptions) $(IntDir)obj\%(RelativeDir) Windows false true true ntdll.lib;dia/lib/diaguids.lib;%(AdditionalDependencies);version.lib _exports.def true Level3 Use MaxSpeed true true WIN32;ZYDIS_STATIC_BUILD;NDEBUG;_WINDOWS;%(PreprocessorDefinitions) true MultiThreaded stdcpplatest /d1trimfile:"$(SolutionDir)\" %(AdditionalOptions) $(IntDir)obj\%(RelativeDir) Windows false true true ntdll.lib;dia/lib/amd64/diaguids.lib;%(AdditionalDependencies);version.lib _exports.def true Level3 Use MaxSpeed true true WIN32;ZYDIS_STATIC_BUILD;NDEBUG;_WINDOWS;%(PreprocessorDefinitions) true MultiThreaded stdcpplatest /d1trimfile:"$(SolutionDir)\" %(AdditionalOptions) $(IntDir)obj\%(RelativeDir) Windows false true true ntdll.lib;dia/lib/arm64/diaguids.lib;ntdll.lib;%(AdditionalDependencies);version.lib _exports.def true false false true true true true NotUsing NotUsing NotUsing NotUsing NotUsing NotUsing true true true true _CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions) _CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions) _CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions) _CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions) _CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions) _CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions) Level1 Level1 Level1 Level1 Level1 Level1 NotUsing NotUsing NotUsing NotUsing NotUsing NotUsing true true true true _CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions) _CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions) _CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions) _CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions) _CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions) _CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions) Level1 Level1 Level1 Level1 Level1 Level1 NotUsing NotUsing NotUsing NotUsing NotUsing NotUsing true true true true _CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions) _CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions) _CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions) _CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions) _CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions) _CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions) Level1 Level1 Level1 Level1 Level1 Level1 NotUsing NotUsing NotUsing NotUsing NotUsing NotUsing true true true true _CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions) _CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions) _CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions) _CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions) _CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions) _CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions) Level1 Level1 Level1 Level1 Level1 Level1 NotUsing NotUsing NotUsing NotUsing NotUsing NotUsing true true true true _CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions) _CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions) _CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions) _CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions) _CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions) _CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions) Level1 Level1 Level1 Level1 Level1 Level1 NotUsing NotUsing NotUsing NotUsing NotUsing NotUsing true true true true _CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions) _CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions) _CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions) _CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions) _CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions) _CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions) Level1 Level1 Level1 Level1 Level1 Level1 NotUsing NotUsing NotUsing NotUsing NotUsing NotUsing true true true true _CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions) _CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions) _CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions) _CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions) _CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions) _CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions) Level1 Level1 Level1 Level1 Level1 Level1 NotUsing NotUsing NotUsing NotUsing NotUsing NotUsing true true true true _CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions) _CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions) _CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions) _CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions) _CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions) _CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions) Level1 Level1 Level1 Level1 Level1 Level1 NotUsing NotUsing NotUsing NotUsing NotUsing NotUsing true true true true _CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions) _CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions) _CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions) _CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions) _CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions) _CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions) Level1 Level1 Level1 Level1 Level1 Level1 NotUsing NotUsing NotUsing NotUsing NotUsing NotUsing true true true true _CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions) _CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions) _CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions) _CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions) _CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions) _CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions) Level1 Level1 Level1 Level1 Level1 Level1 NotUsing NotUsing NotUsing NotUsing NotUsing NotUsing true true true true _CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions) _CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions) _CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions) _CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions) _CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions) _CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions) Level1 Level1 Level1 Level1 Level1 Level1 NotUsing NotUsing NotUsing NotUsing NotUsing NotUsing true true true true _CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions) _CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions) _CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions) _CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions) _CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions) _CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions) Level1 Level1 Level1 Level1 Level1 Level1 NotUsing NotUsing NotUsing NotUsing NotUsing NotUsing true true true true _CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions) _CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions) _CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions) _CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions) _CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions) _CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions) Level1 Level1 Level1 Level1 Level1 Level1 NotUsing NotUsing NotUsing NotUsing NotUsing NotUsing true true true true _CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions) _CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions) _CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions) _CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions) _CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions) _CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions) Level1 Level1 Level1 Level1 Level1 Level1 NotUsing NotUsing NotUsing NotUsing NotUsing NotUsing true true true true _CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions) _CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions) _CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions) _CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions) _CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions) _CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions) Level1 Level1 Level1 Level1 Level1 Level1 NotUsing NotUsing NotUsing NotUsing NotUsing NotUsing NotUsing NotUsing NotUsing NotUsing NotUsing NotUsing NotUsing NotUsing NotUsing NotUsing NotUsing NotUsing NotUsing NotUsing NotUsing NotUsing NotUsing NotUsing NotUsing NotUsing NotUsing NotUsing NotUsing NotUsing NotUsing NotUsing NotUsing NotUsing NotUsing NotUsing NotUsing NotUsing NotUsing NotUsing NotUsing NotUsing NotUsing NotUsing NotUsing NotUsing NotUsing NotUsing true true NotUsing NotUsing NotUsing NotUsing NotUsing NotUsing true true true true true true NotUsing NotUsing NotUsing NotUsing NotUsing NotUsing true true true true true true NotUsing NotUsing NotUsing NotUsing NotUsing NotUsing true true true true true true NotUsing NotUsing NotUsing NotUsing NotUsing NotUsing true true true true true true NotUsing NotUsing NotUsing NotUsing NotUsing NotUsing true true true true NotUsing NotUsing NotUsing NotUsing NotUsing NotUsing NotUsing NotUsing NotUsing NotUsing NotUsing NotUsing NotUsing NotUsing NotUsing NotUsing NotUsing NotUsing NotUsing NotUsing NotUsing NotUsing NotUsing NotUsing NotUsing NotUsing NotUsing NotUsing NotUsing NotUsing true true Create Create Create Create Create Create false false true true true true ================================================ FILE: src/windhawk/engine/engine.vcxproj.filters ================================================  {4FC737F1-C7A5-4376-A066-2A32D752A2FF} cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx {93995380-89BD-4b04-88EB-625FBE52EBFB} h;hh;hpp;hxx;hm;inl;inc;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 {dbc84a6d-1bd2-44c1-8485-a0ca8231f2c2} {e8326006-2f1d-452e-8735-9ef7d0f0940e} {3e48c109-06fb-4f27-9600-a875a73ecf97} {d1e5db26-6c1a-4423-a69e-955b89519bc9} {bbbe2e93-e300-48cd-8b34-69e6c0e26802} {ce2439c9-7065-439c-a1dd-4a3337b938c8} Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Libraries\Zydis Libraries\binaryninja-arm64-disassembler Libraries\binaryninja-arm64-disassembler Libraries\binaryninja-arm64-disassembler Libraries\binaryninja-arm64-disassembler Libraries\binaryninja-arm64-disassembler Libraries\binaryninja-arm64-disassembler Libraries\binaryninja-arm64-disassembler Libraries\binaryninja-arm64-disassembler Libraries\binaryninja-arm64-disassembler Libraries\binaryninja-arm64-disassembler Libraries\binaryninja-arm64-disassembler Libraries\binaryninja-arm64-disassembler Libraries\binaryninja-arm64-disassembler Libraries\binaryninja-arm64-disassembler Libraries\binaryninja-arm64-disassembler Libraries\MinHook Libraries\MinHook Libraries\MinHook Libraries\MinHook Libraries\MinHook Libraries\MinHook-Detours Libraries\MinHook-Detours Libraries\MinHook-Detours Libraries\MinHook-Detours Libraries\MinHook-Detours Libraries\MinHook-Detours Libraries\MinHook-Detours Libraries\MinHook-Detours Libraries\thread-call-stack-scanner Libraries\thread-call-stack-scanner Libraries\thread-call-stack-scanner Libraries\thread-call-stack-scanner Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Resource Files ================================================ FILE: src/windhawk/engine/functions.cpp ================================================ #include "stdafx.h" #include "functions.h" #include "var_init_once.h" namespace Functions { namespace { // Source: // https://github.com/dotnet-bot/corert/blob/8928dfd66d98f40017ec7435df1fbada113656a8/src/Native/Runtime/windows/PalRedhawkCommon.cpp#L78 // // Given the OS handle of a loaded module, compute the upper and lower virtual // address bounds (inclusive). void PalGetModuleBounds(HANDLE hOsHandle, _Out_ BYTE** ppLowerBound, _Out_ BYTE** ppUpperBound) { BYTE* pbModule = (BYTE*)hOsHandle; DWORD cbModule; IMAGE_NT_HEADERS* pNtHeaders = (IMAGE_NT_HEADERS*)(pbModule + ((IMAGE_DOS_HEADER*)hOsHandle)->e_lfanew); if (pNtHeaders->OptionalHeader.Magic == IMAGE_NT_OPTIONAL_HDR32_MAGIC) cbModule = ((IMAGE_OPTIONAL_HEADER32*)&pNtHeaders->OptionalHeader) ->SizeOfImage; else cbModule = ((IMAGE_OPTIONAL_HEADER64*)&pNtHeaders->OptionalHeader) ->SizeOfImage; *ppLowerBound = pbModule; *ppUpperBound = pbModule + cbModule - 1; } } // namespace // https://github.com/tidwall/match.c // // match returns true if str matches pattern. This is a very // simple wildcard match where '*' matches on any number characters // and '?' matches on any one character. // // pattern: // { term } // term: // '*' matches any sequence of non-Separator characters // '?' matches any single non-Separator character // c matches character c (c != '*', '?') bool wcsmatch(PCWSTR pat, size_t plen, PCWSTR str, size_t slen) { if (plen < 0) plen = wcslen(pat); if (slen < 0) slen = wcslen(str); while (plen > 0) { if (pat[0] == L'*') { if (plen == 1) return true; if (pat[1] == L'*') { pat++; plen--; continue; } if (wcsmatch(pat + 1, plen - 1, str, slen)) return true; if (slen == 0) return false; str++; slen--; continue; } if (slen == 0) return false; if (pat[0] != L'?' && str[0] != pat[0]) return false; pat++; plen--; str++; slen--; } return slen == 0 && plen == 0; } std::vector SplitString(std::wstring_view s, WCHAR delim) { // https://stackoverflow.com/a/48403210 auto view = s | std::views::split(delim) | std::views::transform([](auto&& rng) { return std::wstring_view(rng.data(), rng.size()); }); return std::vector(view.begin(), view.end()); } std::vector SplitStringToViews(std::wstring_view s, WCHAR delim) { // https://stackoverflow.com/a/48403210 auto view = s | std::views::split(delim) | std::views::transform([](auto&& rng) { return std::wstring_view(rng.data(), rng.size()); }); return std::vector(view.begin(), view.end()); } // https://stackoverflow.com/a/29752943 std::wstring ReplaceAll(std::wstring_view source, std::wstring_view from, std::wstring_view to, bool ignoreCase) { auto findString = [ignoreCase](std::wstring_view haystack, std::wstring_view needle, size_t pos) -> size_t { if (!ignoreCase) { return haystack.find(needle, pos); } auto it = std::search( haystack.begin() + pos, haystack.end(), needle.begin(), needle.end(), [](WCHAR ch1, WCHAR ch2) { LCMapStringEx(LOCALE_NAME_USER_DEFAULT, LCMAP_UPPERCASE, &ch1, 1, &ch1, 1, nullptr, nullptr, 0); LCMapStringEx(LOCALE_NAME_USER_DEFAULT, LCMAP_UPPERCASE, &ch2, 1, &ch2, 1, nullptr, nullptr, 0); return ch1 == ch2; }); if (it == haystack.end()) { return haystack.npos; } return std::distance(haystack.begin(), it); }; std::wstring newString; size_t lastPos = 0; size_t findPos; while ((findPos = findString(source, from, lastPos)) != source.npos) { newString.append(source, lastPos, findPos - lastPos); newString += to; lastPos = findPos + from.length(); } // Care for the rest after last occurrence. newString += source.substr(lastPos); return newString; } bool DoesPathMatchPattern(std::wstring_view path, std::wstring_view pattern, bool explicitOnly) { if (pattern.empty()) { return false; } // A case-insensitive comparison as recommended here: // https://stackoverflow.com/q/410502 std::wstring pathUpper{path}; // Don't use CharUpperBuff to avoid depending on user32.dll. Use // LCMapStringEx just like it's called internally by CharUpperBuff. // CharUpperBuff(&pathUpper[0], wil::safe_cast(pathUpper.length())); LCMapStringEx(LOCALE_NAME_USER_DEFAULT, LCMAP_UPPERCASE, &pathUpper[0], wil::safe_cast(pathUpper.length()), &pathUpper[0], wil::safe_cast(pathUpper.length()), nullptr, nullptr, 0); std::wstring_view pathFileNameUpper = pathUpper; if (size_t i = pathFileNameUpper.rfind(L'\\'); i != pathFileNameUpper.npos) { pathFileNameUpper.remove_prefix(i + 1); } for (const auto& patternPartView : SplitStringToViews(pattern, L'|')) { if (explicitOnly) { bool patternIsWildcard = patternPartView.find_first_of(L"*?") != patternPartView.npos; if (patternIsWildcard) { // If the pattern contains wildcards, it's not an explicit // match. continue; } } auto patternPart = std::wstring{patternPartView}; #ifndef _WIN64 BOOL isWow64; if (IsWow64Process(GetCurrentProcess(), &isWow64) && isWow64) { // Get the native Program Files path regardless of the current // process architecture. patternPart = ReplaceAll(patternPart, L"%ProgramFiles%", L"%ProgramW6432%", /*ignoreCase=*/true); } #endif // _WIN64 auto patternPartNormalized = wil::ExpandEnvironmentStrings(patternPart.c_str()); // CharUpperBuff(&patternPartNormalized[0], // wil::safe_cast(patternPartNormalized.length())); LCMapStringEx(LOCALE_NAME_USER_DEFAULT, LCMAP_UPPERCASE, &patternPartNormalized[0], wil::safe_cast(patternPartNormalized.length()), &patternPartNormalized[0], wil::safe_cast(patternPartNormalized.length()), nullptr, nullptr, 0); std::wstring_view match = pathUpper; // If there's no backslash in the pattern part, match only against the // file name, not the full path. if (patternPartNormalized.find(L'\\') == patternPartNormalized.npos) { match = pathFileNameUpper; } if (wcsmatch(patternPartNormalized.data(), patternPartNormalized.length(), match.data(), match.length())) { return true; } } return false; } void** FindImportPtr(HMODULE hFindInModule, PCSTR pModuleName, PCSTR pImportName) { IMAGE_DOS_HEADER* pDosHeader = (IMAGE_DOS_HEADER*)hFindInModule; IMAGE_NT_HEADERS* pNtHeader = (IMAGE_NT_HEADERS*)((char*)pDosHeader + pDosHeader->e_lfanew); if (pNtHeader->OptionalHeader.NumberOfRvaAndSizes <= IMAGE_DIRECTORY_ENTRY_IMPORT || !pNtHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT] .VirtualAddress) { return nullptr; } ULONG_PTR ImageBase = (ULONG_PTR)hFindInModule; IMAGE_IMPORT_DESCRIPTOR* pImportDescriptor = (IMAGE_IMPORT_DESCRIPTOR*)(ImageBase + pNtHeader->OptionalHeader .DataDirectory [IMAGE_DIRECTORY_ENTRY_IMPORT] .VirtualAddress); while (pImportDescriptor->OriginalFirstThunk) { if (_stricmp((char*)(ImageBase + pImportDescriptor->Name), pModuleName) == 0) { IMAGE_THUNK_DATA* pOriginalFirstThunk = (IMAGE_THUNK_DATA*)(ImageBase + pImportDescriptor->OriginalFirstThunk); IMAGE_THUNK_DATA* pFirstThunk = (IMAGE_THUNK_DATA*)(ImageBase + pImportDescriptor->FirstThunk); while (ULONG_PTR ImageImportByName = pOriginalFirstThunk->u1.Function) { if (!IMAGE_SNAP_BY_ORDINAL(ImageImportByName)) { if ((ULONG_PTR)pImportName & ~0xFFFF) { ImageImportByName += sizeof(WORD); if (strcmp((char*)(ImageBase + ImageImportByName), pImportName) == 0) { return (void**)pFirstThunk; } } } else { if (((ULONG_PTR)pImportName & ~0xFFFF) == 0) { if (IMAGE_ORDINAL(ImageImportByName) == (ULONG_PTR)pImportName) { return (void**)pFirstThunk; } } } pOriginalFirstThunk++; pFirstThunk++; } } pImportDescriptor++; } return nullptr; } BOOL GetFullAccessSecurityDescriptor( _Outptr_ PSECURITY_DESCRIPTOR* SecurityDescriptor, _Out_opt_ PULONG SecurityDescriptorSize) { // http://rsdn.org/forum/winapi/7510772.flat // // For full access maniacs :) // Full access for the "Everyone" group and for the "All [Restricted] App // Packages" groups. The integrity label is Untrusted (lowest level). // // D - DACL // P - Protected // A - Access Allowed // GA - GENERIC_ALL // WD - 'All' Group (World) // S-1-15-2-1 - All Application Packages // S-1-15-2-2 - All Restricted Application Packages // // S - SACL // ML - Mandatory Label // NW - No Write-Up policy // S-1-16-0 - Untrusted Mandatory Level PCWSTR pszStringSecurityDescriptor = L"D:P(A;;GA;;;WD)(A;;GA;;;S-1-15-2-1)(A;;GA;;;S-1-15-2-2)S:(ML;;NW;;;S-" L"1-16-0)"; return ConvertStringSecurityDescriptorToSecurityDescriptor( pszStringSecurityDescriptor, SDDL_REVISION_1, SecurityDescriptor, SecurityDescriptorSize); } // Based on: // http://securityxploded.com/ntcreatethreadex.php // Another reference: // https://github.com/winsiderss/systeminformer/blob/25846070780183848dc8d8f335a54fa6e636e281/phlib/basesup.c#L217 HANDLE MyCreateRemoteThread(HANDLE hProcess, LPTHREAD_START_ROUTINE lpStartAddress, LPVOID lpParameter, ULONG createFlags) { using NtCreateThreadEx_t = NTSTATUS(WINAPI*)( _Out_ PHANDLE ThreadHandle, _In_ ACCESS_MASK DesiredAccess, _In_opt_ LPVOID ObjectAttributes, // POBJECT_ATTRIBUTES _In_ HANDLE ProcessHandle, _In_ PVOID StartRoutine, // PUSER_THREAD_START_ROUTINE _In_opt_ PVOID Argument, _In_ ULONG CreateFlags, // THREAD_CREATE_FLAGS_* _In_ SIZE_T ZeroBits, _In_ SIZE_T StackSize, _In_ SIZE_T MaximumStackSize, _In_opt_ LPVOID AttributeList // PPS_ATTRIBUTE_LIST ); GET_PROC_ADDRESS_ONCE(NtCreateThreadEx_t, pNtCreateThreadEx, L"ntdll.dll", "NtCreateThreadEx"); if (!pNtCreateThreadEx) { SetLastError(ERROR_PROC_NOT_FOUND); return nullptr; } HANDLE hThread; NTSTATUS result = pNtCreateThreadEx(&hThread, THREAD_ALL_ACCESS, nullptr, hProcess, lpStartAddress, lpParameter, createFlags, 0, 0, 0, nullptr); if (result < 0) { SetLastError(LsaNtStatusToWinError(result)); return nullptr; } return hThread; } void GetNtVersionNumbers(ULONG* pNtMajorVersion, ULONG* pNtMinorVersion, ULONG* pNtBuildNumber) { using RtlGetNtVersionNumbers_t = void(WINAPI*)(ULONG * pNtMajorVersion, ULONG * pNtMinorVersion, ULONG * pNtBuildNumber); GET_PROC_ADDRESS_ONCE(RtlGetNtVersionNumbers_t, pRtlGetNtVersionNumbers, L"ntdll.dll", "RtlGetNtVersionNumbers"); if (pRtlGetNtVersionNumbers) { pRtlGetNtVersionNumbers(pNtMajorVersion, pNtMinorVersion, pNtBuildNumber); // The upper 4 bits are reserved for the type of the OS build. // https://dennisbabkin.com/blog/?t=how-to-tell-the-real-version-of-windows-your-app-is-running-on *pNtBuildNumber &= ~0xF0000000; return; } // Use GetVersionEx as a fallback. #pragma warning(push) #pragma warning(disable : 4996) // disable deprecation message OSVERSIONINFO versionInfo = {sizeof(OSVERSIONINFO)}; if (GetVersionEx(&versionInfo)) { *pNtMajorVersion = versionInfo.dwMajorVersion; *pNtMinorVersion = versionInfo.dwMinorVersion; *pNtBuildNumber = versionInfo.dwBuildNumber; return; } #pragma warning(pop) *pNtMajorVersion = 0; *pNtMinorVersion = 0; *pNtBuildNumber = 0; } bool IsWindowsVersionOrGreaterWithBuildNumber(WORD wMajorVersion, WORD wMinorVersion, WORD wBuildNumber) { ULONG majorVersion = 0; ULONG minorVersion = 0; ULONG buildNumber = 0; Functions::GetNtVersionNumbers(&majorVersion, &minorVersion, &buildNumber); if (majorVersion != wMajorVersion) { return majorVersion > wMajorVersion; } if (minorVersion != wMinorVersion) { return minorVersion > wMinorVersion; } return buildNumber >= wBuildNumber; } // Based on: // https://github.com/dotnet-bot/corert/blob/8928dfd66d98f40017ec7435df1fbada113656a8/src/Native/Runtime/windows/PalRedhawkCommon.cpp#L109 // // Reads through the PE header of the specified module, and returns // the module's matching PDB's signature GUID and age by // fishing them out of the last IMAGE_DEBUG_DIRECTORY of type // IMAGE_DEBUG_TYPE_CODEVIEW. Used when sending the ModuleLoad event // to help profilers find matching PDBs for loaded modules. // // Arguments: // // [in] hOsHandle - OS Handle for module from which to get PDB info // [out] pGuidSignature - PDB's signature GUID to be placed here // [out] pdwAge - PDB's age to be placed here // // This is a simplification of similar code in desktop CLR's GetCodeViewInfo // in eventtrace.cpp. bool ModuleGetPDBInfo(HANDLE hOsHandle, _Out_ GUID* pGuidSignature, _Out_ DWORD* pdwAge) { // Zero-init [out]-params ZeroMemory(pGuidSignature, sizeof(*pGuidSignature)); *pdwAge = 0; BYTE* pbModule = (BYTE*)hOsHandle; IMAGE_NT_HEADERS const* pNtHeaders = (IMAGE_NT_HEADERS*)(pbModule + ((IMAGE_DOS_HEADER*)hOsHandle)->e_lfanew); IMAGE_DATA_DIRECTORY const* rgDataDirectory = NULL; if (pNtHeaders->OptionalHeader.Magic == IMAGE_NT_OPTIONAL_HDR32_MAGIC) rgDataDirectory = ((IMAGE_OPTIONAL_HEADER32 const*)&pNtHeaders->OptionalHeader) ->DataDirectory; else rgDataDirectory = ((IMAGE_OPTIONAL_HEADER64 const*)&pNtHeaders->OptionalHeader) ->DataDirectory; IMAGE_DATA_DIRECTORY const* pDebugDataDirectory = &rgDataDirectory[IMAGE_DIRECTORY_ENTRY_DEBUG]; // In Redhawk, modules are loaded as MAPPED, so we don't have to worry about // dealing with FLAT files (with padding missing), so header addresses can // be used as is IMAGE_DEBUG_DIRECTORY const* rgDebugEntries = (IMAGE_DEBUG_DIRECTORY const*)(pbModule + pDebugDataDirectory->VirtualAddress); DWORD cbDebugEntries = pDebugDataDirectory->Size; if (cbDebugEntries < sizeof(IMAGE_DEBUG_DIRECTORY)) return false; // Since rgDebugEntries is an array of IMAGE_DEBUG_DIRECTORYs, // cbDebugEntries should be a multiple of sizeof(IMAGE_DEBUG_DIRECTORY). if (cbDebugEntries % sizeof(IMAGE_DEBUG_DIRECTORY) != 0) return false; // CodeView RSDS debug information -> PDB 7.00 struct CV_INFO_PDB70 { DWORD magic; GUID signature; // unique identifier DWORD age; // an always-incrementing value _Field_z_ char path[MAX_PATH]; // zero terminated string with the name // of the PDB file }; // Temporary storage for a CV_INFO_PDB70 and its size (which could be less // than sizeof(CV_INFO_PDB70); see below). struct PdbInfo { CV_INFO_PDB70* m_pPdb70; ULONG m_cbPdb70; }; // Grab module bounds so we can do some rough sanity checking before we // follow any RVAs BYTE* pbModuleLowerBound = NULL; BYTE* pbModuleUpperBound = NULL; PalGetModuleBounds(hOsHandle, &pbModuleLowerBound, &pbModuleUpperBound); // Iterate through all debug directory entries. The convention is that // debuggers & profilers typically just use the very last // IMAGE_DEBUG_TYPE_CODEVIEW entry. Treat raw bytes we read as untrusted. PdbInfo pdbInfoLast = {0}; int cEntries = cbDebugEntries / sizeof(IMAGE_DEBUG_DIRECTORY); for (int i = 0; i < cEntries; i++) { if ((BYTE*)(&rgDebugEntries[i]) + sizeof(rgDebugEntries[i]) >= pbModuleUpperBound) { // Bogus pointer return false; } if (rgDebugEntries[i].Type != IMAGE_DEBUG_TYPE_CODEVIEW) continue; // Get raw data pointed to by this IMAGE_DEBUG_DIRECTORY // AddressOfRawData is generally set properly for Redhawk modules, so we // don't have to worry about using PointerToRawData and converting it to // an RVA if (rgDebugEntries[i].AddressOfRawData == NULL) continue; DWORD rvaOfRawData = rgDebugEntries[i].AddressOfRawData; ULONG cbDebugData = rgDebugEntries[i].SizeOfData; if (cbDebugData < size_t(&((CV_INFO_PDB70*)0)->magic) + sizeof(((CV_INFO_PDB70*)0)->magic)) { // raw data too small to contain magic number at expected spot, so // its format is not recognizable. Skip continue; } // Verify the magic number is as expected const DWORD CV_SIGNATURE_RSDS = 0x53445352; CV_INFO_PDB70* pPdb70 = (CV_INFO_PDB70*)(pbModule + rvaOfRawData); if ((BYTE*)(pPdb70) + cbDebugData >= pbModuleUpperBound) { // Bogus pointer return false; } if (pPdb70->magic != CV_SIGNATURE_RSDS) { // Unrecognized magic number. Skip continue; } // From this point forward, the format should adhere to the expected // layout of CV_INFO_PDB70. If we find otherwise, then assume the // IMAGE_DEBUG_DIRECTORY is outright corrupt. // Verify sane size of raw data if (cbDebugData > sizeof(CV_INFO_PDB70)) return false; // cbDebugData actually can be < sizeof(CV_INFO_PDB70), since the "path" // field can be truncated to its actual data length (i.e., fewer than // MAX_PATH chars may be present in the PE file). In some cases, though, // cbDebugData will include all MAX_PATH chars even though path gets // null-terminated well before the MAX_PATH limit. // Gotta have at least one byte of the path if (cbDebugData < offsetof(CV_INFO_PDB70, path) + sizeof(char)) return false; // How much space is available for the path? size_t cchPathMaxIncludingNullTerminator = (cbDebugData - offsetof(CV_INFO_PDB70, path)) / sizeof(char); // assert(cchPathMaxIncludingNullTerminator >= 1); // Guaranteed above // Verify path string fits inside the declared size size_t cchPathActualExcludingNullTerminator = strnlen_s(pPdb70->path, cchPathMaxIncludingNullTerminator); if (cchPathActualExcludingNullTerminator == cchPathMaxIncludingNullTerminator) { // This is how strnlen indicates failure--it couldn't find the null // terminator within the buffer size specified return false; } // Looks valid. Remember it. pdbInfoLast.m_pPdb70 = pPdb70; pdbInfoLast.m_cbPdb70 = cbDebugData; } // Take the last IMAGE_DEBUG_TYPE_CODEVIEW entry we saw, and return it to // the caller if (pdbInfoLast.m_pPdb70 != NULL) { memcpy(pGuidSignature, &pdbInfoLast.m_pPdb70->signature, sizeof(GUID)); *pdwAge = pdbInfoLast.m_pPdb70->age; return true; } return false; } std::string GetModuleVersion(HMODULE hModule) { // Avoid having version.dll in the import table, since it might not be // available in all cases, e.g. sandboxed processes. using VerQueryValueW_t = decltype(&VerQueryValueW); LOAD_LIBRARY_GET_PROC_ADDRESS_ONCE( VerQueryValueW_t, pVerQueryValueW, L"version.dll", LOAD_LIBRARY_SEARCH_SYSTEM32, "VerQueryValueW"); if (!pVerQueryValueW) { return {}; } HRSRC hResource = FindResource(hModule, MAKEINTRESOURCE(VS_VERSION_INFO), VS_FILE_INFO); if (!hResource) { return {}; } HGLOBAL hGlobal = LoadResource(hModule, hResource); if (!hGlobal) { return {}; } void* pData = LockResource(hGlobal); if (!pData) { return {}; } VS_FIXEDFILEINFO* pFixedFileInfo = nullptr; UINT uPtrLen = 0; if (!pVerQueryValueW(pData, L"\\", reinterpret_cast(&pFixedFileInfo), &uPtrLen) || uPtrLen == 0) { return {}; } WORD nMajor = HIWORD(pFixedFileInfo->dwFileVersionMS); WORD nMinor = LOWORD(pFixedFileInfo->dwFileVersionMS); WORD nBuild = HIWORD(pFixedFileInfo->dwFileVersionLS); WORD nQFE = LOWORD(pFixedFileInfo->dwFileVersionLS); std::string result; result += std::to_string(nMajor); result += '.'; result += std::to_string(nMinor); result += '.'; result += std::to_string(nBuild); result += '.'; result += std::to_string(nQFE); return result; } HRESULT SetThreadDescriptionIfAvailable(HANDLE hThread, PCWSTR lpThreadDescription) { using SetThreadDescription_t = decltype(&SetThreadDescription); LOAD_LIBRARY_GET_PROC_ADDRESS_ONCE( SetThreadDescription_t, pSetThreadDescription, L"kernel32.dll", LOAD_LIBRARY_SEARCH_SYSTEM32, "SetThreadDescription"); if (!pSetThreadDescription) { return E_NOTIMPL; } return pSetThreadDescription(hThread, lpThreadDescription); } } // namespace Functions ================================================ FILE: src/windhawk/engine/functions.h ================================================ #pragma once namespace Functions { bool wcsmatch(PCWSTR pat, size_t plen, PCWSTR str, size_t slen); std::vector SplitString(std::wstring_view s, WCHAR delim); std::vector SplitStringToViews(std::wstring_view s, WCHAR delim); std::wstring ReplaceAll(std::wstring_view source, std::wstring_view from, std::wstring_view to, bool ignoreCase = false); bool DoesPathMatchPattern(std::wstring_view path, std::wstring_view pattern, bool explicitOnly = false); void** FindImportPtr(HMODULE hFindInModule, PCSTR pModuleName, PCSTR pImportName); BOOL GetFullAccessSecurityDescriptor( _Outptr_ PSECURITY_DESCRIPTOR* SecurityDescriptor, _Out_opt_ PULONG SecurityDescriptorSize); // https://waleedassar.blogspot.com/2012/12/skipthreadattach.html enum MyCreateRemoteThreadFlags : ULONG { MY_REMOTE_THREAD_CREATE_SUSPENDED = 0x01, MY_REMOTE_THREAD_THREAD_ATTACH_EXEMPT = 0x02, MY_REMOTE_THREAD_HIDE_FROM_DEBUGGER = 0x04, MY_REMOTE_THREAD_LOADER_WORKER = 0x10, // since THRESHOLD MY_REMOTE_THREAD_SKIP_LOADER_INIT = 0x20, // since REDSTONE2 MY_REMOTE_THREAD_BYPASS_PROCESS_FREEZE = 0x40, // since 19H1 }; // Using MyCreateRemoteThread instead of CreateRemoteThread provides the // following benefits: // * On Windows 7, it allows to create a remote thread in a process running in // another session. // * It allows providing extra flags. We use the // MY_REMOTE_THREAD_THREAD_ATTACH_EXEMPT flag to reduce incompatibility with // other processes. HANDLE MyCreateRemoteThread(HANDLE hProcess, LPTHREAD_START_ROUTINE lpStartAddress, LPVOID lpParameter, ULONG createFlags); void GetNtVersionNumbers(ULONG* pNtMajorVersion, ULONG* pNtMinorVersion, ULONG* pNtBuildNumber); bool IsWindowsVersionOrGreaterWithBuildNumber(WORD wMajorVersion, WORD wMinorVersion, WORD wBuildNumber); bool ModuleGetPDBInfo(HANDLE hOsHandle, _Out_ GUID* pGuidSignature, _Out_ DWORD* pdwAge); std::string GetModuleVersion(HMODULE hModule); HRESULT SetThreadDescriptionIfAvailable(HANDLE hThread, PCWSTR lpThreadDescription); } // namespace Functions ================================================ FILE: src/windhawk/engine/inject_shellcode/.clang-format ================================================ BasedOnStyle: Microsoft UseTab: ForContinuationAndIndentation PointerAlignment: Left AlignConsecutiveMacros: AcrossEmptyLines ================================================ FILE: src/windhawk/engine/inject_shellcode/InjectShellcode.filters ================================================  {4FC737F1-C7A5-4376-A066-2A32D752A2FF} cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx {93995380-89BD-4b04-88EB-625FBE52EBFB} h;hh;hpp;hxx;hm;inl;inc;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 ================================================ FILE: src/windhawk/engine/inject_shellcode/InjectShellcode.sln ================================================  Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio Version 17 VisualStudioVersion = 17.3.32901.215 MinimumVisualStudioVersion = 10.0.40219.1 Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "InjectShellcode", "InjectShellcode.vcxproj", "{EDF9A877-A2A8-43AF-8BCC-897F5B28E104}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|ARM64 = Debug|ARM64 Debug|Win32 = Debug|Win32 Debug|x64 = Debug|x64 Release|ARM64 = Release|ARM64 Release|Win32 = Release|Win32 Release|x64 = Release|x64 EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution {EDF9A877-A2A8-43AF-8BCC-897F5B28E104}.Debug|ARM64.ActiveCfg = Debug|ARM64 {EDF9A877-A2A8-43AF-8BCC-897F5B28E104}.Debug|ARM64.Build.0 = Debug|ARM64 {EDF9A877-A2A8-43AF-8BCC-897F5B28E104}.Debug|Win32.ActiveCfg = Debug|Win32 {EDF9A877-A2A8-43AF-8BCC-897F5B28E104}.Debug|Win32.Build.0 = Debug|Win32 {EDF9A877-A2A8-43AF-8BCC-897F5B28E104}.Debug|x64.ActiveCfg = Debug|x64 {EDF9A877-A2A8-43AF-8BCC-897F5B28E104}.Debug|x64.Build.0 = Debug|x64 {EDF9A877-A2A8-43AF-8BCC-897F5B28E104}.Release|ARM64.ActiveCfg = Release|ARM64 {EDF9A877-A2A8-43AF-8BCC-897F5B28E104}.Release|ARM64.Build.0 = Release|ARM64 {EDF9A877-A2A8-43AF-8BCC-897F5B28E104}.Release|Win32.ActiveCfg = Release|Win32 {EDF9A877-A2A8-43AF-8BCC-897F5B28E104}.Release|Win32.Build.0 = Release|Win32 {EDF9A877-A2A8-43AF-8BCC-897F5B28E104}.Release|x64.ActiveCfg = Release|x64 {EDF9A877-A2A8-43AF-8BCC-897F5B28E104}.Release|x64.Build.0 = Release|x64 EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {ECA2D17E-0AA3-4DA9-941B-8F0232D3A06D} EndGlobalSection EndGlobal ================================================ FILE: src/windhawk/engine/inject_shellcode/InjectShellcode.vcxproj ================================================  Debug ARM64 Debug Win32 Debug x64 Release ARM64 Release Win32 Release x64 {EDF9A877-A2A8-43AF-8BCC-897F5B28E104} Win32Proj InjectShellcode 10.0 Application true v143 Unicode Application true v143 Unicode Application true v143 Unicode Application false v143 true Unicode Application false v143 true Unicode Application false v143 true Unicode true true true false false false Level3 Disabled WIN32;_DEBUG;_WINDOWS;%(PreprocessorDefinitions) false stdcpp20 Windows true Level3 Disabled WIN32;_DEBUG;_WINDOWS;%(PreprocessorDefinitions) false stdcpp20 Windows true Level3 Disabled WIN32;_DEBUG;_WINDOWS;%(PreprocessorDefinitions) false NoExtensions stdcpp20 Windows true Level3 MaxSpeed true true WIN32;NDEBUG;_WINDOWS;%(PreprocessorDefinitions) false stdcpp20 Windows true true true Level3 MaxSpeed true true WIN32;NDEBUG;_WINDOWS;%(PreprocessorDefinitions) false stdcpp20 Windows true true true Level3 MaxSpeed true true WIN32;NDEBUG;_WINDOWS;%(PreprocessorDefinitions) false NoExtensions stdcpp20 Windows true true true ================================================ FILE: src/windhawk/engine/inject_shellcode/main.cpp ================================================ //===============================================================================================// // The code is based on code from the ReflectiveDLLInjection project: // https://github.com/stephenfewer/ReflectiveDLLInjection/tree/master/dll/src // Original license can be found below. //===============================================================================================// // Copyright (c) 2012, Stephen Fewer of Harmony Security (www.harmonysecurity.com) // All rights reserved. // // Redistribution and use in source and binary forms, with or without modification, are permitted // provided that the following conditions are met: // // * Redistributions of source code must retain the above copyright notice, this list of // conditions and the following disclaimer. // // * Redistributions in binary form must reproduce the above copyright notice, this list of // conditions and the following disclaimer in the documentation and/or other materials provided // with the distribution. // // * Neither the name of Harmony Security nor the names of its contributors may be used to // endorse or promote products derived from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR // IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND // FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR // CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR // CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR // SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR // OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE // POSSIBILITY OF SUCH DAMAGE. //===============================================================================================// #include #include #include "../dll_inject.h" // #define MESSAGE_BOX_TEST #define DEREF(name) *(UINT_PTR*)(name) #define DEREF_64(name) *(DWORD64*)(name) #define DEREF_32(name) *(DWORD*)(name) #define DEREF_16(name) *(WORD*)(name) #define DEREF_8(name) *(BYTE*)(name) //===============================================================================================// typedef struct _UNICODE_STR { USHORT Length; USHORT MaximumLength; PWSTR pBuffer; } UNICODE_STR, *PUNICODE_STR; // WinDbg> dt -v ntdll!_LDR_DATA_TABLE_ENTRY //__declspec( align(8) ) typedef struct _LDR_DATA_TABLE_ENTRY { LIST_ENTRY InLoadOrderLinks; LIST_ENTRY InMemoryOrderModuleList; LIST_ENTRY InInitializationOrderModuleList; PVOID DllBase; PVOID EntryPoint; ULONG SizeOfImage; UNICODE_STR FullDllName; UNICODE_STR BaseDllName; ULONG Flags; SHORT LoadCount; SHORT TlsIndex; LIST_ENTRY HashTableEntry; ULONG TimeDateStamp; } LDR_DATA_TABLE_ENTRY, *PLDR_DATA_TABLE_ENTRY; // WinDbg> dt -v ntdll!_PEB_LDR_DATA typedef struct _PEB_LDR_DATA //, 7 elements, 0x28 bytes { DWORD dwLength; DWORD dwInitialized; LPVOID lpSsHandle; LIST_ENTRY InLoadOrderModuleList; LIST_ENTRY InMemoryOrderModuleList; LIST_ENTRY InInitializationOrderModuleList; LPVOID lpEntryInProgress; } PEB_LDR_DATA, *PPEB_LDR_DATA; // WinDbg> dt -v ntdll!_PEB_FREE_BLOCK typedef struct _PEB_FREE_BLOCK // 2 elements, 0x8 bytes { struct _PEB_FREE_BLOCK* pNext; DWORD dwSize; } PEB_FREE_BLOCK, *PPEB_FREE_BLOCK; // struct _PEB is defined in Winternl.h but it is incomplete // https://ntdoc.m417z.com/peb typedef struct __PEB { BOOLEAN InheritedAddressSpace; BOOLEAN ReadImageFileExecOptions; BOOLEAN BeingDebugged; union { BOOLEAN BitField; struct { BOOLEAN ImageUsesLargePages : 1; BOOLEAN IsProtectedProcess : 1; BOOLEAN IsImageDynamicallyRelocated : 1; BOOLEAN SkipPatchingUser32Forwarders : 1; BOOLEAN IsPackagedProcess : 1; BOOLEAN IsAppContainer : 1; BOOLEAN IsProtectedProcessLight : 1; BOOLEAN IsLongPathAwareProcess : 1; }; }; HANDLE Mutant; PVOID ImageBaseAddress; PPEB_LDR_DATA Ldr; /*PRTL_USER_PROCESS_PARAMETERS*/ PVOID ProcessParameters; PVOID SubSystemData; PVOID ProcessHeap; PRTL_CRITICAL_SECTION FastPebLock; PSLIST_HEADER AtlThunkSListPtr; PVOID IFEOKey; union { ULONG CrossProcessFlags; struct { ULONG ProcessInJob : 1; ULONG ProcessInitializing : 1; ULONG ProcessUsingVEH : 1; ULONG ProcessUsingVCH : 1; ULONG ProcessUsingFTH : 1; ULONG ProcessPreviouslyThrottled : 1; ULONG ProcessCurrentlyThrottled : 1; ULONG ProcessImagesHotPatched : 1; ULONG ReservedBits0 : 24; }; }; union { PVOID KernelCallbackTable; PVOID UserSharedInfoPtr; }; ULONG SystemReserved; ULONG AtlThunkSListPtr32; /*PAPI_SET_NAMESPACE*/ PVOID ApiSetMap; ULONG TlsExpansionCounter; /*PRTL_BITMAP*/ PVOID TlsBitmap; ULONG TlsBitmapBits[2]; PVOID ReadOnlySharedMemoryBase; /*PSILO_USER_SHARED_DATA*/ PVOID SharedData; PVOID* ReadOnlyStaticServerData; PVOID AnsiCodePageData; PVOID OemCodePageData; PVOID UnicodeCaseTableData; ULONG NumberOfProcessors; union { ULONG NtGlobalFlag; struct { ULONG StopOnException : 1; ULONG ShowLoaderSnaps : 1; ULONG DebugInitialCommand : 1; ULONG StopOnHungGUI : 1; ULONG HeapEnableTailCheck : 1; ULONG HeapEnableFreeCheck : 1; ULONG HeapValidateParameters : 1; ULONG HeapValidateAll : 1; ULONG ApplicationVerifier : 1; ULONG MonitorSilentProcessExit : 1; ULONG PoolEnableTagging : 1; ULONG HeapEnableTagging : 1; ULONG UserStackTraceDb : 1; ULONG KernelStackTraceDb : 1; ULONG MaintainObjectTypeList : 1; ULONG HeapEnableTagByDll : 1; ULONG DisableStackExtension : 1; ULONG EnableCsrDebug : 1; ULONG EnableKDebugSymbolLoad : 1; ULONG DisablePageKernelStacks : 1; ULONG EnableSystemCritBreaks : 1; ULONG HeapDisableCoalescing : 1; ULONG EnableCloseExceptions : 1; ULONG EnableExceptionLogging : 1; ULONG EnableHandleTypeTagging : 1; ULONG HeapPageAllocs : 1; ULONG DebugInitialCommandEx : 1; ULONG DisableDbgPrint : 1; ULONG CritSecEventCreation : 1; ULONG LdrTopDown : 1; ULONG EnableHandleExceptions : 1; ULONG DisableProtDlls : 1; } NtGlobalFlags; }; LARGE_INTEGER CriticalSectionTimeout; SIZE_T HeapSegmentReserve; SIZE_T HeapSegmentCommit; SIZE_T HeapDeCommitTotalFreeThreshold; SIZE_T HeapDeCommitFreeBlockThreshold; ULONG NumberOfHeaps; ULONG MaximumNumberOfHeaps; PVOID* ProcessHeaps; PVOID GdiSharedHandleTable; PVOID ProcessStarterHelper; ULONG GdiDCAttributeList; PRTL_CRITICAL_SECTION LoaderLock; ULONG OSMajorVersion; ULONG OSMinorVersion; USHORT OSBuildNumber; USHORT OSCSDVersion; ULONG OSPlatformId; ULONG ImageSubsystem; ULONG ImageSubsystemMajorVersion; ULONG ImageSubsystemMinorVersion; KAFFINITY ActiveProcessAffinityMask; } _PEB, *_PPEB; struct ModuleExportLookupData { const char* moduleName; size_t moduleNameLength; const char** functionNames; void*** functionTargets; size_t functionsLeft; }; #if defined(_M_X64) #define VOLATILE_X64 volatile #else #define VOLATILE_X64 #endif __declspec(dllexport) void* __stdcall InjectShellcode(void* pParameter, DWORD_PTR apcArgument2, DWORD_PTR apcArgument3) { const DllInject::LOAD_LIBRARY_REMOTE_DATA* pInjData = (const DllInject::LOAD_LIBRARY_REMOTE_DATA*)pParameter; bool threadCreatedFromAPC = false; bool runningFromAPC = false; if ((DWORD_PTR)pInjData & 1) { pInjData = (const DllInject::LOAD_LIBRARY_REMOTE_DATA*)((DWORD_PTR)pInjData & ~1); threadCreatedFromAPC = true; } else { runningFromAPC = pInjData->bRunningFromAPC; } // Get the Process Environment Block. // https://github.com/sandboxie-plus/Sandboxie/blob/dbf7ae81cfc50db3598085472e5f143b7653e4a8/Sandboxie/common/my_xeb.h#L433 #if defined(_M_X64) _PPEB peb = (_PPEB)__readgsqword(0x60); #elif defined(_M_IX86) _PPEB peb = (_PPEB)__readfsdword(0x30); #elif defined(_M_ARM64) _PPEB peb = *(_PPEB*)(__getReg(18) + 0x60); // TEB in x18 #else #error "This architecture is currently unsupported" #endif // If there's no loader data, we can't do much. if (!peb->Ldr) { return nullptr; } //////////////////////////////////////////////////////////////////////////////// // KERNEL32.DLL const char szKernel32Dll[] = {'K', 'E', 'R', 'N', 'E', 'L', '3', '2', '.', 'D', 'L', 'L'}; // Add volatile to long strings to prevent the compiler from using XMM registers and storing their values in the // data section. const char szLoadLibraryW[] = {'L', 'o', 'a', 'd', 'L', 'i', 'b', 'r', 'a', 'r', 'y', 'W', '\0'}; const char szGetProcAddress[] = {'G', 'e', 't', 'P', 'r', 'o', 'c', 'A', 'd', 'd', 'r', 'e', 's', 's', '\0'}; const char szFreeLibrary[] = {'F', 'r', 'e', 'e', 'L', 'i', 'b', 'r', 'a', 'r', 'y', '\0'}; const char szVirtualFree[] = {'V', 'i', 'r', 't', 'u', 'a', 'l', 'F', 'r', 'e', 'e', '\0'}; const char szGetLastError[] = {'G', 'e', 't', 'L', 'a', 's', 't', 'E', 'r', 'r', 'o', 'r', '\0'}; VOLATILE_X64 const char szOutputDebugStringA[] = {'O', 'u', 't', 'p', 'u', 't', 'D', 'e', 'b', 'u', 'g', 'S', 't', 'r', 'i', 'n', 'g', 'A', '\0'}; const char szCloseHandle[] = {'C', 'l', 'o', 's', 'e', 'H', 'a', 'n', 'd', 'l', 'e', '\0'}; VOLATILE_X64 const char szSetThreadErrorMode[] = {'S', 'e', 't', 'T', 'h', 'r', 'e', 'a', 'd', 'E', 'r', 'r', 'o', 'r', 'M', 'o', 'd', 'e', '\0'}; const char szCreateThread[] = {'C', 'r', 'e', 'a', 't', 'e', 'T', 'h', 'r', 'e', 'a', 'd', '\0'}; const char* kernel32FunctionNames[] = { szLoadLibraryW, szGetProcAddress, szFreeLibrary, szVirtualFree, szGetLastError, (const char*)szOutputDebugStringA, szCloseHandle, (const char*)szSetThreadErrorMode, szCreateThread, }; decltype(&LoadLibraryW) pLoadLibraryW = nullptr; decltype(&GetProcAddress) pGetProcAddress = nullptr; decltype(&FreeLibrary) pFreeLibrary = nullptr; decltype(&VirtualFree) pVirtualFree = nullptr; decltype(&GetLastError) pGetLastError = nullptr; decltype(&OutputDebugStringA) pOutputDebugStringA = nullptr; decltype(&CloseHandle) pCloseHandle = nullptr; decltype(&SetThreadErrorMode) pSetThreadErrorMode = nullptr; decltype(&CreateThread) pCreateThread = nullptr; void** kernel32FunctionTargets[] = { (void**)&pLoadLibraryW, (void**)&pGetProcAddress, (void**)&pFreeLibrary, (void**)&pVirtualFree, (void**)&pGetLastError, (void**)&pOutputDebugStringA, (void**)&pCloseHandle, (void**)&pSetThreadErrorMode, (void**)&pCreateThread, }; static_assert(std::size(kernel32FunctionNames) == std::size(kernel32FunctionTargets)); //////////////////////////////////////////////////////////////////////////////// // Lookup data ModuleExportLookupData lookupData[3] = { { szKernel32Dll, std::size(szKernel32Dll), kernel32FunctionNames, kernel32FunctionTargets, std::size(kernel32FunctionNames), }, }; //////////////////////////////////////////////////////////////////////////////// // NTDLL.DLL const char szNtdll[] = {'N', 'T', 'D', 'L', 'L', '.', 'D', 'L', 'L'}; VOLATILE_X64 const char szNtQueueApcThread[] = {'N', 't', 'Q', 'u', 'e', 'u', 'e', 'A', 'p', 'c', 'T', 'h', 'r', 'e', 'a', 'd', '\0'}; const char szNtAlertThread[] = {'N', 't', 'A', 'l', 'e', 'r', 't', 'T', 'h', 'r', 'e', 'a', 'd', '\0'}; const char* ntdllFunctionNames[] = { (const char*)szNtQueueApcThread, szNtAlertThread, }; NTSTATUS(NTAPI * pNtQueueApcThread)(HANDLE, PVOID, PVOID, PVOID, PVOID) = nullptr; NTSTATUS(NTAPI * pNtAlertThread)(HANDLE) = nullptr; void** ntdllFunctionTargets[] = { (void**)&pNtQueueApcThread, (void**)&pNtAlertThread, }; static_assert(std::size(ntdllFunctionNames) == std::size(ntdllFunctionTargets)); // The ntdll functions are only needed for APC re-queueing. if (runningFromAPC && peb->ProcessInitializing) { lookupData[1] = { szNtdll, std::size(szNtdll), ntdllFunctionNames, ntdllFunctionTargets, std::size(ntdllFunctionNames), }; } // Process the kernels exports for the functions our loader needs. bool foundAll = false; // Get the first entry of the module list. PLIST_ENTRY pleInLoadHead = &peb->Ldr->InLoadOrderModuleList; PLIST_ENTRY pleInLoadIter = pleInLoadHead->Flink; while (pleInLoadIter != pleInLoadHead) { PLIST_ENTRY pleInLoadCurrent = pleInLoadIter; // Get the next entry. pleInLoadIter = pleInLoadIter->Flink; PCWSTR BaseDllNameBuffer = ((PLDR_DATA_TABLE_ENTRY)pleInLoadCurrent)->BaseDllName.pBuffer; USHORT BaseDllNameLength = ((PLDR_DATA_TABLE_ENTRY)pleInLoadCurrent)->BaseDllName.Length / sizeof(WCHAR); ModuleExportLookupData* lookupItem = nullptr; for (size_t mod = 0; lookupData[mod].moduleName; mod++) { auto& item = lookupData[mod]; if (item.functionsLeft == 0) continue; if (BaseDllNameLength != item.moduleNameLength) continue; USHORT i; for (i = 0; i < BaseDllNameLength; i++) { WCHAR c = BaseDllNameBuffer[i]; if (c >= 'a' && c <= 'z') c -= 'a' - 'A'; if (c != item.moduleName[i]) break; } if (i == BaseDllNameLength) { lookupItem = &item; break; } } if (!lookupItem) continue; // Variables for processing the kernel's export table. ULONG_PTR uiBaseAddress; ULONG_PTR uiAddressArray; ULONG_PTR uiNameArray; ULONG_PTR uiExportDir; ULONG_PTR uiNameOrdinals; DWORD dwNumberOfNames; // Get this modules base address. uiBaseAddress = (ULONG_PTR)((PLDR_DATA_TABLE_ENTRY)pleInLoadCurrent)->DllBase; // Get the VA of the modules NT Header. uiExportDir = uiBaseAddress + ((PIMAGE_DOS_HEADER)uiBaseAddress)->e_lfanew; // uiNameArray = the address of the modules export directory entry. uiNameArray = (ULONG_PTR) & ((PIMAGE_NT_HEADERS)uiExportDir)->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT]; // Get the VA of the export directory. uiExportDir = (uiBaseAddress + ((PIMAGE_DATA_DIRECTORY)uiNameArray)->VirtualAddress); // Get the VA for the array of name pointers. uiNameArray = (uiBaseAddress + ((PIMAGE_EXPORT_DIRECTORY)uiExportDir)->AddressOfNames); // Get the VA for the array of name ordinals. uiNameOrdinals = (uiBaseAddress + ((PIMAGE_EXPORT_DIRECTORY)uiExportDir)->AddressOfNameOrdinals); // Get the total number of named exports. dwNumberOfNames = ((PIMAGE_EXPORT_DIRECTORY)uiExportDir)->NumberOfNames; // Loop while we still have imports to find. while (lookupItem->functionsLeft > 0 && dwNumberOfNames > 0) { PCSTR pFunctionName = (PCSTR)(uiBaseAddress + DEREF_32(uiNameArray)); void** pTargetAddress = nullptr; for (size_t i = 0; i < lookupItem->functionsLeft; i++) { bool matched = false; const char* lookupFunctionName = lookupItem->functionNames[i]; for (size_t j = 0; lookupFunctionName[j] == pFunctionName[j]; j++) { if (lookupFunctionName[j] == '\0') { matched = true; break; } } if (matched) { pTargetAddress = lookupItem->functionTargets[i]; // Compact the arrays if needed. if (i < lookupItem->functionsLeft - 1) { lookupItem->functionNames[i] = lookupItem->functionNames[lookupItem->functionsLeft - 1]; lookupItem->functionTargets[i] = lookupItem->functionTargets[lookupItem->functionsLeft - 1]; } // Decrement the counter. lookupItem->functionsLeft--; break; } } // If we have found a function we want, retrieve its virtual address. if (pTargetAddress) { // Get the VA for the array of addresses. uiAddressArray = (uiBaseAddress + ((PIMAGE_EXPORT_DIRECTORY)uiExportDir)->AddressOfFunctions); // Use this function's name ordinal as an index into the array of name pointers. uiAddressArray += (DEREF_16(uiNameOrdinals) * sizeof(DWORD)); // Store this function's VA. *pTargetAddress = (void*)(uiBaseAddress + DEREF_32(uiAddressArray)); } // Move to the next exported function name in the array. uiNameArray += sizeof(DWORD); // Move to the next exported function name ordinal in the array. uiNameOrdinals += sizeof(WORD); // Decrement the counter for the number of names left to process. dwNumberOfNames--; } // Stop searching when we have found all the required functions. foundAll = true; for (size_t mod = 0; lookupData[mod].moduleName; mod++) { const auto& item = lookupData[mod]; if (item.functionsLeft > 0) { foundAll = false; break; } } if (foundAll) break; } #ifdef MESSAGE_BOX_TEST HMODULE hUser32 = pLoadLibraryW(L"user32.dll"); decltype(&MessageBoxA) pMessageBoxW = (decltype(&MessageBoxA))pGetProcAddress(hUser32, "MessageBoxA"); const char szMessage[] = {'H', 'e', 'l', 'l', 'o', ',', ' ', 'w', 'o', 'r', 'l', 'd', '!', '\0'}; MessageBoxA(nullptr, szMessage, szMessage, MB_OK); return nullptr; #endif INT32 nLogVerbosity = pInjData->nLogVerbosity; // If we are running from an APC and the process is not yet initialized, retry // by re-queueing the APC and exiting. Reference: // https://x.com/sixtyvividtails/status/1910374252307534071 if (runningFromAPC && peb->ProcessInitializing) { DWORD apcRunCount = (DWORD)apcArgument2 + 1; if (pOutputDebugStringA && nLogVerbosity >= 2) { const char c = (char)apcRunCount - 1 + 'A'; char szApcRetryMessage[] = {'[', 'W', 'H', ']', ' ', 'A', 'P', 'C', ' ', 'R', 'E', ' ', c, '\n', '\0'}; pOutputDebugStringA(szApcRetryMessage); } enum class ApcRequeueError : char { kNone = 0, kNoImports = '1', kNtQueueApcThread = '2', kNtAlertThread = '3', kCreateThread = '4', }; bool queued = false; ApcRequeueError error = ApcRequeueError::kNone; if (apcRunCount >= 10) { // Limit the amount of attempts do avoid an infinite loop. // https://x.com/m417z/status/1923449532920025390 // As a fallback, create as a new thread. if (pCreateThread && pCloseHandle) { HANDLE hThread = pCreateThread(nullptr, 0, (LPTHREAD_START_ROUTINE)pInjData->pThreadShellcodeAddress, (void*)((DWORD_PTR)pInjData | 1), 0, nullptr); if (hThread) { pCloseHandle(hThread); queued = true; } else { error = ApcRequeueError::kCreateThread; } } else { error = ApcRequeueError::kNoImports; } } else if (pNtQueueApcThread && pNtAlertThread) { HANDLE hCurrentThread = (HANDLE)(LONG_PTR)-2; if (SUCCEEDED(pNtQueueApcThread(hCurrentThread, pInjData->pAPCShellcodeAddress, (void*)pInjData, (PVOID)(DWORD_PTR)apcRunCount, nullptr))) { queued = true; if (FAILED(pNtAlertThread(hCurrentThread))) { error = ApcRequeueError::kNtAlertThread; } } else { error = ApcRequeueError::kNtQueueApcThread; } } else { error = ApcRequeueError::kNoImports; } if (error != ApcRequeueError::kNone && pOutputDebugStringA && nLogVerbosity >= 1) { const char c = (char)error; char szApcErrorMessage[] = {'[', 'W', 'H', ']', ' ', 'A', 'P', 'C', ' ', 'E', 'R', 'R', c, '\n', '\0'}; pOutputDebugStringA(szApcErrorMessage); } return queued ? nullptr : pVirtualFree; } if (!foundAll) { // If possible, at least log the error. if (pOutputDebugStringA && nLogVerbosity >= 1) { char szExportResolutionErrorMessage[] = {'[', 'W', 'H', ']', ' ', 'E', 'X', 'P', '\n', '\0'}; pOutputDebugStringA(szExportResolutionErrorMessage); } // If we are running from an APC-created thread and the process is not yet initialized, // don't free the shellcode as it might be still executing in the APC. return (threadCreatedFromAPC && peb->ProcessInitializing) ? nullptr : pVirtualFree; } HMODULE hModule; const char szInjectInit[] = {'I', 'n', 'j', 'e', 'c', 't', 'I', 'n', 'i', 't', '\0'}; void* pInjectInit; BOOL bInitAttempted = FALSE; BOOL bInitSucceeded = FALSE; DWORD dwLastErrorValue = 0; DWORD dwOldMode; // Prevent the system from displaying the critical-error-handler message box. // A message box like this was appearing while trying to load a dll in a // process with the ProcessSignaturePolicy mitigation, and it looked like this: // https://stackoverflow.com/q/38367847 pSetThreadErrorMode(SEM_FAILCRITICALERRORS, &dwOldMode); if (nLogVerbosity >= 2) { char szLoadLibraryMessage[] = {'[', 'W', 'H', ']', ' ', 'L', 'L', '\n', '\0'}; pOutputDebugStringA(szLoadLibraryMessage); } hModule = pLoadLibraryW(pInjData->szDllName); if (hModule) { if (nLogVerbosity >= 2) { char szGetProcAddressMessage[] = {'[', 'W', 'H', ']', ' ', 'G', 'P', 'A', '\n', '\0'}; pOutputDebugStringA(szGetProcAddressMessage); } pInjectInit = pGetProcAddress(hModule, szInjectInit); if (pInjectInit) { if (nLogVerbosity >= 2) { char szInjectInitMessage[] = {'[', 'W', 'H', ']', ' ', 'I', 'I', '\n', '\0'}; pOutputDebugStringA(szInjectInitMessage); } bInitAttempted = TRUE; bInitSucceeded = ((BOOL(*)(const DllInject::LOAD_LIBRARY_REMOTE_DATA*))pInjectInit)(pInjData); if (nLogVerbosity >= 2) { char szInjectInitResultMessage[] = { '[', 'W', 'H', ']', ' ', 'I', 'I', ':', ' ', bInitSucceeded ? '1' : '0', '\n', '\0'}; pOutputDebugStringA(szInjectInitResultMessage); } } else { dwLastErrorValue = pGetLastError(); } pFreeLibrary(hModule); } else { dwLastErrorValue = pGetLastError(); } if (!bInitSucceeded) { if (pInjData->hSessionMutex) { pCloseHandle(pInjData->hSessionMutex); } pCloseHandle(pInjData->hSessionManagerProcess); if (!bInitAttempted && nLogVerbosity >= 1) { char szLastErrorMessage[] = {'[', 'W', 'H', ']', ' ', 'E', 'R', 'R', ':', ' ', '1', '1', '1', '1', '1', '1', '1', '1', '\n', '\0'}; char* pHex = szLastErrorMessage + sizeof(szLastErrorMessage) - 2; for (int i = 0; i < 8; i++) { int digit = dwLastErrorValue & 0x0F; char letter; if (digit < 0x0A) { letter = digit + '0'; } else { letter = digit - 0x0A + 'A'; } pHex--; *pHex = letter; dwLastErrorValue >>= 4; } pOutputDebugStringA(szLastErrorMessage); } } pSetThreadErrorMode(dwOldMode, nullptr); // If we are running from an APC-created thread and the process is not yet initialized, // don't free the shellcode as it might be still executing in the APC. return (threadCreatedFromAPC && peb->ProcessInitializing) ? nullptr : pVirtualFree; } // Helpers for creating PRE_ARM64SHELLCODE_VIRTUAL_FREE. #if 0 using VirtualFree_t = decltype(&VirtualFree); __declspec(dllexport) __declspec(noinline) VirtualFree_t GetVirtualFree(); __declspec(dllexport) __declspec(noinline) void func1() { VirtualFree_t pVirtualFree = GetVirtualFree(); if (pVirtualFree) { pVirtualFree(func1, 0, MEM_RELEASE); } } __declspec(dllexport) __declspec(noinline) VirtualFree_t GetVirtualFree() { return VirtualFree; } #endif int CALLBACK wWinMain(_In_ HINSTANCE hInstance, _In_opt_ HINSTANCE hPrevInstance, _In_ LPWSTR lpCmdLine, _In_ int nCmdShow) { DllInject::LOAD_LIBRARY_REMOTE_DATA injData{}; InjectShellcode(&injData, 0, 0); return 0; } ================================================ FILE: src/windhawk/engine/libraries/MinHook/include/MinHook.h ================================================ /* * MinHook - The Minimalistic API Hooking Library for x64/x86 * Copyright (C) 2009-2017 Tsuda Kageyu. * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #pragma once #if !(defined _M_IX86) && !(defined _M_X64) && !(defined __i386__) && !(defined __x86_64__) #error MinHook supports only x86 and x64 systems. #endif #include // MinHook Error Codes. typedef enum MH_STATUS { // Unknown error. Should not be returned. MH_UNKNOWN = -1, // Successful. MH_OK = 0, // MinHook is already initialized. MH_ERROR_ALREADY_INITIALIZED, // MinHook is not initialized yet, or already uninitialized. MH_ERROR_NOT_INITIALIZED, // The hook for the specified target function is already created. MH_ERROR_ALREADY_CREATED, // The hook for the specified target function is not created yet. MH_ERROR_NOT_CREATED, // The hook for the specified target function is already enabled. MH_ERROR_ENABLED, // The hook for the specified target function is not enabled yet, or already // disabled. MH_ERROR_DISABLED, // The specified pointer is invalid. It points the address of non-allocated // and/or non-executable region. MH_ERROR_NOT_EXECUTABLE, // The specified target function cannot be hooked. MH_ERROR_UNSUPPORTED_FUNCTION, // Failed to allocate memory. MH_ERROR_MEMORY_ALLOC, // Failed to change the memory protection. MH_ERROR_MEMORY_PROTECT, // The specified module is not loaded. MH_ERROR_MODULE_NOT_FOUND, // The specified function is not found. MH_ERROR_FUNCTION_NOT_FOUND, // Failed to create, or to wait for the main mutex. MH_ERROR_MUTEX_FAILURE } MH_STATUS; // The method of suspending and resuming threads. // // It's possible to add an additional method using PssCaptureSnapshot. // Pros: Documented, fast. // Cons: Available from Windows 8.1, less reliable. typedef enum MH_THREAD_FREEZE_METHOD { // The original MinHook method, using CreateToolhelp32Snapshot. Documented // and supported on all Windows versions, but very slow and less reliable. MH_FREEZE_METHOD_ORIGINAL = 0, // A much faster and more reliable, but undocumented method, using // NtGetNextThread. Supported since Windows Vista, on older versions falls // back to MH_ORIGINAL. MH_FREEZE_METHOD_FAST_UNDOCUMENTED, // Threads are not suspended and instruction pointer registers are not // adjusted. Don't use this method unless you understand the implications // and know that it's safe. MH_FREEZE_METHOD_NONE_UNSAFE } MH_THREAD_FREEZE_METHOD; // Can be passed as a parameter to MH_EnableHook, MH_DisableHook, // MH_QueueEnableHook or MH_QueueDisableHook. #define MH_ALL_HOOKS NULL #define MH_ALL_IDENTS 0 #define MH_DEFAULT_IDENT 1 #ifdef __cplusplus extern "C" { #endif // Initialize the MinHook library. You must call this function EXACTLY ONCE // at the beginning of your program. MH_STATUS WINAPI MH_Initialize(VOID); // Uninitialize the MinHook library. You must call this function EXACTLY // ONCE at the end of your program. MH_STATUS WINAPI MH_Uninitialize(VOID); // Set the method of suspending and resuming threads. MH_STATUS WINAPI MH_SetThreadFreezeMethod(MH_THREAD_FREEZE_METHOD method); // Creates a hook for the specified target function, in disabled state. // Parameters: // hookIdent [in] A hook identifier, can be set to different values for // different hooks to hook the same function more than // once. Default value: MH_DEFAULT_IDENT. // pTarget [in] A pointer to the target function, which will be // overridden by the detour function. // pDetour [in] A pointer to the detour function, which will override // the target function. // ppOriginal [out] A pointer to the trampoline function, which will be // used to call the original target function. // This parameter can be NULL. MH_STATUS WINAPI MH_CreateHook(LPVOID pTarget, LPVOID pDetour, LPVOID *ppOriginal); MH_STATUS WINAPI MH_CreateHookEx(ULONG_PTR hookIdent, LPVOID pTarget, LPVOID pDetour, LPVOID *ppOriginal); // Creates a hook for the specified API function, in disabled state. // Parameters: // pszModule [in] A pointer to the loaded module name which contains the // target function. // pszProcName [in] A pointer to the target function name, which will be // overridden by the detour function. // pDetour [in] A pointer to the detour function, which will override // the target function. // ppOriginal [out] A pointer to the trampoline function, which will be // used to call the original target function. // This parameter can be NULL. MH_STATUS WINAPI MH_CreateHookApi( LPCWSTR pszModule, LPCSTR pszProcName, LPVOID pDetour, LPVOID *ppOriginal); // Creates a hook for the specified API function, in disabled state. // Parameters: // pszModule [in] A pointer to the loaded module name which contains the // target function. // pszProcName [in] A pointer to the target function name, which will be // overridden by the detour function. // pDetour [in] A pointer to the detour function, which will override // the target function. // ppOriginal [out] A pointer to the trampoline function, which will be // used to call the original target function. // This parameter can be NULL. // ppTarget [out] A pointer to the target function, which will be used // with other functions. // This parameter can be NULL. MH_STATUS WINAPI MH_CreateHookApiEx( LPCWSTR pszModule, LPCSTR pszProcName, LPVOID pDetour, LPVOID *ppOriginal, LPVOID *ppTarget); // Removes an already created hook. // Parameters: // hookIdent [in] A hook identifier, can be set to different values for // different hooks to hook the same function more than // once. Default value: MH_DEFAULT_IDENT. // pTarget [in] A pointer to the target function. // If this parameter is MH_ALL_HOOKS, all created hooks are // removed in one go. MH_STATUS WINAPI MH_RemoveHook(LPVOID pTarget); MH_STATUS WINAPI MH_RemoveHookEx(ULONG_PTR hookIdent, LPVOID pTarget); // Removes all disabled hooks. // Parameters: // hookIdent [in] A hook identifier, can be set to different values for // different hooks to hook the same function more than // once. Default value: MH_DEFAULT_IDENT. MH_STATUS WINAPI MH_RemoveDisabledHooks(); MH_STATUS WINAPI MH_RemoveDisabledHooksEx(ULONG_PTR hookIdent); // Enables an already created hook. // Parameters: // hookIdent [in] A hook identifier, can be set to different values for // different hooks to hook the same function more than // once. Default value: MH_DEFAULT_IDENT. // pTarget [in] A pointer to the target function. // If this parameter is MH_ALL_HOOKS, all created hooks are // enabled in one go. MH_STATUS WINAPI MH_EnableHook(LPVOID pTarget); MH_STATUS WINAPI MH_EnableHookEx(ULONG_PTR hookIdent, LPVOID pTarget); // Disables an already created hook. // Parameters: // hookIdent [in] A hook identifier, can be set to different values for // different hooks to hook the same function more than // once. Default value: MH_DEFAULT_IDENT. // pTarget [in] A pointer to the target function. // If this parameter is MH_ALL_HOOKS, all created hooks are // disabled in one go. MH_STATUS WINAPI MH_DisableHook(LPVOID pTarget); MH_STATUS WINAPI MH_DisableHookEx(ULONG_PTR hookIdent, LPVOID pTarget); // Queues to enable an already created hook. // Parameters: // hookIdent [in] A hook identifier, can be set to different values for // different hooks to hook the same function more than // once. Default value: MH_DEFAULT_IDENT. // pTarget [in] A pointer to the target function. // If this parameter is MH_ALL_HOOKS, all created hooks are // queued to be enabled. MH_STATUS WINAPI MH_QueueEnableHook(LPVOID pTarget); MH_STATUS WINAPI MH_QueueEnableHookEx(ULONG_PTR hookIdent, LPVOID pTarget); // Queues to disable an already created hook. // Parameters: // hookIdent [in] A hook identifier, can be set to different values for // different hooks to hook the same function more than // once. Default value: MH_DEFAULT_IDENT. // pTarget [in] A pointer to the target function. // If this parameter is MH_ALL_HOOKS, all created hooks are // queued to be disabled. MH_STATUS WINAPI MH_QueueDisableHook(LPVOID pTarget); MH_STATUS WINAPI MH_QueueDisableHookEx(ULONG_PTR hookIdent, LPVOID pTarget); // Applies all queued changes in one go. // hookIdent [in] A hook identifier, can be set to different values for // different hooks to hook the same function more than // once. Default value: MH_DEFAULT_IDENT. MH_STATUS WINAPI MH_ApplyQueued(VOID); MH_STATUS WINAPI MH_ApplyQueuedEx(ULONG_PTR hookIdent); // Translates the MH_STATUS to its name as a string. const char * WINAPI MH_StatusToString(MH_STATUS status); #ifdef __cplusplus } #endif ================================================ FILE: src/windhawk/engine/libraries/MinHook/src/buffer.c ================================================ /* * MinHook - The Minimalistic API Hooking Library for x64/x86 * Copyright (C) 2009-2017 Tsuda Kageyu. * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include #include "buffer.h" // Size of each memory block. (= page size of VirtualAlloc) #define MEMORY_BLOCK_SIZE 0x1000 // Max range for seeking a memory block. (= 1024MB) #define MAX_MEMORY_RANGE 0x40000000 // Memory protection flags to check the executable address. #define PAGE_EXECUTE_FLAGS \ (PAGE_EXECUTE | PAGE_EXECUTE_READ | PAGE_EXECUTE_READWRITE | PAGE_EXECUTE_WRITECOPY) // Memory slot. typedef struct _MEMORY_SLOT { union { struct _MEMORY_SLOT *pNext; UINT8 buffer[MEMORY_SLOT_SIZE]; }; } MEMORY_SLOT, *PMEMORY_SLOT; // Memory block info. Placed at the head of each block. typedef struct _MEMORY_BLOCK { struct _MEMORY_BLOCK *pNext; PMEMORY_SLOT pFree; // First element of the free slot list. UINT usedCount; } MEMORY_BLOCK, *PMEMORY_BLOCK; //------------------------------------------------------------------------- // Global Variables: //------------------------------------------------------------------------- // First element of the memory block list. static PMEMORY_BLOCK g_pMemoryBlocks; //------------------------------------------------------------------------- VOID InitializeBuffer(VOID) { // Nothing to do for now. } //------------------------------------------------------------------------- VOID UninitializeBuffer(VOID) { PMEMORY_BLOCK pBlock = g_pMemoryBlocks; g_pMemoryBlocks = NULL; while (pBlock) { PMEMORY_BLOCK pNext = pBlock->pNext; VirtualFree(pBlock, 0, MEM_RELEASE); pBlock = pNext; } } //------------------------------------------------------------------------- #if defined(_M_X64) || defined(__x86_64__) static LPVOID FindPrevFreeRegion(LPVOID pAddress, LPVOID pMinAddr, DWORD dwAllocationGranularity) { ULONG_PTR tryAddr = (ULONG_PTR)pAddress; // Round down to the allocation granularity. tryAddr -= tryAddr % dwAllocationGranularity; // Start from the previous allocation granularity multiply. tryAddr -= dwAllocationGranularity; while (tryAddr >= (ULONG_PTR)pMinAddr) { MEMORY_BASIC_INFORMATION mbi; if (VirtualQuery((LPVOID)tryAddr, &mbi, sizeof(mbi)) == 0) break; if (mbi.State == MEM_FREE) return (LPVOID)tryAddr; if ((ULONG_PTR)mbi.AllocationBase < dwAllocationGranularity) break; tryAddr = (ULONG_PTR)mbi.AllocationBase - dwAllocationGranularity; } return NULL; } #endif //------------------------------------------------------------------------- #if defined(_M_X64) || defined(__x86_64__) static LPVOID FindNextFreeRegion(LPVOID pAddress, LPVOID pMaxAddr, DWORD dwAllocationGranularity) { ULONG_PTR tryAddr = (ULONG_PTR)pAddress; // Round down to the allocation granularity. tryAddr -= tryAddr % dwAllocationGranularity; // Start from the next allocation granularity multiply. tryAddr += dwAllocationGranularity; while (tryAddr <= (ULONG_PTR)pMaxAddr) { MEMORY_BASIC_INFORMATION mbi; if (VirtualQuery((LPVOID)tryAddr, &mbi, sizeof(mbi)) == 0) break; if (mbi.State == MEM_FREE) return (LPVOID)tryAddr; tryAddr = (ULONG_PTR)mbi.BaseAddress + mbi.RegionSize; // Round up to the next allocation granularity. tryAddr += dwAllocationGranularity - 1; tryAddr -= tryAddr % dwAllocationGranularity; } return NULL; } #endif //------------------------------------------------------------------------- static PMEMORY_BLOCK GetMemoryBlock(LPVOID pOrigin) { PMEMORY_BLOCK pBlock; #if defined(_M_X64) || defined(__x86_64__) ULONG_PTR minAddr; ULONG_PTR maxAddr; SYSTEM_INFO si; GetSystemInfo(&si); minAddr = (ULONG_PTR)si.lpMinimumApplicationAddress; maxAddr = (ULONG_PTR)si.lpMaximumApplicationAddress; // pOrigin ± 512MB if ((ULONG_PTR)pOrigin > MAX_MEMORY_RANGE && minAddr < (ULONG_PTR)pOrigin - MAX_MEMORY_RANGE) minAddr = (ULONG_PTR)pOrigin - MAX_MEMORY_RANGE; if (maxAddr > (ULONG_PTR)pOrigin + MAX_MEMORY_RANGE) maxAddr = (ULONG_PTR)pOrigin + MAX_MEMORY_RANGE; // Make room for MEMORY_BLOCK_SIZE bytes. maxAddr -= MEMORY_BLOCK_SIZE - 1; #endif // Look the registered blocks for a reachable one. for (pBlock = g_pMemoryBlocks; pBlock != NULL; pBlock = pBlock->pNext) { #if defined(_M_X64) || defined(__x86_64__) // Ignore the blocks too far. if ((ULONG_PTR)pBlock < minAddr || (ULONG_PTR)pBlock >= maxAddr) continue; #endif // The block has at least one unused slot. if (pBlock->pFree != NULL) return pBlock; } #if defined(_M_X64) || defined(__x86_64__) // Alloc a new block above if not found. { LPVOID pAlloc = pOrigin; while ((ULONG_PTR)pAlloc >= minAddr) { pAlloc = FindPrevFreeRegion(pAlloc, (LPVOID)minAddr, si.dwAllocationGranularity); if (pAlloc == NULL) break; pBlock = (PMEMORY_BLOCK)VirtualAlloc( pAlloc, MEMORY_BLOCK_SIZE, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE); if (pBlock != NULL) break; } } // Alloc a new block below if not found. if (pBlock == NULL) { LPVOID pAlloc = pOrigin; while ((ULONG_PTR)pAlloc <= maxAddr) { pAlloc = FindNextFreeRegion(pAlloc, (LPVOID)maxAddr, si.dwAllocationGranularity); if (pAlloc == NULL) break; pBlock = (PMEMORY_BLOCK)VirtualAlloc( pAlloc, MEMORY_BLOCK_SIZE, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE); if (pBlock != NULL) break; } } #else // In x86 mode, a memory block can be placed anywhere. pBlock = (PMEMORY_BLOCK)VirtualAlloc( NULL, MEMORY_BLOCK_SIZE, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE); #endif if (pBlock != NULL) { // Build a linked list of all the slots. PMEMORY_SLOT pSlot = (PMEMORY_SLOT)pBlock + 1; pBlock->pFree = NULL; pBlock->usedCount = 0; do { pSlot->pNext = pBlock->pFree; pBlock->pFree = pSlot; pSlot++; } while ((ULONG_PTR)pSlot - (ULONG_PTR)pBlock <= MEMORY_BLOCK_SIZE - MEMORY_SLOT_SIZE); pBlock->pNext = g_pMemoryBlocks; g_pMemoryBlocks = pBlock; } return pBlock; } //------------------------------------------------------------------------- LPVOID AllocateBuffer(LPVOID pOrigin) { PMEMORY_SLOT pSlot; PMEMORY_BLOCK pBlock = GetMemoryBlock(pOrigin); if (pBlock == NULL) return NULL; // Remove an unused slot from the list. pSlot = pBlock->pFree; pBlock->pFree = pSlot->pNext; pBlock->usedCount++; #ifdef _DEBUG // Fill the slot with INT3 for debugging. memset(pSlot, 0xCC, sizeof(MEMORY_SLOT)); #endif return pSlot; } //------------------------------------------------------------------------- VOID FreeBuffer(LPVOID pBuffer) { PMEMORY_BLOCK pBlock = g_pMemoryBlocks; PMEMORY_BLOCK pPrev = NULL; ULONG_PTR pTargetBlock = ((ULONG_PTR)pBuffer / MEMORY_BLOCK_SIZE) * MEMORY_BLOCK_SIZE; while (pBlock != NULL) { if ((ULONG_PTR)pBlock == pTargetBlock) { PMEMORY_SLOT pSlot = (PMEMORY_SLOT)pBuffer; #ifdef _DEBUG // Clear the released slot for debugging. memset(pSlot, 0x00, sizeof(MEMORY_SLOT)); #endif // Restore the released slot to the list. pSlot->pNext = pBlock->pFree; pBlock->pFree = pSlot; pBlock->usedCount--; // Free if unused. if (pBlock->usedCount == 0) { if (pPrev) pPrev->pNext = pBlock->pNext; else g_pMemoryBlocks = pBlock->pNext; VirtualFree(pBlock, 0, MEM_RELEASE); } break; } pPrev = pBlock; pBlock = pBlock->pNext; } } //------------------------------------------------------------------------- BOOL IsExecutableAddress(LPVOID pAddress) { MEMORY_BASIC_INFORMATION mi; VirtualQuery(pAddress, &mi, sizeof(mi)); return (mi.State == MEM_COMMIT && (mi.Protect & PAGE_EXECUTE_FLAGS)); } ================================================ FILE: src/windhawk/engine/libraries/MinHook/src/buffer.h ================================================ /* * MinHook - The Minimalistic API Hooking Library for x64/x86 * Copyright (C) 2009-2017 Tsuda Kageyu. * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #pragma once // Size of each memory slot. #if defined(_M_X64) || defined(__x86_64__) #define MEMORY_SLOT_SIZE 64 #else #define MEMORY_SLOT_SIZE 64 #endif VOID InitializeBuffer(VOID); VOID UninitializeBuffer(VOID); LPVOID AllocateBuffer(LPVOID pOrigin); VOID FreeBuffer(LPVOID pBuffer); BOOL IsExecutableAddress(LPVOID pAddress); ================================================ FILE: src/windhawk/engine/libraries/MinHook/src/hde/hde32.c ================================================ /* * Hacker Disassembler Engine 32 C * Copyright (c) 2008-2009, Vyacheslav Patkov. * All rights reserved. * */ #if defined(_M_IX86) || defined(__i386__) #include #include "hde32.h" #include "table32.h" unsigned int hde32_disasm(const void *code, hde32s *hs) { uint8_t x, c, *p = (uint8_t *)code, cflags, opcode, pref = 0; uint8_t *ht = hde32_table, m_mod, m_reg, m_rm, disp_size = 0; memset(hs, 0, sizeof(hde32s)); for (x = 16; x; x--) switch (c = *p++) { case 0xf3: hs->p_rep = c; pref |= PRE_F3; break; case 0xf2: hs->p_rep = c; pref |= PRE_F2; break; case 0xf0: hs->p_lock = c; pref |= PRE_LOCK; break; case 0x26: case 0x2e: case 0x36: case 0x3e: case 0x64: case 0x65: hs->p_seg = c; pref |= PRE_SEG; break; case 0x66: hs->p_66 = c; pref |= PRE_66; break; case 0x67: hs->p_67 = c; pref |= PRE_67; break; default: goto pref_done; } pref_done: hs->flags = (uint32_t)pref << 23; if (!pref) pref |= PRE_NONE; if ((hs->opcode = c) == 0x0f) { hs->opcode2 = c = *p++; ht += DELTA_OPCODES; } else if (c >= 0xa0 && c <= 0xa3) { if (pref & PRE_67) pref |= PRE_66; else pref &= ~PRE_66; } opcode = c; cflags = ht[ht[opcode / 4] + (opcode % 4)]; if (cflags == C_ERROR) { hs->flags |= F_ERROR | F_ERROR_OPCODE; cflags = 0; if ((opcode & -3) == 0x24) cflags++; } x = 0; if (cflags & C_GROUP) { uint16_t t; t = *(uint16_t *)(ht + (cflags & 0x7f)); cflags = (uint8_t)t; x = (uint8_t)(t >> 8); } if (hs->opcode2) { ht = hde32_table + DELTA_PREFIXES; if (ht[ht[opcode / 4] + (opcode % 4)] & pref) hs->flags |= F_ERROR | F_ERROR_OPCODE; } if (cflags & C_MODRM) { hs->flags |= F_MODRM; hs->modrm = c = *p++; hs->modrm_mod = m_mod = c >> 6; hs->modrm_rm = m_rm = c & 7; hs->modrm_reg = m_reg = (c & 0x3f) >> 3; if (x && ((x << m_reg) & 0x80)) hs->flags |= F_ERROR | F_ERROR_OPCODE; if (!hs->opcode2 && opcode >= 0xd9 && opcode <= 0xdf) { uint8_t t = opcode - 0xd9; if (m_mod == 3) { ht = hde32_table + DELTA_FPU_MODRM + t*8; t = ht[m_reg] << m_rm; } else { ht = hde32_table + DELTA_FPU_REG; t = ht[t] << m_reg; } if (t & 0x80) hs->flags |= F_ERROR | F_ERROR_OPCODE; } if (pref & PRE_LOCK) { if (m_mod == 3) { hs->flags |= F_ERROR | F_ERROR_LOCK; } else { uint8_t *table_end, op = opcode; if (hs->opcode2) { ht = hde32_table + DELTA_OP2_LOCK_OK; table_end = ht + DELTA_OP_ONLY_MEM - DELTA_OP2_LOCK_OK; } else { ht = hde32_table + DELTA_OP_LOCK_OK; table_end = ht + DELTA_OP2_LOCK_OK - DELTA_OP_LOCK_OK; op &= -2; } for (; ht != table_end; ht++) if (*ht++ == op) { if (!((*ht << m_reg) & 0x80)) goto no_lock_error; else break; } hs->flags |= F_ERROR | F_ERROR_LOCK; no_lock_error: ; } } if (hs->opcode2) { switch (opcode) { case 0x20: case 0x22: m_mod = 3; if (m_reg > 4 || m_reg == 1) goto error_operand; else goto no_error_operand; case 0x21: case 0x23: m_mod = 3; if (m_reg == 4 || m_reg == 5) goto error_operand; else goto no_error_operand; } } else { switch (opcode) { case 0x8c: if (m_reg > 5) goto error_operand; else goto no_error_operand; case 0x8e: if (m_reg == 1 || m_reg > 5) goto error_operand; else goto no_error_operand; } } if (m_mod == 3) { uint8_t *table_end; if (hs->opcode2) { ht = hde32_table + DELTA_OP2_ONLY_MEM; table_end = ht + sizeof(hde32_table) - DELTA_OP2_ONLY_MEM; } else { ht = hde32_table + DELTA_OP_ONLY_MEM; table_end = ht + DELTA_OP2_ONLY_MEM - DELTA_OP_ONLY_MEM; } for (; ht != table_end; ht += 2) if (*ht++ == opcode) { if ((*ht++ & pref) && !((*ht << m_reg) & 0x80)) goto error_operand; else break; } goto no_error_operand; } else if (hs->opcode2) { switch (opcode) { case 0x50: case 0xd7: case 0xf7: if (pref & (PRE_NONE | PRE_66)) goto error_operand; break; case 0xd6: if (pref & (PRE_F2 | PRE_F3)) goto error_operand; break; case 0xc5: goto error_operand; } goto no_error_operand; } else goto no_error_operand; error_operand: hs->flags |= F_ERROR | F_ERROR_OPERAND; no_error_operand: c = *p++; if (m_reg <= 1) { if (opcode == 0xf6) cflags |= C_IMM8; else if (opcode == 0xf7) cflags |= C_IMM_P66; } switch (m_mod) { case 0: if (pref & PRE_67) { if (m_rm == 6) disp_size = 2; } else if (m_rm == 5) disp_size = 4; break; case 1: disp_size = 1; break; case 2: disp_size = 2; if (!(pref & PRE_67)) disp_size <<= 1; break; } if (m_mod != 3 && m_rm == 4 && !(pref & PRE_67)) { hs->flags |= F_SIB; p++; hs->sib = c; hs->sib_scale = c >> 6; hs->sib_index = (c & 0x3f) >> 3; if ((hs->sib_base = c & 7) == 5 && !(m_mod & 1)) disp_size = 4; } p--; switch (disp_size) { case 1: hs->flags |= F_DISP8; hs->disp.disp8 = *p; break; case 2: hs->flags |= F_DISP16; hs->disp.disp16 = *(uint16_t *)p; break; case 4: hs->flags |= F_DISP32; hs->disp.disp32 = *(uint32_t *)p; break; } p += disp_size; } else if (pref & PRE_LOCK) hs->flags |= F_ERROR | F_ERROR_LOCK; if (cflags & C_IMM_P66) { if (cflags & C_REL32) { if (pref & PRE_66) { hs->flags |= F_IMM16 | F_RELATIVE; hs->imm.imm16 = *(uint16_t *)p; p += 2; goto disasm_done; } goto rel32_ok; } if (pref & PRE_66) { hs->flags |= F_IMM16; hs->imm.imm16 = *(uint16_t *)p; p += 2; } else { hs->flags |= F_IMM32; hs->imm.imm32 = *(uint32_t *)p; p += 4; } } if (cflags & C_IMM16) { if (hs->flags & F_IMM32) { hs->flags |= F_IMM16; hs->disp.disp16 = *(uint16_t *)p; } else if (hs->flags & F_IMM16) { hs->flags |= F_2IMM16; hs->disp.disp16 = *(uint16_t *)p; } else { hs->flags |= F_IMM16; hs->imm.imm16 = *(uint16_t *)p; } p += 2; } if (cflags & C_IMM8) { hs->flags |= F_IMM8; hs->imm.imm8 = *p++; } if (cflags & C_REL32) { rel32_ok: hs->flags |= F_IMM32 | F_RELATIVE; hs->imm.imm32 = *(uint32_t *)p; p += 4; } else if (cflags & C_REL8) { hs->flags |= F_IMM8 | F_RELATIVE; hs->imm.imm8 = *p++; } disasm_done: if ((hs->len = (uint8_t)(p-(uint8_t *)code)) > 15) { hs->flags |= F_ERROR | F_ERROR_LENGTH; hs->len = 15; } return (unsigned int)hs->len; } #endif // defined(_M_IX86) || defined(__i386__) ================================================ FILE: src/windhawk/engine/libraries/MinHook/src/hde/hde32.h ================================================ /* * Hacker Disassembler Engine 32 * Copyright (c) 2006-2009, Vyacheslav Patkov. * All rights reserved. * * hde32.h: C/C++ header file * */ #ifndef _HDE32_H_ #define _HDE32_H_ /* stdint.h - C99 standard header * http://en.wikipedia.org/wiki/stdint.h * * if your compiler doesn't contain "stdint.h" header (for * example, Microsoft Visual C++), you can download file: * http://www.azillionmonkeys.com/qed/pstdint.h * and change next line to: * #include "pstdint.h" */ #include "pstdint.h" #define F_MODRM 0x00000001 #define F_SIB 0x00000002 #define F_IMM8 0x00000004 #define F_IMM16 0x00000008 #define F_IMM32 0x00000010 #define F_DISP8 0x00000020 #define F_DISP16 0x00000040 #define F_DISP32 0x00000080 #define F_RELATIVE 0x00000100 #define F_2IMM16 0x00000800 #define F_ERROR 0x00001000 #define F_ERROR_OPCODE 0x00002000 #define F_ERROR_LENGTH 0x00004000 #define F_ERROR_LOCK 0x00008000 #define F_ERROR_OPERAND 0x00010000 #define F_PREFIX_REPNZ 0x01000000 #define F_PREFIX_REPX 0x02000000 #define F_PREFIX_REP 0x03000000 #define F_PREFIX_66 0x04000000 #define F_PREFIX_67 0x08000000 #define F_PREFIX_LOCK 0x10000000 #define F_PREFIX_SEG 0x20000000 #define F_PREFIX_ANY 0x3f000000 #define PREFIX_SEGMENT_CS 0x2e #define PREFIX_SEGMENT_SS 0x36 #define PREFIX_SEGMENT_DS 0x3e #define PREFIX_SEGMENT_ES 0x26 #define PREFIX_SEGMENT_FS 0x64 #define PREFIX_SEGMENT_GS 0x65 #define PREFIX_LOCK 0xf0 #define PREFIX_REPNZ 0xf2 #define PREFIX_REPX 0xf3 #define PREFIX_OPERAND_SIZE 0x66 #define PREFIX_ADDRESS_SIZE 0x67 #pragma pack(push,1) typedef struct { uint8_t len; uint8_t p_rep; uint8_t p_lock; uint8_t p_seg; uint8_t p_66; uint8_t p_67; uint8_t opcode; uint8_t opcode2; uint8_t modrm; uint8_t modrm_mod; uint8_t modrm_reg; uint8_t modrm_rm; uint8_t sib; uint8_t sib_scale; uint8_t sib_index; uint8_t sib_base; union { uint8_t imm8; uint16_t imm16; uint32_t imm32; } imm; union { uint8_t disp8; uint16_t disp16; uint32_t disp32; } disp; uint32_t flags; } hde32s; #pragma pack(pop) #ifdef __cplusplus extern "C" { #endif /* __cdecl */ unsigned int hde32_disasm(const void *code, hde32s *hs); #ifdef __cplusplus } #endif #endif /* _HDE32_H_ */ ================================================ FILE: src/windhawk/engine/libraries/MinHook/src/hde/hde64.c ================================================ /* * Hacker Disassembler Engine 64 C * Copyright (c) 2008-2009, Vyacheslav Patkov. * All rights reserved. * */ #if defined(_M_X64) || defined(__x86_64__) #include #include "hde64.h" #include "table64.h" unsigned int hde64_disasm(const void *code, hde64s *hs) { uint8_t x, c, *p = (uint8_t *)code, cflags, opcode, pref = 0; uint8_t *ht = hde64_table, m_mod, m_reg, m_rm, disp_size = 0; uint8_t op64 = 0; memset(hs, 0, sizeof(hde64s)); for (x = 16; x; x--) switch (c = *p++) { case 0xf3: hs->p_rep = c; pref |= PRE_F3; break; case 0xf2: hs->p_rep = c; pref |= PRE_F2; break; case 0xf0: hs->p_lock = c; pref |= PRE_LOCK; break; case 0x26: case 0x2e: case 0x36: case 0x3e: case 0x64: case 0x65: hs->p_seg = c; pref |= PRE_SEG; break; case 0x66: hs->p_66 = c; pref |= PRE_66; break; case 0x67: hs->p_67 = c; pref |= PRE_67; break; default: goto pref_done; } pref_done: hs->flags = (uint32_t)pref << 23; if (!pref) pref |= PRE_NONE; if ((c & 0xf0) == 0x40) { hs->flags |= F_PREFIX_REX; if ((hs->rex_w = (c & 0xf) >> 3) && (*p & 0xf8) == 0xb8) op64++; hs->rex_r = (c & 7) >> 2; hs->rex_x = (c & 3) >> 1; hs->rex_b = c & 1; if (((c = *p++) & 0xf0) == 0x40) { opcode = c; goto error_opcode; } } if ((hs->opcode = c) == 0x0f) { hs->opcode2 = c = *p++; ht += DELTA_OPCODES; } else if (c >= 0xa0 && c <= 0xa3) { op64++; if (pref & PRE_67) pref |= PRE_66; else pref &= ~PRE_66; } opcode = c; cflags = ht[ht[opcode / 4] + (opcode % 4)]; if (cflags == C_ERROR) { error_opcode: hs->flags |= F_ERROR | F_ERROR_OPCODE; cflags = 0; if ((opcode & -3) == 0x24) cflags++; } x = 0; if (cflags & C_GROUP) { uint16_t t; t = *(uint16_t *)(ht + (cflags & 0x7f)); cflags = (uint8_t)t; x = (uint8_t)(t >> 8); } if (hs->opcode2) { ht = hde64_table + DELTA_PREFIXES; if (ht[ht[opcode / 4] + (opcode % 4)] & pref) hs->flags |= F_ERROR | F_ERROR_OPCODE; } if (cflags & C_MODRM) { hs->flags |= F_MODRM; hs->modrm = c = *p++; hs->modrm_mod = m_mod = c >> 6; hs->modrm_rm = m_rm = c & 7; hs->modrm_reg = m_reg = (c & 0x3f) >> 3; if (x && ((x << m_reg) & 0x80)) hs->flags |= F_ERROR | F_ERROR_OPCODE; if (!hs->opcode2 && opcode >= 0xd9 && opcode <= 0xdf) { uint8_t t = opcode - 0xd9; if (m_mod == 3) { ht = hde64_table + DELTA_FPU_MODRM + t*8; t = ht[m_reg] << m_rm; } else { ht = hde64_table + DELTA_FPU_REG; t = ht[t] << m_reg; } if (t & 0x80) hs->flags |= F_ERROR | F_ERROR_OPCODE; } if (pref & PRE_LOCK) { if (m_mod == 3) { hs->flags |= F_ERROR | F_ERROR_LOCK; } else { uint8_t *table_end, op = opcode; if (hs->opcode2) { ht = hde64_table + DELTA_OP2_LOCK_OK; table_end = ht + DELTA_OP_ONLY_MEM - DELTA_OP2_LOCK_OK; } else { ht = hde64_table + DELTA_OP_LOCK_OK; table_end = ht + DELTA_OP2_LOCK_OK - DELTA_OP_LOCK_OK; op &= -2; } for (; ht != table_end; ht++) if (*ht++ == op) { if (!((*ht << m_reg) & 0x80)) goto no_lock_error; else break; } hs->flags |= F_ERROR | F_ERROR_LOCK; no_lock_error: ; } } if (hs->opcode2) { switch (opcode) { case 0x20: case 0x22: m_mod = 3; if (m_reg > 4 || m_reg == 1) goto error_operand; else goto no_error_operand; case 0x21: case 0x23: m_mod = 3; if (m_reg == 4 || m_reg == 5) goto error_operand; else goto no_error_operand; } } else { switch (opcode) { case 0x8c: if (m_reg > 5) goto error_operand; else goto no_error_operand; case 0x8e: if (m_reg == 1 || m_reg > 5) goto error_operand; else goto no_error_operand; } } if (m_mod == 3) { uint8_t *table_end; if (hs->opcode2) { ht = hde64_table + DELTA_OP2_ONLY_MEM; table_end = ht + sizeof(hde64_table) - DELTA_OP2_ONLY_MEM; } else { ht = hde64_table + DELTA_OP_ONLY_MEM; table_end = ht + DELTA_OP2_ONLY_MEM - DELTA_OP_ONLY_MEM; } for (; ht != table_end; ht += 2) if (*ht++ == opcode) { if ((*ht++ & pref) && !((*ht << m_reg) & 0x80)) goto error_operand; else break; } goto no_error_operand; } else if (hs->opcode2) { switch (opcode) { case 0x50: case 0xd7: case 0xf7: if (pref & (PRE_NONE | PRE_66)) goto error_operand; break; case 0xd6: if (pref & (PRE_F2 | PRE_F3)) goto error_operand; break; case 0xc5: goto error_operand; } goto no_error_operand; } else goto no_error_operand; error_operand: hs->flags |= F_ERROR | F_ERROR_OPERAND; no_error_operand: c = *p++; if (m_reg <= 1) { if (opcode == 0xf6) cflags |= C_IMM8; else if (opcode == 0xf7) cflags |= C_IMM_P66; } switch (m_mod) { case 0: if (pref & PRE_67) { if (m_rm == 6) disp_size = 2; } else if (m_rm == 5) disp_size = 4; break; case 1: disp_size = 1; break; case 2: disp_size = 2; if (!(pref & PRE_67)) disp_size <<= 1; break; } if (m_mod != 3 && m_rm == 4) { hs->flags |= F_SIB; p++; hs->sib = c; hs->sib_scale = c >> 6; hs->sib_index = (c & 0x3f) >> 3; if ((hs->sib_base = c & 7) == 5 && !(m_mod & 1)) disp_size = 4; } p--; switch (disp_size) { case 1: hs->flags |= F_DISP8; hs->disp.disp8 = *p; break; case 2: hs->flags |= F_DISP16; hs->disp.disp16 = *(uint16_t *)p; break; case 4: hs->flags |= F_DISP32; hs->disp.disp32 = *(uint32_t *)p; break; } p += disp_size; } else if (pref & PRE_LOCK) hs->flags |= F_ERROR | F_ERROR_LOCK; if (cflags & C_IMM_P66) { if (cflags & C_REL32) { if (pref & PRE_66) { hs->flags |= F_IMM16 | F_RELATIVE; hs->imm.imm16 = *(uint16_t *)p; p += 2; goto disasm_done; } goto rel32_ok; } if (op64) { hs->flags |= F_IMM64; hs->imm.imm64 = *(uint64_t *)p; p += 8; } else if (!(pref & PRE_66)) { hs->flags |= F_IMM32; hs->imm.imm32 = *(uint32_t *)p; p += 4; } else goto imm16_ok; } if (cflags & C_IMM16) { imm16_ok: hs->flags |= F_IMM16; hs->imm.imm16 = *(uint16_t *)p; p += 2; } if (cflags & C_IMM8) { hs->flags |= F_IMM8; hs->imm.imm8 = *p++; } if (cflags & C_REL32) { rel32_ok: hs->flags |= F_IMM32 | F_RELATIVE; hs->imm.imm32 = *(uint32_t *)p; p += 4; } else if (cflags & C_REL8) { hs->flags |= F_IMM8 | F_RELATIVE; hs->imm.imm8 = *p++; } disasm_done: if ((hs->len = (uint8_t)(p-(uint8_t *)code)) > 15) { hs->flags |= F_ERROR | F_ERROR_LENGTH; hs->len = 15; } return (unsigned int)hs->len; } #endif // defined(_M_X64) || defined(__x86_64__) ================================================ FILE: src/windhawk/engine/libraries/MinHook/src/hde/hde64.h ================================================ /* * Hacker Disassembler Engine 64 * Copyright (c) 2008-2009, Vyacheslav Patkov. * All rights reserved. * * hde64.h: C/C++ header file * */ #ifndef _HDE64_H_ #define _HDE64_H_ /* stdint.h - C99 standard header * http://en.wikipedia.org/wiki/stdint.h * * if your compiler doesn't contain "stdint.h" header (for * example, Microsoft Visual C++), you can download file: * http://www.azillionmonkeys.com/qed/pstdint.h * and change next line to: * #include "pstdint.h" */ #include "pstdint.h" #define F_MODRM 0x00000001 #define F_SIB 0x00000002 #define F_IMM8 0x00000004 #define F_IMM16 0x00000008 #define F_IMM32 0x00000010 #define F_IMM64 0x00000020 #define F_DISP8 0x00000040 #define F_DISP16 0x00000080 #define F_DISP32 0x00000100 #define F_RELATIVE 0x00000200 #define F_ERROR 0x00001000 #define F_ERROR_OPCODE 0x00002000 #define F_ERROR_LENGTH 0x00004000 #define F_ERROR_LOCK 0x00008000 #define F_ERROR_OPERAND 0x00010000 #define F_PREFIX_REPNZ 0x01000000 #define F_PREFIX_REPX 0x02000000 #define F_PREFIX_REP 0x03000000 #define F_PREFIX_66 0x04000000 #define F_PREFIX_67 0x08000000 #define F_PREFIX_LOCK 0x10000000 #define F_PREFIX_SEG 0x20000000 #define F_PREFIX_REX 0x40000000 #define F_PREFIX_ANY 0x7f000000 #define PREFIX_SEGMENT_CS 0x2e #define PREFIX_SEGMENT_SS 0x36 #define PREFIX_SEGMENT_DS 0x3e #define PREFIX_SEGMENT_ES 0x26 #define PREFIX_SEGMENT_FS 0x64 #define PREFIX_SEGMENT_GS 0x65 #define PREFIX_LOCK 0xf0 #define PREFIX_REPNZ 0xf2 #define PREFIX_REPX 0xf3 #define PREFIX_OPERAND_SIZE 0x66 #define PREFIX_ADDRESS_SIZE 0x67 #pragma pack(push,1) typedef struct { uint8_t len; uint8_t p_rep; uint8_t p_lock; uint8_t p_seg; uint8_t p_66; uint8_t p_67; uint8_t rex; uint8_t rex_w; uint8_t rex_r; uint8_t rex_x; uint8_t rex_b; uint8_t opcode; uint8_t opcode2; uint8_t modrm; uint8_t modrm_mod; uint8_t modrm_reg; uint8_t modrm_rm; uint8_t sib; uint8_t sib_scale; uint8_t sib_index; uint8_t sib_base; union { uint8_t imm8; uint16_t imm16; uint32_t imm32; uint64_t imm64; } imm; union { uint8_t disp8; uint16_t disp16; uint32_t disp32; } disp; uint32_t flags; } hde64s; #pragma pack(pop) #ifdef __cplusplus extern "C" { #endif /* __cdecl */ unsigned int hde64_disasm(const void *code, hde64s *hs); #ifdef __cplusplus } #endif #endif /* _HDE64_H_ */ ================================================ FILE: src/windhawk/engine/libraries/MinHook/src/hde/pstdint.h ================================================ /* * MinHook - The Minimalistic API Hooking Library for x64/x86 * Copyright (C) 2009-2017 Tsuda Kageyu. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR "AS IS" AND ANY EXPRESS OR * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #pragma once #include // Integer types for HDE. typedef INT8 int8_t; typedef INT16 int16_t; typedef INT32 int32_t; typedef INT64 int64_t; typedef UINT8 uint8_t; typedef UINT16 uint16_t; typedef UINT32 uint32_t; typedef UINT64 uint64_t; ================================================ FILE: src/windhawk/engine/libraries/MinHook/src/hde/table32.h ================================================ /* * Hacker Disassembler Engine 32 C * Copyright (c) 2008-2009, Vyacheslav Patkov. * All rights reserved. * */ #define C_NONE 0x00 #define C_MODRM 0x01 #define C_IMM8 0x02 #define C_IMM16 0x04 #define C_IMM_P66 0x10 #define C_REL8 0x20 #define C_REL32 0x40 #define C_GROUP 0x80 #define C_ERROR 0xff #define PRE_ANY 0x00 #define PRE_NONE 0x01 #define PRE_F2 0x02 #define PRE_F3 0x04 #define PRE_66 0x08 #define PRE_67 0x10 #define PRE_LOCK 0x20 #define PRE_SEG 0x40 #define PRE_ALL 0xff #define DELTA_OPCODES 0x4a #define DELTA_FPU_REG 0xf1 #define DELTA_FPU_MODRM 0xf8 #define DELTA_PREFIXES 0x130 #define DELTA_OP_LOCK_OK 0x1a1 #define DELTA_OP2_LOCK_OK 0x1b9 #define DELTA_OP_ONLY_MEM 0x1cb #define DELTA_OP2_ONLY_MEM 0x1da unsigned char hde32_table[] = { 0xa3,0xa8,0xa3,0xa8,0xa3,0xa8,0xa3,0xa8,0xa3,0xa8,0xa3,0xa8,0xa3,0xa8,0xa3, 0xa8,0xaa,0xaa,0xaa,0xaa,0xaa,0xaa,0xaa,0xaa,0xac,0xaa,0xb2,0xaa,0x9f,0x9f, 0x9f,0x9f,0xb5,0xa3,0xa3,0xa4,0xaa,0xaa,0xba,0xaa,0x96,0xaa,0xa8,0xaa,0xc3, 0xc3,0x96,0x96,0xb7,0xae,0xd6,0xbd,0xa3,0xc5,0xa3,0xa3,0x9f,0xc3,0x9c,0xaa, 0xaa,0xac,0xaa,0xbf,0x03,0x7f,0x11,0x7f,0x01,0x7f,0x01,0x3f,0x01,0x01,0x90, 0x82,0x7d,0x97,0x59,0x59,0x59,0x59,0x59,0x7f,0x59,0x59,0x60,0x7d,0x7f,0x7f, 0x59,0x59,0x59,0x59,0x59,0x59,0x59,0x59,0x59,0x59,0x59,0x59,0x9a,0x88,0x7d, 0x59,0x50,0x50,0x50,0x50,0x59,0x59,0x59,0x59,0x61,0x94,0x61,0x9e,0x59,0x59, 0x85,0x59,0x92,0xa3,0x60,0x60,0x59,0x59,0x59,0x59,0x59,0x59,0x59,0x59,0x59, 0x59,0x59,0x9f,0x01,0x03,0x01,0x04,0x03,0xd5,0x03,0xcc,0x01,0xbc,0x03,0xf0, 0x10,0x10,0x10,0x10,0x50,0x50,0x50,0x50,0x14,0x20,0x20,0x20,0x20,0x01,0x01, 0x01,0x01,0xc4,0x02,0x10,0x00,0x00,0x00,0x00,0x01,0x01,0xc0,0xc2,0x10,0x11, 0x02,0x03,0x11,0x03,0x03,0x04,0x00,0x00,0x14,0x00,0x02,0x00,0x00,0xc6,0xc8, 0x02,0x02,0x02,0x02,0x00,0x00,0xff,0xff,0xff,0xff,0x00,0x00,0x00,0xff,0xca, 0x01,0x01,0x01,0x00,0x06,0x00,0x04,0x00,0xc0,0xc2,0x01,0x01,0x03,0x01,0xff, 0xff,0x01,0x00,0x03,0xc4,0xc4,0xc6,0x03,0x01,0x01,0x01,0xff,0x03,0x03,0x03, 0xc8,0x40,0x00,0x0a,0x00,0x04,0x00,0x00,0x00,0x00,0x7f,0x00,0x33,0x01,0x00, 0x00,0x00,0x00,0x00,0x00,0xff,0xbf,0xff,0xff,0x00,0x00,0x00,0x00,0x07,0x00, 0x00,0xff,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0xff,0xff,0x00,0x00,0x00,0xbf,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x7f,0x00,0x00,0xff,0x4a,0x4a,0x4a,0x4a,0x4b,0x52,0x4a,0x4a,0x4a,0x4a,0x4f, 0x4c,0x4a,0x4a,0x4a,0x4a,0x4a,0x4a,0x4a,0x4a,0x55,0x45,0x40,0x4a,0x4a,0x4a, 0x45,0x59,0x4d,0x46,0x4a,0x5d,0x4a,0x4a,0x4a,0x4a,0x4a,0x4a,0x4a,0x4a,0x4a, 0x4a,0x4a,0x4a,0x4a,0x4a,0x61,0x63,0x67,0x4e,0x4a,0x4a,0x6b,0x6d,0x4a,0x4a, 0x45,0x6d,0x4a,0x4a,0x44,0x45,0x4a,0x4a,0x00,0x00,0x00,0x02,0x0d,0x06,0x06, 0x06,0x06,0x0e,0x00,0x00,0x00,0x00,0x06,0x06,0x06,0x00,0x06,0x06,0x02,0x06, 0x00,0x0a,0x0a,0x07,0x07,0x06,0x02,0x05,0x05,0x02,0x02,0x00,0x00,0x04,0x04, 0x04,0x04,0x00,0x00,0x00,0x0e,0x05,0x06,0x06,0x06,0x01,0x06,0x00,0x00,0x08, 0x00,0x10,0x00,0x18,0x00,0x20,0x00,0x28,0x00,0x30,0x00,0x80,0x01,0x82,0x01, 0x86,0x00,0xf6,0xcf,0xfe,0x3f,0xab,0x00,0xb0,0x00,0xb1,0x00,0xb3,0x00,0xba, 0xf8,0xbb,0x00,0xc0,0x00,0xc1,0x00,0xc7,0xbf,0x62,0xff,0x00,0x8d,0xff,0x00, 0xc4,0xff,0x00,0xc5,0xff,0x00,0xff,0xff,0xeb,0x01,0xff,0x0e,0x12,0x08,0x00, 0x13,0x09,0x00,0x16,0x08,0x00,0x17,0x09,0x00,0x2b,0x09,0x00,0xae,0xff,0x07, 0xb2,0xff,0x00,0xb4,0xff,0x00,0xb5,0xff,0x00,0xc3,0x01,0x00,0xc7,0xff,0xbf, 0xe7,0x08,0x00,0xf0,0x02,0x00 }; ================================================ FILE: src/windhawk/engine/libraries/MinHook/src/hde/table64.h ================================================ /* * Hacker Disassembler Engine 64 C * Copyright (c) 2008-2009, Vyacheslav Patkov. * All rights reserved. * */ #define C_NONE 0x00 #define C_MODRM 0x01 #define C_IMM8 0x02 #define C_IMM16 0x04 #define C_IMM_P66 0x10 #define C_REL8 0x20 #define C_REL32 0x40 #define C_GROUP 0x80 #define C_ERROR 0xff #define PRE_ANY 0x00 #define PRE_NONE 0x01 #define PRE_F2 0x02 #define PRE_F3 0x04 #define PRE_66 0x08 #define PRE_67 0x10 #define PRE_LOCK 0x20 #define PRE_SEG 0x40 #define PRE_ALL 0xff #define DELTA_OPCODES 0x4a #define DELTA_FPU_REG 0xfd #define DELTA_FPU_MODRM 0x104 #define DELTA_PREFIXES 0x13c #define DELTA_OP_LOCK_OK 0x1ae #define DELTA_OP2_LOCK_OK 0x1c6 #define DELTA_OP_ONLY_MEM 0x1d8 #define DELTA_OP2_ONLY_MEM 0x1e7 unsigned char hde64_table[] = { 0xa5,0xaa,0xa5,0xb8,0xa5,0xaa,0xa5,0xaa,0xa5,0xb8,0xa5,0xb8,0xa5,0xb8,0xa5, 0xb8,0xc0,0xc0,0xc0,0xc0,0xc0,0xc0,0xc0,0xc0,0xac,0xc0,0xcc,0xc0,0xa1,0xa1, 0xa1,0xa1,0xb1,0xa5,0xa5,0xa6,0xc0,0xc0,0xd7,0xda,0xe0,0xc0,0xe4,0xc0,0xea, 0xea,0xe0,0xe0,0x98,0xc8,0xee,0xf1,0xa5,0xd3,0xa5,0xa5,0xa1,0xea,0x9e,0xc0, 0xc0,0xc2,0xc0,0xe6,0x03,0x7f,0x11,0x7f,0x01,0x7f,0x01,0x3f,0x01,0x01,0xab, 0x8b,0x90,0x64,0x5b,0x5b,0x5b,0x5b,0x5b,0x92,0x5b,0x5b,0x76,0x90,0x92,0x92, 0x5b,0x5b,0x5b,0x5b,0x5b,0x5b,0x5b,0x5b,0x5b,0x5b,0x5b,0x5b,0x6a,0x73,0x90, 0x5b,0x52,0x52,0x52,0x52,0x5b,0x5b,0x5b,0x5b,0x77,0x7c,0x77,0x85,0x5b,0x5b, 0x70,0x5b,0x7a,0xaf,0x76,0x76,0x5b,0x5b,0x5b,0x5b,0x5b,0x5b,0x5b,0x5b,0x5b, 0x5b,0x5b,0x86,0x01,0x03,0x01,0x04,0x03,0xd5,0x03,0xd5,0x03,0xcc,0x01,0xbc, 0x03,0xf0,0x03,0x03,0x04,0x00,0x50,0x50,0x50,0x50,0xff,0x20,0x20,0x20,0x20, 0x01,0x01,0x01,0x01,0xc4,0x02,0x10,0xff,0xff,0xff,0x01,0x00,0x03,0x11,0xff, 0x03,0xc4,0xc6,0xc8,0x02,0x10,0x00,0xff,0xcc,0x01,0x01,0x01,0x00,0x00,0x00, 0x00,0x01,0x01,0x03,0x01,0xff,0xff,0xc0,0xc2,0x10,0x11,0x02,0x03,0x01,0x01, 0x01,0xff,0xff,0xff,0x00,0x00,0x00,0xff,0x00,0x00,0xff,0xff,0xff,0xff,0x10, 0x10,0x10,0x10,0x02,0x10,0x00,0x00,0xc6,0xc8,0x02,0x02,0x02,0x02,0x06,0x00, 0x04,0x00,0x02,0xff,0x00,0xc0,0xc2,0x01,0x01,0x03,0x03,0x03,0xca,0x40,0x00, 0x0a,0x00,0x04,0x00,0x00,0x00,0x00,0x7f,0x00,0x33,0x01,0x00,0x00,0x00,0x00, 0x00,0x00,0xff,0xbf,0xff,0xff,0x00,0x00,0x00,0x00,0x07,0x00,0x00,0xff,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xff,0xff, 0x00,0x00,0x00,0xbf,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x7f,0x00,0x00, 0xff,0x40,0x40,0x40,0x40,0x41,0x49,0x40,0x40,0x40,0x40,0x4c,0x42,0x40,0x40, 0x40,0x40,0x40,0x40,0x40,0x40,0x4f,0x44,0x53,0x40,0x40,0x40,0x44,0x57,0x43, 0x5c,0x40,0x60,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40, 0x40,0x40,0x64,0x66,0x6e,0x6b,0x40,0x40,0x6a,0x46,0x40,0x40,0x44,0x46,0x40, 0x40,0x5b,0x44,0x40,0x40,0x00,0x00,0x00,0x00,0x06,0x06,0x06,0x06,0x01,0x06, 0x06,0x02,0x06,0x06,0x00,0x06,0x00,0x0a,0x0a,0x00,0x00,0x00,0x02,0x07,0x07, 0x06,0x02,0x0d,0x06,0x06,0x06,0x0e,0x05,0x05,0x02,0x02,0x00,0x00,0x04,0x04, 0x04,0x04,0x05,0x06,0x06,0x06,0x00,0x00,0x00,0x0e,0x00,0x00,0x08,0x00,0x10, 0x00,0x18,0x00,0x20,0x00,0x28,0x00,0x30,0x00,0x80,0x01,0x82,0x01,0x86,0x00, 0xf6,0xcf,0xfe,0x3f,0xab,0x00,0xb0,0x00,0xb1,0x00,0xb3,0x00,0xba,0xf8,0xbb, 0x00,0xc0,0x00,0xc1,0x00,0xc7,0xbf,0x62,0xff,0x00,0x8d,0xff,0x00,0xc4,0xff, 0x00,0xc5,0xff,0x00,0xff,0xff,0xeb,0x01,0xff,0x0e,0x12,0x08,0x00,0x13,0x09, 0x00,0x16,0x08,0x00,0x17,0x09,0x00,0x2b,0x09,0x00,0xae,0xff,0x07,0xb2,0xff, 0x00,0xb4,0xff,0x00,0xb5,0xff,0x00,0xc3,0x01,0x00,0xc7,0xff,0xbf,0xe7,0x08, 0x00,0xf0,0x02,0x00 }; ================================================ FILE: src/windhawk/engine/libraries/MinHook/src/hook.c ================================================ /* * MinHook - The Minimalistic API Hooking Library for x64/x86 * Copyright (C) 2009-2017 Tsuda Kageyu. * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include #include #include #include "../include/MinHook.h" #include "buffer.h" #include "trampoline.h" #ifndef ARRAYSIZE #define ARRAYSIZE(A) (sizeof(A)/sizeof((A)[0])) #endif // Initial capacity of the HOOK_ENTRY buffer. #define INITIAL_HOOK_CAPACITY 32 // Initial capacity of the thread IDs buffer. #define INITIAL_THREAD_CAPACITY 128 // Special hook position values. #define INVALID_HOOK_POS UINT_MAX // Thread access rights for suspending/resuming threads. #define THREAD_ACCESS \ (THREAD_SUSPEND_RESUME | THREAD_GET_CONTEXT | THREAD_QUERY_INFORMATION | THREAD_SET_CONTEXT) // Suspended threads for Freeze()/Unfreeze(). typedef struct _FROZEN_THREADS { LPHANDLE pItems; // Data heap UINT capacity; // Size of allocated data heap, items UINT size; // Actual number of data items } FROZEN_THREADS, *PFROZEN_THREADS; // Thread freeze related definitions. typedef NTSTATUS(NTAPI *NtGetNextThread_t)( _In_ HANDLE ProcessHandle, _In_opt_ HANDLE ThreadHandle, _In_ ACCESS_MASK DesiredAccess, _In_ ULONG HandleAttributes, _In_ ULONG Flags, _Out_ PHANDLE NewThreadHandle ); #ifndef STATUS_NO_MORE_ENTRIES #define STATUS_NO_MORE_ENTRIES ((NTSTATUS)0x8000001AL) #endif #ifndef NT_SUCCESS #define NT_SUCCESS(Status) ((NTSTATUS)(Status) >= 0) #endif // Function and function pointer declarations. typedef MH_STATUS(WINAPI *ENABLE_HOOK_LL_PROC)(UINT pos, BOOL enable, PFROZEN_THREADS pThreads); typedef MH_STATUS(WINAPI *DISABLE_HOOK_CHAIN_PROC)(ULONG_PTR hookIdent, LPVOID pTarget, UINT parentPos, ENABLE_HOOK_LL_PROC ParentEnableHookLL, PFROZEN_THREADS pThreads); static MH_STATUS WINAPI DisableHookChain(ULONG_PTR hookIdent, LPVOID pTarget, UINT parentPos, ENABLE_HOOK_LL_PROC ParentEnableHookLL, PFROZEN_THREADS pThreads); // Executable buffer of a hook. typedef struct _EXEC_BUFFER { DISABLE_HOOK_CHAIN_PROC pDisableHookChain; ULONG_PTR hookIdent; JMP_RELAY jmpRelay; UINT8 trampoline[1]; // Uses the rest of the MEMORY_SLOT_SIZE bytes. } EXEC_BUFFER, *PEXEC_BUFFER; // Hook information. typedef struct _HOOK_ENTRY { ULONG_PTR hookIdent; // Hook identifier, allows to hook the same function multiple times with different identifiers. LPVOID pTarget; // Address of the target function. LPVOID pDetour; // Address of the detour function. PEXEC_BUFFER pExecBuffer; // Address of the executable buffer for relay and trampoline. UINT8 backup[8]; // Original prologue of the target function. UINT8 patchAbove : 1; // Uses the hot patch area. UINT8 isEnabled : 1; // Enabled. UINT8 queueEnable : 1; // Queued for enabling/disabling when != isEnabled. UINT nIP : 4; // Count of the instruction boundaries. UINT8 oldIPs[8]; // Instruction boundaries of the target function. UINT8 newIPs[8]; // Instruction boundaries of the trampoline function. } HOOK_ENTRY, *PHOOK_ENTRY; //------------------------------------------------------------------------- // Global Variables: //------------------------------------------------------------------------- // Mutex. If not NULL, this library is initialized. static HANDLE g_hMutex = NULL; // Private heap handle. static HANDLE g_hHeap; // Thread freeze related variables. static MH_THREAD_FREEZE_METHOD g_threadFreezeMethod = MH_FREEZE_METHOD_ORIGINAL; static NtGetNextThread_t pNtGetNextThread; // Hook entries. static struct { PHOOK_ENTRY pItems; // Data heap UINT capacity; // Size of allocated data heap, items UINT size; // Actual number of data items } g_hooks; //------------------------------------------------------------------------- // Returns INVALID_HOOK_POS if not found. static UINT FindHookEntry(ULONG_PTR hookIdent, LPVOID pTarget) { UINT i; for (i = 0; i < g_hooks.size; ++i) { PHOOK_ENTRY pHook = &g_hooks.pItems[i]; if (hookIdent == pHook->hookIdent && (ULONG_PTR)pTarget == (ULONG_PTR)pHook->pTarget) return i; } return INVALID_HOOK_POS; } //------------------------------------------------------------------------- static PHOOK_ENTRY AddHookEntry() { if (g_hooks.pItems == NULL) { g_hooks.capacity = INITIAL_HOOK_CAPACITY; g_hooks.pItems = (PHOOK_ENTRY)HeapAlloc( g_hHeap, 0, g_hooks.capacity * sizeof(HOOK_ENTRY)); if (g_hooks.pItems == NULL) return NULL; } else if (g_hooks.size >= g_hooks.capacity) { PHOOK_ENTRY p = (PHOOK_ENTRY)HeapReAlloc( g_hHeap, 0, g_hooks.pItems, (g_hooks.capacity * 2) * sizeof(HOOK_ENTRY)); if (p == NULL) return NULL; g_hooks.capacity *= 2; g_hooks.pItems = p; } return &g_hooks.pItems[g_hooks.size++]; } //------------------------------------------------------------------------- static VOID DeleteHookEntry(UINT pos) { if (pos < g_hooks.size - 1) g_hooks.pItems[pos] = g_hooks.pItems[g_hooks.size - 1]; g_hooks.size--; if (g_hooks.capacity / 2 >= INITIAL_HOOK_CAPACITY && g_hooks.capacity / 2 >= g_hooks.size) { PHOOK_ENTRY p = (PHOOK_ENTRY)HeapReAlloc( g_hHeap, 0, g_hooks.pItems, (g_hooks.capacity / 2) * sizeof(HOOK_ENTRY)); if (p == NULL) return; g_hooks.capacity /= 2; g_hooks.pItems = p; } } //------------------------------------------------------------------------- static DWORD_PTR FindOldIP(PHOOK_ENTRY pHook, DWORD_PTR ip) { // In any of the jump locations: // Target -> Hotpatch jump (if patchAbove) -> Relay jump // Restore IP to the detour. This is required for consistent behavior // as a part of a DisableHookChain call, otherwise, if IP is restored // to the target, hooks that should be called may be skipped. if (ip == (DWORD_PTR)pHook->pTarget) return (DWORD_PTR)pHook->pDetour; if (pHook->patchAbove && ip == ((DWORD_PTR)pHook->pTarget - sizeof(JMP_REL))) return (DWORD_PTR)pHook->pDetour; if (ip == (DWORD_PTR)&pHook->pExecBuffer->jmpRelay) return (DWORD_PTR)pHook->pDetour; UINT i; for (i = 0; i < pHook->nIP; ++i) { if (ip == ((DWORD_PTR)pHook->pExecBuffer->trampoline + pHook->newIPs[i])) return (DWORD_PTR)pHook->pTarget + pHook->oldIPs[i]; } return 0; } //------------------------------------------------------------------------- static DWORD_PTR FindNewIP(PHOOK_ENTRY pHook, DWORD_PTR ip) { UINT i; for (i = 0; i < pHook->nIP; ++i) { if (ip == ((DWORD_PTR)pHook->pTarget + pHook->oldIPs[i])) return (DWORD_PTR)pHook->pExecBuffer->trampoline + pHook->newIPs[i]; } return 0; } //------------------------------------------------------------------------- static VOID ProcessThreadIPs(HANDLE hThread, UINT pos, BOOL enable) { // If the thread suspended in the overwritten area, // move IP to the proper address. CONTEXT c; #if defined(_M_X64) || defined(__x86_64__) DWORD64 *pIP = &c.Rip; #else DWORD *pIP = &c.Eip; #endif PHOOK_ENTRY pHook = &g_hooks.pItems[pos]; DWORD_PTR ip; c.ContextFlags = CONTEXT_CONTROL; if (!GetThreadContext(hThread, &c)) return; if (enable) ip = FindNewIP(pHook, *pIP); else ip = FindOldIP(pHook, *pIP); if (ip != 0) { *pIP = ip; SetThreadContext(hThread, &c); } } //------------------------------------------------------------------------- static BOOL EnumerateAndSuspendThreads(PFROZEN_THREADS pThreads) { BOOL succeeded = FALSE; HANDLE hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, 0); if (hSnapshot != INVALID_HANDLE_VALUE) { THREADENTRY32 te; te.dwSize = sizeof(THREADENTRY32); if (Thread32First(hSnapshot, &te)) { succeeded = TRUE; do { if (te.dwSize >= (FIELD_OFFSET(THREADENTRY32, th32OwnerProcessID) + sizeof(DWORD)) && te.th32OwnerProcessID == GetCurrentProcessId() && te.th32ThreadID != GetCurrentThreadId()) { HANDLE hThread = OpenThread(THREAD_ACCESS, FALSE, te.th32ThreadID); if (hThread != NULL && SuspendThread(hThread) == (DWORD)-1) { CloseHandle(hThread); hThread = NULL; } if (hThread != NULL) { if (pThreads->pItems == NULL) { pThreads->capacity = INITIAL_THREAD_CAPACITY; pThreads->pItems = (LPHANDLE)HeapAlloc(g_hHeap, 0, pThreads->capacity * sizeof(HANDLE)); if (pThreads->pItems == NULL) succeeded = FALSE; } else if (pThreads->size >= pThreads->capacity) { LPHANDLE p; pThreads->capacity *= 2; p = (LPHANDLE)HeapReAlloc( g_hHeap, 0, pThreads->pItems, pThreads->capacity * sizeof(HANDLE)); if (p) pThreads->pItems = p; else succeeded = FALSE; } if (!succeeded) { ResumeThread(hThread); CloseHandle(hThread); break; } pThreads->pItems[pThreads->size++] = hThread; } } te.dwSize = sizeof(THREADENTRY32); } while (Thread32Next(hSnapshot, &te)); if (succeeded && GetLastError() != ERROR_NO_MORE_FILES) succeeded = FALSE; if (!succeeded && pThreads->pItems != NULL) { UINT i; for (i = 0; i < pThreads->size; ++i) { ResumeThread(pThreads->pItems[i]); CloseHandle(pThreads->pItems[i]); } HeapFree(g_hHeap, 0, pThreads->pItems); pThreads->pItems = NULL; } } CloseHandle(hSnapshot); } return succeeded; } //------------------------------------------------------------------------- static BOOL EnumerateAndSuspendThreadsFast(PFROZEN_THREADS pThreads) { BOOL succeeded = TRUE; HANDLE hThread = NULL; BOOL bClosePrevThread = FALSE; while (1) { HANDLE hNextThread; NTSTATUS status = pNtGetNextThread(GetCurrentProcess(), hThread, THREAD_ACCESS, 0, 0, &hNextThread); if (bClosePrevThread) CloseHandle(hThread); if (!NT_SUCCESS(status)) { if (status != STATUS_NO_MORE_ENTRIES) succeeded = FALSE; break; } hThread = hNextThread; bClosePrevThread = TRUE; if (GetThreadId(hThread) == GetCurrentThreadId()) continue; if (SuspendThread(hThread) == (DWORD)-1) continue; bClosePrevThread = FALSE; if (pThreads->pItems == NULL) { pThreads->capacity = INITIAL_THREAD_CAPACITY; pThreads->pItems = (LPHANDLE)HeapAlloc(g_hHeap, 0, pThreads->capacity * sizeof(HANDLE)); if (pThreads->pItems == NULL) succeeded = FALSE; } else if (pThreads->size >= pThreads->capacity) { pThreads->capacity *= 2; LPHANDLE p = (LPHANDLE)HeapReAlloc( g_hHeap, 0, pThreads->pItems, pThreads->capacity * sizeof(HANDLE)); if (p) pThreads->pItems = p; else succeeded = FALSE; } if (!succeeded) { ResumeThread(hThread); CloseHandle(hThread); break; } // Perform a synchronous operation to make sure the thread really is suspended. // https://devblogs.microsoft.com/oldnewthing/20150205-00/?p=44743 CONTEXT c; c.ContextFlags = CONTEXT_CONTROL; GetThreadContext(hThread, &c); pThreads->pItems[pThreads->size++] = hThread; } if (!succeeded && pThreads->pItems != NULL) { UINT i; for (i = 0; i < pThreads->size; ++i) { ResumeThread(pThreads->pItems[i]); CloseHandle(pThreads->pItems[i]); } HeapFree(g_hHeap, 0, pThreads->pItems); pThreads->pItems = NULL; } return succeeded; } //------------------------------------------------------------------------- static VOID ProcessFrozenThreads(PFROZEN_THREADS pThreads, UINT pos, BOOL enable) { if (pThreads->pItems != NULL) { UINT i; for (i = 0; i < pThreads->size; ++i) { ProcessThreadIPs(pThreads->pItems[i], pos, enable); } } } //------------------------------------------------------------------------- static MH_STATUS Freeze(PFROZEN_THREADS pThreads) { MH_STATUS status = MH_OK; pThreads->pItems = NULL; pThreads->capacity = 0; pThreads->size = 0; switch (g_threadFreezeMethod) { case MH_FREEZE_METHOD_ORIGINAL: if (!EnumerateAndSuspendThreads(pThreads)) status = MH_ERROR_MEMORY_ALLOC; break; case MH_FREEZE_METHOD_FAST_UNDOCUMENTED: if (!EnumerateAndSuspendThreadsFast(pThreads)) status = MH_ERROR_MEMORY_ALLOC; break; case MH_FREEZE_METHOD_NONE_UNSAFE: // Nothing to do. break; } return status; } //------------------------------------------------------------------------- static VOID Unfreeze(PFROZEN_THREADS pThreads) { if (pThreads->pItems != NULL) { UINT i; for (i = 0; i < pThreads->size; ++i) { ResumeThread(pThreads->pItems[i]); CloseHandle(pThreads->pItems[i]); } HeapFree(g_hHeap, 0, pThreads->pItems); } } //------------------------------------------------------------------------- static MH_STATUS CreateHookTrampoline(UINT pos) { PHOOK_ENTRY pHook = &g_hooks.pItems[pos]; TRAMPOLINE ct; ct.pTarget = pHook->pTarget; ct.pTrampoline = pHook->pExecBuffer->trampoline; ct.trampolineSize = MEMORY_SLOT_SIZE - offsetof(EXEC_BUFFER, trampoline); if (!CreateTrampolineFunction(&ct)) { return MH_ERROR_UNSUPPORTED_FUNCTION; } // Back up the target function. if (ct.patchAbove) { memcpy( pHook->backup, (LPBYTE)pHook->pTarget - sizeof(JMP_REL), sizeof(JMP_REL) + sizeof(JMP_REL_SHORT)); } else { memcpy(pHook->backup, pHook->pTarget, sizeof(JMP_REL)); } pHook->patchAbove = ct.patchAbove; pHook->nIP = ct.nIP; memcpy(pHook->oldIPs, ct.oldIPs, ARRAYSIZE(ct.oldIPs)); memcpy(pHook->newIPs, ct.newIPs, ARRAYSIZE(ct.newIPs)); return MH_OK; } //------------------------------------------------------------------------- static MH_STATUS WINAPI EnableHookLL(UINT pos, BOOL enable, PFROZEN_THREADS pThreads) { PHOOK_ENTRY pHook = &g_hooks.pItems[pos]; DWORD oldProtect; SIZE_T patchSize = sizeof(JMP_REL); LPBYTE pPatchTarget = (LPBYTE)pHook->pTarget; if (enable) { MH_STATUS status = CreateHookTrampoline(pos); if (status != MH_OK) return status; } if (pHook->patchAbove) { pPatchTarget -= sizeof(JMP_REL); patchSize += sizeof(JMP_REL_SHORT); } if (!enable) { PJMP_REL pJmp = (PJMP_REL)pPatchTarget; if (pJmp->opcode == 0xE9) { PJMP_RELAY pJmpRelay = (PJMP_RELAY)(((LPBYTE)pJmp + sizeof(JMP_REL)) + (INT32)pJmp->operand); if (&pHook->pExecBuffer->jmpRelay != pJmpRelay) { PEXEC_BUFFER pOtherExecBuffer = (PEXEC_BUFFER)((LPBYTE)pJmpRelay - offsetof(EXEC_BUFFER, jmpRelay)); return pOtherExecBuffer->pDisableHookChain(pOtherExecBuffer->hookIdent, pHook->pTarget, pos, EnableHookLL, pThreads); } } } if (!VirtualProtect(pPatchTarget, patchSize, PAGE_EXECUTE_READWRITE, &oldProtect)) return MH_ERROR_MEMORY_PROTECT; if (enable) { PJMP_REL pJmp = (PJMP_REL)pPatchTarget; pJmp->opcode = 0xE9; pJmp->operand = (UINT32)((LPBYTE)&pHook->pExecBuffer->jmpRelay - (pPatchTarget + sizeof(JMP_REL))); if (pHook->patchAbove) { PJMP_REL_SHORT pShortJmp = (PJMP_REL_SHORT)pHook->pTarget; pShortJmp->opcode = 0xEB; pShortJmp->operand = (UINT8)(0 - (sizeof(JMP_REL_SHORT) + sizeof(JMP_REL))); } } else { if (pHook->patchAbove) memcpy(pPatchTarget, pHook->backup, sizeof(JMP_REL) + sizeof(JMP_REL_SHORT)); else memcpy(pPatchTarget, pHook->backup, sizeof(JMP_REL)); } VirtualProtect(pPatchTarget, patchSize, oldProtect, &oldProtect); // Just-in-case measure. FlushInstructionCache(GetCurrentProcess(), pPatchTarget, patchSize); ProcessFrozenThreads(pThreads, pos, enable); pHook->isEnabled = enable; pHook->queueEnable = enable; return MH_OK; } //------------------------------------------------------------------------- static MH_STATUS EnableHooksLL(ULONG_PTR hookIdent, LPVOID pTarget, BOOL enable) { MH_STATUS status = MH_OK; UINT i, first = INVALID_HOOK_POS; for (i = 0; i < g_hooks.size; ++i) { PHOOK_ENTRY pHook = &g_hooks.pItems[i]; if (pHook->isEnabled != enable && (hookIdent == MH_ALL_IDENTS || pHook->hookIdent == hookIdent) && (pTarget == MH_ALL_HOOKS || (ULONG_PTR)pTarget == (ULONG_PTR)pHook->pTarget)) { first = i; break; } } if (first != INVALID_HOOK_POS) { FROZEN_THREADS threads; status = Freeze(&threads); if (status == MH_OK) { for (i = first; i < g_hooks.size; ++i) { PHOOK_ENTRY pHook = &g_hooks.pItems[i]; if (pHook->isEnabled != enable && (hookIdent == MH_ALL_IDENTS || pHook->hookIdent == hookIdent) && (pTarget == MH_ALL_HOOKS || (ULONG_PTR)pTarget == (ULONG_PTR)pHook->pTarget)) { MH_STATUS enable_status = EnableHookLL(i, enable, &threads); // Instead of stopping on the first error, we enable as much // hooks as we can, and return the last error, if any. if (enable_status != MH_OK) status = enable_status; } } Unfreeze(&threads); } } return status; } //------------------------------------------------------------------------- static HANDLE CreateProcessMutex(VOID) { TCHAR szMutexName[sizeof("minhook_multihook_12345678")] = TEXT("minhook_multihook_"); UINT mutexNameLen = sizeof("minhook_multihook_") - 1; DWORD dw = GetCurrentProcessId(); UINT i; // Build szMutexName in the following format: // printf("minhook_multihook_%08X", GetCurrentProcessId()); for (i = 0; i < 8; i++) { TCHAR ch; BYTE b = dw >> (32 - 4); if (b < 0x0A) ch = b + TEXT('0'); else ch = b - 0x0A + TEXT('A'); szMutexName[mutexNameLen++] = ch; dw <<= 4; } szMutexName[mutexNameLen] = TEXT('\0'); return CreateMutex(NULL, FALSE, szMutexName); } //------------------------------------------------------------------------- MH_STATUS WINAPI MH_Initialize(VOID) { if (g_hMutex != NULL) return MH_ERROR_ALREADY_INITIALIZED; g_hMutex = CreateProcessMutex(); if (g_hMutex == NULL) return MH_ERROR_MUTEX_FAILURE; g_hHeap = HeapCreate(0, 0, 0); if (g_hHeap == NULL) { CloseHandle(g_hMutex); g_hMutex = NULL; return MH_ERROR_MEMORY_ALLOC; } // Initialize the internal function buffer. InitializeBuffer(); return MH_OK; } //------------------------------------------------------------------------- MH_STATUS WINAPI MH_Uninitialize(VOID) { if (g_hMutex == NULL) return MH_ERROR_NOT_INITIALIZED; if (WaitForSingleObject(g_hMutex, INFINITE) != WAIT_OBJECT_0) return MH_ERROR_MUTEX_FAILURE; MH_STATUS status = EnableHooksLL(MH_ALL_IDENTS, MH_ALL_HOOKS, FALSE); ReleaseMutex(g_hMutex); if (status != MH_OK) return status; // Free the internal function buffer. // HeapFree is actually not required, but some tools detect a false // memory leak without HeapFree. UninitializeBuffer(); HeapFree(g_hHeap, 0, g_hooks.pItems); HeapDestroy(g_hHeap); g_hHeap = NULL; g_hooks.pItems = NULL; g_hooks.capacity = 0; g_hooks.size = 0; CloseHandle(g_hMutex); g_hMutex = NULL; return MH_OK; } //------------------------------------------------------------------------- MH_STATUS WINAPI MH_SetThreadFreezeMethod(MH_THREAD_FREEZE_METHOD method) { if (g_hMutex == NULL) return MH_ERROR_NOT_INITIALIZED; if (WaitForSingleObject(g_hMutex, INFINITE) != WAIT_OBJECT_0) return MH_ERROR_MUTEX_FAILURE; if (method == MH_FREEZE_METHOD_FAST_UNDOCUMENTED && !pNtGetNextThread) { HMODULE hNtdll = GetModuleHandle(L"ntdll.dll"); if (hNtdll) pNtGetNextThread = (NtGetNextThread_t)GetProcAddress(hNtdll, "NtGetNextThread"); if (!pNtGetNextThread) { // Fall back to the original method. method = MH_FREEZE_METHOD_ORIGINAL; } } g_threadFreezeMethod = method; ReleaseMutex(g_hMutex); return MH_OK; } //------------------------------------------------------------------------- MH_STATUS WINAPI MH_CreateHookEx(ULONG_PTR hookIdent, LPVOID pTarget, LPVOID pDetour, LPVOID *ppOriginal) { if (g_hMutex == NULL) return MH_ERROR_NOT_INITIALIZED; if (WaitForSingleObject(g_hMutex, INFINITE) != WAIT_OBJECT_0) return MH_ERROR_MUTEX_FAILURE; MH_STATUS status = MH_OK; if (IsExecutableAddress(pTarget) && IsExecutableAddress(pDetour)) { UINT pos = FindHookEntry(hookIdent, pTarget); if (pos == INVALID_HOOK_POS) { PEXEC_BUFFER pBuffer = (PEXEC_BUFFER)AllocateBuffer(pTarget); if (pBuffer != NULL) { PHOOK_ENTRY pHook = AddHookEntry(); if (pHook != NULL) { pBuffer->hookIdent = hookIdent; pBuffer->pDisableHookChain = DisableHookChain; CreateRelayFunction(&pBuffer->jmpRelay, pDetour); pHook->hookIdent = hookIdent; pHook->pTarget = pTarget; pHook->pDetour = pDetour; pHook->pExecBuffer = pBuffer; pHook->isEnabled = FALSE; pHook->queueEnable = FALSE; if (ppOriginal != NULL) *ppOriginal = pBuffer->trampoline; } else { status = MH_ERROR_MEMORY_ALLOC; } if (status != MH_OK) { FreeBuffer(pBuffer); } } else { status = MH_ERROR_MEMORY_ALLOC; } } else { status = MH_ERROR_ALREADY_CREATED; } } else { status = MH_ERROR_NOT_EXECUTABLE; } ReleaseMutex(g_hMutex); return status; } //------------------------------------------------------------------------- MH_STATUS WINAPI MH_CreateHook(LPVOID pTarget, LPVOID pDetour, LPVOID *ppOriginal) { return MH_CreateHookEx(MH_DEFAULT_IDENT, pTarget, pDetour, ppOriginal); } //------------------------------------------------------------------------- MH_STATUS WINAPI MH_RemoveHookEx(ULONG_PTR hookIdent, LPVOID pTarget) { if (g_hMutex == NULL) return MH_ERROR_NOT_INITIALIZED; if (WaitForSingleObject(g_hMutex, INFINITE) != WAIT_OBJECT_0) return MH_ERROR_MUTEX_FAILURE; MH_STATUS status = MH_OK; if (hookIdent == MH_ALL_IDENTS || pTarget == MH_ALL_HOOKS) { status = EnableHooksLL(hookIdent, pTarget, FALSE); if (status == MH_OK) { UINT i = 0; while (i < g_hooks.size) { PHOOK_ENTRY pHook = &g_hooks.pItems[i]; if ((hookIdent == MH_ALL_IDENTS || pHook->hookIdent == hookIdent) && (pTarget == MH_ALL_HOOKS || (ULONG_PTR)pTarget == (ULONG_PTR)pHook->pTarget)) { FreeBuffer(pHook->pExecBuffer); DeleteHookEntry(i); } else { ++i; } } } } else { UINT pos = FindHookEntry(hookIdent, pTarget); if (pos != INVALID_HOOK_POS) { if (g_hooks.pItems[pos].isEnabled) { FROZEN_THREADS threads; status = Freeze(&threads); if (status == MH_OK) { status = EnableHookLL(pos, FALSE, &threads); Unfreeze(&threads); } } if (status == MH_OK) { FreeBuffer(g_hooks.pItems[pos].pExecBuffer); DeleteHookEntry(pos); } } else { status = MH_ERROR_NOT_CREATED; } } ReleaseMutex(g_hMutex); return status; } //------------------------------------------------------------------------- MH_STATUS WINAPI MH_RemoveHook(LPVOID pTarget) { return MH_RemoveHookEx(MH_DEFAULT_IDENT, pTarget); } //------------------------------------------------------------------------- MH_STATUS WINAPI MH_RemoveDisabledHooksEx(ULONG_PTR hookIdent) { if (g_hMutex == NULL) return MH_ERROR_NOT_INITIALIZED; if (WaitForSingleObject(g_hMutex, INFINITE) != WAIT_OBJECT_0) return MH_ERROR_MUTEX_FAILURE; MH_STATUS status = MH_OK; UINT i = 0; while (i < g_hooks.size) { PHOOK_ENTRY pHook = &g_hooks.pItems[i]; if ((hookIdent == MH_ALL_IDENTS || pHook->hookIdent == hookIdent) && !pHook->isEnabled) { FreeBuffer(pHook->pExecBuffer); DeleteHookEntry(i); } else { ++i; } } ReleaseMutex(g_hMutex); return status; } //------------------------------------------------------------------------- MH_STATUS WINAPI MH_RemoveDisabledHooks() { return MH_RemoveDisabledHooksEx(MH_DEFAULT_IDENT); } //------------------------------------------------------------------------- static MH_STATUS WINAPI DisableHookChain(ULONG_PTR hookIdent, LPVOID pTarget, UINT parentPos, ENABLE_HOOK_LL_PROC ParentEnableHookLL, PFROZEN_THREADS pThreads) { UINT pos = FindHookEntry(hookIdent, pTarget); if (pos == INVALID_HOOK_POS) return MH_ERROR_NOT_CREATED; if (!g_hooks.pItems[pos].isEnabled) return MH_ERROR_DISABLED; // We're not Freeze()-ing the threads here, because we assume that the function // was called from a different MinHook module, which already suspended all threads. MH_STATUS status = EnableHookLL(pos, FALSE, pThreads); if (status != MH_OK) return status; status = ParentEnableHookLL(parentPos, FALSE, pThreads); if (status != MH_OK) return status; return EnableHookLL(pos, TRUE, pThreads); } //------------------------------------------------------------------------- static MH_STATUS EnableHook(ULONG_PTR hookIdent, LPVOID pTarget, BOOL enable) { if (g_hMutex == NULL) return MH_ERROR_NOT_INITIALIZED; if (WaitForSingleObject(g_hMutex, INFINITE) != WAIT_OBJECT_0) return MH_ERROR_MUTEX_FAILURE; MH_STATUS status = MH_OK; if (hookIdent == MH_ALL_IDENTS || pTarget == MH_ALL_HOOKS) { status = EnableHooksLL(hookIdent, pTarget, enable); } else { UINT pos = FindHookEntry(hookIdent, pTarget); if (pos != INVALID_HOOK_POS) { if (g_hooks.pItems[pos].isEnabled != enable) { FROZEN_THREADS threads; status = Freeze(&threads); if (status == MH_OK) { status = EnableHookLL(pos, enable, &threads); Unfreeze(&threads); } } else { status = enable ? MH_ERROR_ENABLED : MH_ERROR_DISABLED; } } else { status = MH_ERROR_NOT_CREATED; } } ReleaseMutex(g_hMutex); return status; } //------------------------------------------------------------------------- MH_STATUS WINAPI MH_EnableHookEx(ULONG_PTR hookIdent, LPVOID pTarget) { return EnableHook(hookIdent, pTarget, TRUE); } //------------------------------------------------------------------------- MH_STATUS WINAPI MH_EnableHook(LPVOID pTarget) { return MH_EnableHookEx(MH_DEFAULT_IDENT, pTarget); } //------------------------------------------------------------------------- MH_STATUS WINAPI MH_DisableHookEx(ULONG_PTR hookIdent, LPVOID pTarget) { return EnableHook(hookIdent, pTarget, FALSE); } //------------------------------------------------------------------------- MH_STATUS WINAPI MH_DisableHook(LPVOID pTarget) { return MH_DisableHookEx(MH_DEFAULT_IDENT, pTarget); } //------------------------------------------------------------------------- static MH_STATUS QueueHook(ULONG_PTR hookIdent, LPVOID pTarget, BOOL queueEnable) { if (g_hMutex == NULL) return MH_ERROR_NOT_INITIALIZED; if (WaitForSingleObject(g_hMutex, INFINITE) != WAIT_OBJECT_0) return MH_ERROR_MUTEX_FAILURE; MH_STATUS status = MH_OK; if (hookIdent == MH_ALL_IDENTS || pTarget == MH_ALL_HOOKS) { UINT i; for (i = 0; i < g_hooks.size; ++i) { PHOOK_ENTRY pHook = &g_hooks.pItems[i]; if ((hookIdent == MH_ALL_IDENTS || pHook->hookIdent == hookIdent) && (pTarget == MH_ALL_HOOKS || (ULONG_PTR)pTarget == (ULONG_PTR)pHook->pTarget)) { pHook->queueEnable = queueEnable; } } } else { UINT pos = FindHookEntry(hookIdent, pTarget); if (pos != INVALID_HOOK_POS) { g_hooks.pItems[pos].queueEnable = queueEnable; } else { status = MH_ERROR_NOT_CREATED; } } ReleaseMutex(g_hMutex); return status; } //------------------------------------------------------------------------- MH_STATUS WINAPI MH_QueueEnableHookEx(ULONG_PTR hookIdent, LPVOID pTarget) { return QueueHook(hookIdent, pTarget, TRUE); } //------------------------------------------------------------------------- MH_STATUS WINAPI MH_QueueEnableHook(LPVOID pTarget) { return MH_QueueEnableHookEx(MH_DEFAULT_IDENT, pTarget); } //------------------------------------------------------------------------- MH_STATUS WINAPI MH_QueueDisableHookEx(ULONG_PTR hookIdent, LPVOID pTarget) { return QueueHook(hookIdent, pTarget, FALSE); } //------------------------------------------------------------------------- MH_STATUS WINAPI MH_QueueDisableHook(LPVOID pTarget) { return MH_QueueDisableHookEx(MH_DEFAULT_IDENT, pTarget); } //------------------------------------------------------------------------- MH_STATUS WINAPI MH_ApplyQueuedEx(ULONG_PTR hookIdent) { if (g_hMutex == NULL) return MH_ERROR_NOT_INITIALIZED; if (WaitForSingleObject(g_hMutex, INFINITE) != WAIT_OBJECT_0) return MH_ERROR_MUTEX_FAILURE; MH_STATUS status = MH_OK; UINT i, first = INVALID_HOOK_POS; for (i = 0; i < g_hooks.size; ++i) { PHOOK_ENTRY pHook = &g_hooks.pItems[i]; if ((hookIdent == MH_ALL_IDENTS || pHook->hookIdent == hookIdent) && pHook->isEnabled != pHook->queueEnable) { first = i; break; } } if (first != INVALID_HOOK_POS) { FROZEN_THREADS threads; status = Freeze(&threads); if (status == MH_OK) { for (i = first; i < g_hooks.size; ++i) { PHOOK_ENTRY pHook = &g_hooks.pItems[i]; if ((hookIdent == MH_ALL_IDENTS || pHook->hookIdent == hookIdent) && pHook->isEnabled != pHook->queueEnable) { MH_STATUS enable_status = EnableHookLL(i, pHook->queueEnable, &threads); // Instead of stopping on the first error, we apply as much // hooks as we can, and return the last error, if any. if (enable_status != MH_OK) status = enable_status; } } Unfreeze(&threads); } } ReleaseMutex(g_hMutex); return status; } //------------------------------------------------------------------------- MH_STATUS WINAPI MH_ApplyQueued(VOID) { return MH_ApplyQueuedEx(MH_DEFAULT_IDENT); } //------------------------------------------------------------------------- MH_STATUS WINAPI MH_CreateHookApiEx( LPCWSTR pszModule, LPCSTR pszProcName, LPVOID pDetour, LPVOID *ppOriginal, LPVOID *ppTarget) { HMODULE hModule; LPVOID pTarget; hModule = GetModuleHandleW(pszModule); if (hModule == NULL) return MH_ERROR_MODULE_NOT_FOUND; pTarget = (LPVOID)GetProcAddress(hModule, pszProcName); if (pTarget == NULL) return MH_ERROR_FUNCTION_NOT_FOUND; if (ppTarget != NULL) *ppTarget = pTarget; return MH_CreateHook(pTarget, pDetour, ppOriginal); } //------------------------------------------------------------------------- MH_STATUS WINAPI MH_CreateHookApi( LPCWSTR pszModule, LPCSTR pszProcName, LPVOID pDetour, LPVOID *ppOriginal) { return MH_CreateHookApiEx(pszModule, pszProcName, pDetour, ppOriginal, NULL); } //------------------------------------------------------------------------- const char *WINAPI MH_StatusToString(MH_STATUS status) { #define MH_ST2STR(x) \ case x: \ return #x; switch (status) { MH_ST2STR(MH_UNKNOWN) MH_ST2STR(MH_OK) MH_ST2STR(MH_ERROR_ALREADY_INITIALIZED) MH_ST2STR(MH_ERROR_NOT_INITIALIZED) MH_ST2STR(MH_ERROR_ALREADY_CREATED) MH_ST2STR(MH_ERROR_NOT_CREATED) MH_ST2STR(MH_ERROR_ENABLED) MH_ST2STR(MH_ERROR_DISABLED) MH_ST2STR(MH_ERROR_NOT_EXECUTABLE) MH_ST2STR(MH_ERROR_UNSUPPORTED_FUNCTION) MH_ST2STR(MH_ERROR_MEMORY_ALLOC) MH_ST2STR(MH_ERROR_MEMORY_PROTECT) MH_ST2STR(MH_ERROR_MODULE_NOT_FOUND) MH_ST2STR(MH_ERROR_FUNCTION_NOT_FOUND) MH_ST2STR(MH_ERROR_MUTEX_FAILURE) } #undef MH_ST2STR return "(unknown)"; } ================================================ FILE: src/windhawk/engine/libraries/MinHook/src/trampoline.c ================================================ /* * MinHook - The Minimalistic API Hooking Library for x64/x86 * Copyright (C) 2009-2017 Tsuda Kageyu. * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include #if defined(_MSC_VER) && !defined(MINHOOK_DISABLE_INTRINSICS) #define ALLOW_INTRINSICS #include #endif #ifndef ARRAYSIZE #define ARRAYSIZE(A) (sizeof(A)/sizeof((A)[0])) #endif #if defined(_M_X64) || defined(__x86_64__) #include "./hde/hde64.h" typedef hde64s HDE; #define HDE_DISASM(code, hs) hde64_disasm(code, hs) #else #include "./hde/hde32.h" typedef hde32s HDE; #define HDE_DISASM(code, hs) hde32_disasm(code, hs) #endif #include "trampoline.h" #include "buffer.h" //------------------------------------------------------------------------- static BOOL IsCodePadding(LPBYTE pInst, UINT size) { UINT i; if (pInst[0] != 0x00 && pInst[0] != 0x90 && pInst[0] != 0xCC) return FALSE; for (i = 1; i < size; ++i) { if (pInst[i] != pInst[0]) return FALSE; } return TRUE; } //------------------------------------------------------------------------- VOID CreateRelayFunction(PJMP_RELAY pJmpRelay, LPVOID pDetour) { #if defined(_M_X64) || defined(__x86_64__) JMP_ABS jmp = { 0xFF, 0x25, 0x00000000, // FF25 00000000: JMP [RIP+6] 0x0000000000000000ULL // Absolute destination address }; jmp.address = (ULONG_PTR)pDetour; #else JMP_REL jmp = { 0xE9, // E9 xxxxxxxx: JMP +5+xxxxxxxx 0x00000000 // Relative destination address }; jmp.operand = (UINT32)((LPBYTE)pDetour - ((LPBYTE)pJmpRelay + sizeof(jmp))); #endif memcpy(pJmpRelay, &jmp, sizeof(jmp)); } //------------------------------------------------------------------------- BOOL CreateTrampolineFunction(PTRAMPOLINE ct) { #if defined(_M_X64) || defined(__x86_64__) CALL_ABS call = { 0xFF, 0x15, 0x00000002, // FF15 00000002: CALL [RIP+8] 0xEB, 0x08, // EB 08: JMP +10 0x0000000000000000ULL // Absolute destination address }; JMP_ABS jmp = { 0xFF, 0x25, 0x00000000, // FF25 00000000: JMP [RIP+6] 0x0000000000000000ULL // Absolute destination address }; JCC_ABS jcc = { 0x70, 0x0E, // 7* 0E: J** +16 0xFF, 0x25, 0x00000000, // FF25 00000000: JMP [RIP+6] 0x0000000000000000ULL // Absolute destination address }; #else CALL_REL call = { 0xE8, // E8 xxxxxxxx: CALL +5+xxxxxxxx 0x00000000 // Relative destination address }; JMP_REL jmp = { 0xE9, // E9 xxxxxxxx: JMP +5+xxxxxxxx 0x00000000 // Relative destination address }; JCC_REL jcc = { 0x0F, 0x80, // 0F8* xxxxxxxx: J** +6+xxxxxxxx 0x00000000 // Relative destination address }; #endif UINT8 oldPos = 0; UINT8 newPos = 0; ULONG_PTR jmpDest = 0; // Destination address of an internal jump. BOOL finished = FALSE; // Is the function completed? #if defined(_M_X64) || defined(__x86_64__) UINT8 instBuf[16]; #endif ct->patchAbove = FALSE; ct->nIP = 0; do { HDE hs; UINT copySize; LPVOID pCopySrc; ULONG_PTR pOldInst = (ULONG_PTR)ct->pTarget + oldPos; ULONG_PTR pNewInst = (ULONG_PTR)ct->pTrampoline + newPos; copySize = HDE_DISASM((LPVOID)pOldInst, &hs); if (hs.flags & F_ERROR) return FALSE; pCopySrc = (LPVOID)pOldInst; if (oldPos >= sizeof(JMP_REL)) { // The trampoline function is long enough. // Complete the function with the jump to the target function. #if defined(_M_X64) || defined(__x86_64__) jmp.address = pOldInst; #else jmp.operand = (UINT32)(pOldInst - (pNewInst + sizeof(jmp))); #endif pCopySrc = &jmp; copySize = sizeof(jmp); finished = TRUE; } #if defined(_M_X64) || defined(__x86_64__) else if ((hs.modrm & 0xC7) == 0x05) { // Instructions using RIP relative addressing. (ModR/M = 00???101B) // Modify the RIP relative address. PUINT32 pRelAddr; // Avoid using memcpy to reduce the footprint. #ifndef ALLOW_INTRINSICS memcpy(instBuf, (LPBYTE)pOldInst, copySize); #else __movsb(instBuf, (LPBYTE)pOldInst, copySize); #endif pCopySrc = instBuf; // Relative address is stored at (instruction length - immediate value length - 4). pRelAddr = (PUINT32)(instBuf + hs.len - ((hs.flags & 0x3C) >> 2) - 4); *pRelAddr = (UINT32)((pOldInst + hs.len + (INT32)hs.disp.disp32) - (pNewInst + hs.len)); // Complete the function if JMP (FF /4). if (hs.opcode == 0xFF && hs.modrm_reg == 4) finished = TRUE; } #endif else if (hs.opcode == 0xE8) { // Direct relative CALL ULONG_PTR dest = pOldInst + hs.len + (INT32)hs.imm.imm32; #if defined(_M_X64) || defined(__x86_64__) call.address = dest; #else call.operand = (UINT32)(dest - (pNewInst + sizeof(call))); #endif pCopySrc = &call; copySize = sizeof(call); } else if ((hs.opcode & 0xFD) == 0xE9) { // Direct relative JMP (EB or E9) ULONG_PTR dest = pOldInst + hs.len; if (hs.opcode == 0xEB) // isShort jmp dest += (INT8)hs.imm.imm8; else dest += (INT32)hs.imm.imm32; // Simply copy an internal jump. if ((ULONG_PTR)ct->pTarget <= dest && dest < ((ULONG_PTR)ct->pTarget + sizeof(JMP_REL))) { if (jmpDest < dest) jmpDest = dest; } else { #if defined(_M_X64) || defined(__x86_64__) jmp.address = dest; #else jmp.operand = (UINT32)(dest - (pNewInst + sizeof(jmp))); #endif pCopySrc = &jmp; copySize = sizeof(jmp); // Exit the function if it is not in the branch. finished = (pOldInst >= jmpDest); } } else if ((hs.opcode & 0xF0) == 0x70 || (hs.opcode & 0xFC) == 0xE0 || (hs.opcode2 & 0xF0) == 0x80) { // Direct relative Jcc ULONG_PTR dest = pOldInst + hs.len; if ((hs.opcode & 0xF0) == 0x70 // Jcc || (hs.opcode & 0xFC) == 0xE0) // LOOPNZ/LOOPZ/LOOP/JECXZ dest += (INT8)hs.imm.imm8; else dest += (INT32)hs.imm.imm32; // Simply copy an internal jump. if ((ULONG_PTR)ct->pTarget <= dest && dest < ((ULONG_PTR)ct->pTarget + sizeof(JMP_REL))) { if (jmpDest < dest) jmpDest = dest; } else if ((hs.opcode & 0xFC) == 0xE0) { // LOOPNZ/LOOPZ/LOOP/JCXZ/JECXZ to the outside are not supported. return FALSE; } else { UINT8 cond = ((hs.opcode != 0x0F ? hs.opcode : hs.opcode2) & 0x0F); #if defined(_M_X64) || defined(__x86_64__) // Invert the condition in x64 mode to simplify the conditional jump logic. jcc.opcode = 0x71 ^ cond; jcc.address = dest; #else jcc.opcode1 = 0x80 | cond; jcc.operand = (UINT32)(dest - (pNewInst + sizeof(jcc))); #endif pCopySrc = &jcc; copySize = sizeof(jcc); } } else if ((hs.opcode & 0xFE) == 0xC2) { // RET (C2 or C3) // Complete the function if not in a branch. finished = (pOldInst >= jmpDest); } // Can't alter the instruction length in a branch. if (pOldInst < jmpDest && copySize != hs.len) return FALSE; // Trampoline function is too large. if ((newPos + copySize) > ct->trampolineSize) return FALSE; // Trampoline function has too many instructions. if (ct->nIP >= ARRAYSIZE(ct->oldIPs)) return FALSE; ct->oldIPs[ct->nIP] = oldPos; ct->newIPs[ct->nIP] = newPos; ct->nIP++; // Avoid using memcpy to reduce the footprint. #ifndef ALLOW_INTRINSICS memcpy((LPBYTE)ct->pTrampoline + newPos, pCopySrc, copySize); #else __movsb((LPBYTE)ct->pTrampoline + newPos, (LPBYTE)pCopySrc, copySize); #endif newPos += copySize; oldPos += hs.len; } while (!finished); // Is there enough place for a long jump? if (oldPos < sizeof(JMP_REL) && !IsCodePadding((LPBYTE)ct->pTarget + oldPos, sizeof(JMP_REL) - oldPos)) { // Is there enough place for a short jump? if (oldPos < sizeof(JMP_REL_SHORT) && !IsCodePadding((LPBYTE)ct->pTarget + oldPos, sizeof(JMP_REL_SHORT) - oldPos)) { return FALSE; } // Can we place the long jump above the function? if (!IsExecutableAddress((LPBYTE)ct->pTarget - sizeof(JMP_REL))) return FALSE; if (!IsCodePadding((LPBYTE)ct->pTarget - sizeof(JMP_REL), sizeof(JMP_REL))) return FALSE; ct->patchAbove = TRUE; } return TRUE; } ================================================ FILE: src/windhawk/engine/libraries/MinHook/src/trampoline.h ================================================ /* * MinHook - The Minimalistic API Hooking Library for x64/x86 * Copyright (C) 2009-2017 Tsuda Kageyu. * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #pragma once #pragma pack(push, 1) // Structs for writing x86/x64 instructions. // 8-bit relative jump. typedef struct _JMP_REL_SHORT { UINT8 opcode; // EB xx: JMP +2+xx UINT8 operand; // Relative destination address } JMP_REL_SHORT, *PJMP_REL_SHORT; // 32-bit direct relative jump/call. typedef struct _JMP_REL { UINT8 opcode; // E9/E8 xxxxxxxx: JMP/CALL +5+xxxxxxxx UINT32 operand; // Relative destination address } JMP_REL, *PJMP_REL, CALL_REL; // 64-bit indirect absolute jump. typedef struct _JMP_ABS { UINT8 opcode0; // FF25 00000000: JMP [+6] UINT8 opcode1; UINT32 dummy; UINT64 address; // Absolute destination address } JMP_ABS, *PJMP_ABS; // 64-bit indirect absolute call. typedef struct _CALL_ABS { UINT8 opcode0; // FF15 00000002: CALL [+6] UINT8 opcode1; UINT32 dummy0; UINT8 dummy1; // EB 08: JMP +10 UINT8 dummy2; UINT64 address; // Absolute destination address } CALL_ABS; // 32-bit direct relative conditional jumps. typedef struct _JCC_REL { UINT8 opcode0; // 0F8* xxxxxxxx: J** +6+xxxxxxxx UINT8 opcode1; UINT32 operand; // Relative destination address } JCC_REL; // 64bit indirect absolute conditional jumps that x64 lacks. typedef struct _JCC_ABS { UINT8 opcode; // 7* 0E: J** +16 UINT8 dummy0; UINT8 dummy1; // FF25 00000000: JMP [+6] UINT8 dummy2; UINT32 dummy3; UINT64 address; // Absolute destination address } JCC_ABS; #pragma pack(pop) #if defined(_M_X64) || defined(__x86_64__) typedef JMP_ABS JMP_RELAY; typedef PJMP_ABS PJMP_RELAY; #else typedef JMP_REL JMP_RELAY; typedef PJMP_REL PJMP_RELAY; #endif typedef struct _TRAMPOLINE { LPVOID pTarget; // [In] Address of the target function. LPVOID pTrampoline; // [In] Buffer address for the trampoline function. UINT trampolineSize; // [In] The size of the trampoline function buffer. BOOL patchAbove; // [Out] Should use the hot patch area? UINT nIP; // [Out] Number of the instruction boundaries. UINT8 oldIPs[8]; // [Out] Instruction boundaries of the target function. UINT8 newIPs[8]; // [Out] Instruction boundaries of the trampoline function. } TRAMPOLINE, *PTRAMPOLINE; VOID CreateRelayFunction(PJMP_RELAY pJmpRelay, LPVOID pDetour); BOOL CreateTrampolineFunction(PTRAMPOLINE ct); ================================================ FILE: src/windhawk/engine/libraries/MinHook-Detours/MinHook.c ================================================ #include "MinHook.h" #include "SlimDetours/SlimDetours.h" // Initial capacity of the HOOK_ENTRY buffer. #define INITIAL_HOOK_CAPACITY 32 // Special hook position values. #define INVALID_HOOK_POS UINT_MAX // Memory protection flags to check the executable address. #define PAGE_EXECUTE_FLAGS \ (PAGE_EXECUTE | PAGE_EXECUTE_READ | PAGE_EXECUTE_READWRITE | PAGE_EXECUTE_WRITECOPY) typedef struct _HOOK_ENTRY { ULONG_PTR hookIdent; LPVOID pTarget; LPVOID pDetour; LPVOID pTargetOrTrampoline; LPVOID *ppOriginal; LPVOID pTrampolineToFree; UINT8 isEnabled : 1; UINT8 queueEnable : 1; HRESULT bulkLastError; } HOOK_ENTRY, *PHOOK_ENTRY; static CRITICAL_SECTION g_criticalSection; static BOOL g_initialized = FALSE; // Thread freeze related variables. static MH_THREAD_FREEZE_METHOD g_threadFreezeMethod = MH_FREEZE_METHOD_ORIGINAL; // Bulk operation related variables. static BOOL g_bulkContinueOnError = FALSE; static MH_ERROR_CALLBACK g_bulkErrorCallback = NULL; // Hook entries. struct { PHOOK_ENTRY pItems; // Data heap UINT capacity; // Size of allocated data heap, items UINT size; // Actual number of data items } g_hooks; // Returns INVALID_HOOK_POS if not found. static UINT FindHookEntry(ULONG_PTR hookIdent, LPVOID pTarget, UINT pos) { UINT i; for (i = pos; i < g_hooks.size; ++i) { PHOOK_ENTRY pHook = &g_hooks.pItems[i]; if ((hookIdent == MH_ALL_IDENTS || pHook->hookIdent == hookIdent) && (pTarget == MH_ALL_HOOKS || (ULONG_PTR)pTarget == (ULONG_PTR)pHook->pTarget)) { return i; } } return INVALID_HOOK_POS; } static UINT FindHookEntryEnabled(ULONG_PTR hookIdent, LPVOID pTarget, UINT pos, BOOL enabled) { UINT i = FindHookEntry(hookIdent, pTarget, pos); while (i != INVALID_HOOK_POS) { PHOOK_ENTRY pHook = &g_hooks.pItems[i]; if (pHook->isEnabled == enabled) { break; } i = FindHookEntry(hookIdent, pTarget, i + 1); } return i; } static UINT FindHookEntryQueued(ULONG_PTR hookIdent, LPVOID pTarget, UINT pos) { UINT i = FindHookEntry(hookIdent, pTarget, pos); while (i != INVALID_HOOK_POS) { PHOOK_ENTRY pHook = &g_hooks.pItems[i]; if (pHook->queueEnable != pHook->isEnabled) { break; } i = FindHookEntry(hookIdent, pTarget, i + 1); } return i; } static PHOOK_ENTRY AddHookEntry() { if (g_hooks.pItems == NULL) { g_hooks.capacity = INITIAL_HOOK_CAPACITY; g_hooks.pItems = (PHOOK_ENTRY)HeapAlloc( GetProcessHeap(), 0, g_hooks.capacity * sizeof(HOOK_ENTRY)); if (g_hooks.pItems == NULL) return NULL; } else if (g_hooks.size >= g_hooks.capacity) { PHOOK_ENTRY p = (PHOOK_ENTRY)HeapReAlloc( GetProcessHeap(), 0, g_hooks.pItems, (g_hooks.capacity * 2) * sizeof(HOOK_ENTRY)); if (p == NULL) return NULL; g_hooks.capacity *= 2; g_hooks.pItems = p; } return &g_hooks.pItems[g_hooks.size++]; } static VOID DeleteHookEntry(UINT pos) { if (pos < g_hooks.size - 1) g_hooks.pItems[pos] = g_hooks.pItems[g_hooks.size - 1]; g_hooks.size--; if (g_hooks.capacity / 2 >= INITIAL_HOOK_CAPACITY && g_hooks.capacity / 2 >= g_hooks.size) { PHOOK_ENTRY p = (PHOOK_ENTRY)HeapReAlloc( GetProcessHeap(), 0, g_hooks.pItems, (g_hooks.capacity / 2) * sizeof(HOOK_ENTRY)); if (p == NULL) return; g_hooks.capacity /= 2; g_hooks.pItems = p; } } static BOOL IsExecutableAddress(LPVOID pAddress) { MEMORY_BASIC_INFORMATION mi; VirtualQuery(pAddress, &mi, sizeof(mi)); return (mi.State == MEM_COMMIT && (mi.Protect & PAGE_EXECUTE_FLAGS)); } static void FreeHookTrampolineIfNeeded(PHOOK_ENTRY pHook) { if (pHook->pTrampolineToFree) { SlimDetoursFreeTrampoline(pHook->pTrampolineToFree); pHook->pTrampolineToFree = NULL; } } static HRESULT MHDetoursTransactionBegin() { DETOUR_TRANSACTION_OPTIONS options = { .fSuspendThreads = g_threadFreezeMethod != MH_FREEZE_METHOD_NONE_UNSAFE, }; return SlimDetoursTransactionBeginEx(&options); } static HRESULT MHDetoursAttach(PHOOK_ENTRY pHook) { FreeHookTrampolineIfNeeded(pHook); LPVOID ppOriginal = pHook->ppOriginal ? pHook->ppOriginal : &pHook->pTargetOrTrampoline; return SlimDetoursAttach(ppOriginal, pHook->pDetour); } static HRESULT MHDetoursDetach(PHOOK_ENTRY pHook) { LPVOID ppOriginal = pHook->ppOriginal ? pHook->ppOriginal : &pHook->pTargetOrTrampoline; DETOUR_DETACH_OPTIONS options = { .ppTrampolineToFreeManually = &pHook->pTrampolineToFree, }; return SlimDetoursDetachEx(ppOriginal, pHook->pDetour, &options); } static MH_STATUS CreateHook(ULONG_PTR hookIdent, LPVOID pTarget, LPVOID pDetour, LPVOID *ppOriginal) { MH_STATUS status = MH_OK; if (hookIdent == MH_ALL_IDENTS || pTarget == MH_ALL_HOOKS) { status = MH_ERROR_UNSUPPORTED_FUNCTION; } else if (FindHookEntry(hookIdent, pTarget, 0) != INVALID_HOOK_POS) { status = MH_ERROR_ALREADY_CREATED; } else if (!IsExecutableAddress(pTarget) || !IsExecutableAddress(pDetour)) { status = MH_ERROR_NOT_EXECUTABLE; } else { PHOOK_ENTRY pHook = AddHookEntry(); if (pHook != NULL) { pHook->hookIdent = hookIdent; pHook->pTarget = pTarget; pHook->pDetour = pDetour; if (ppOriginal) { // Check if the ppOriginal pointer was already specified for // other hooks. If so, modify them to use pTargetOrTrampoline. // This fixes a problem with the following questionable code: // // MH_CreateHook(pTarget1, pDetour, &ppOriginal); // // ... // MH_CreateHook(pTarget2, pDetour, &ppOriginal); // // While it's unsupported to have the same ppOriginal pointer // specified more than once, it worked in MinHook, and some // Windhawk mods which call HandleLoadedExplorerPatcher rely on // it. for (UINT i = 0; i < g_hooks.size - 1; ++i) { PHOOK_ENTRY pHookIter = &g_hooks.pItems[i]; if (pHookIter->ppOriginal == ppOriginal) { pHookIter->pTargetOrTrampoline = *pHookIter->ppOriginal; pHookIter->ppOriginal = NULL; } } pHook->pTargetOrTrampoline = NULL; pHook->ppOriginal = ppOriginal; *ppOriginal = pTarget; } else { pHook->pTargetOrTrampoline = pTarget; pHook->ppOriginal = NULL; } pHook->pTrampolineToFree = NULL; pHook->isEnabled = FALSE; pHook->queueEnable = FALSE; pHook->bulkLastError = S_OK; } else { status = MH_ERROR_MEMORY_ALLOC; } } return status; } static MH_STATUS EnableHook(ULONG_PTR hookIdent, LPVOID pTarget, BOOL enable) { MH_STATUS status = MH_OK; HRESULT hr; if (hookIdent == MH_ALL_IDENTS || pTarget == MH_ALL_HOOKS) { UINT pos = FindHookEntryEnabled(hookIdent, pTarget, 0, !enable); if (pos != INVALID_HOOK_POS) { hr = MHDetoursTransactionBegin(); if (SUCCEEDED(hr)) { do { PHOOK_ENTRY pHook = &g_hooks.pItems[pos]; if (enable) { hr = MHDetoursAttach(pHook); } else { hr = MHDetoursDetach(pHook); } pHook->bulkLastError = hr; if (g_bulkContinueOnError) { hr = S_OK; } else if (FAILED(hr)) { break; } pos = FindHookEntryEnabled(hookIdent, pTarget, pos + 1, !enable); } while (pos != INVALID_HOOK_POS); if (SUCCEEDED(hr)) { hr = SlimDetoursTransactionCommit(); if (SUCCEEDED(hr)) { UINT pos = FindHookEntryEnabled(hookIdent, pTarget, 0, !enable); while (pos != INVALID_HOOK_POS) { PHOOK_ENTRY pHook = &g_hooks.pItems[pos]; if (SUCCEEDED(pHook->bulkLastError)) { pHook->isEnabled = enable; pHook->queueEnable = enable; } else if (g_bulkErrorCallback) { g_bulkErrorCallback(pHook->pTarget, pHook->bulkLastError); status = MH_ERROR_PARTIAL_FAILURE; } pos = FindHookEntryEnabled(hookIdent, pTarget, pos + 1, !enable); } } else { status = MH_ERROR_DETOURS_TRANSACTION_COMMIT; } } else { status = MH_ERROR_UNSUPPORTED_FUNCTION; SlimDetoursTransactionAbort(); } } else { status = MH_ERROR_DETOURS_TRANSACTION_BEGIN; } } } else { UINT pos = FindHookEntry(hookIdent, pTarget, 0); if (pos != INVALID_HOOK_POS) { PHOOK_ENTRY pHook = &g_hooks.pItems[pos]; if (pHook->isEnabled != enable) { hr = MHDetoursTransactionBegin(); if (SUCCEEDED(hr)) { if (enable) { hr = MHDetoursAttach(pHook); } else { hr = MHDetoursDetach(pHook); } if (SUCCEEDED(hr)) { hr = SlimDetoursTransactionCommit(); if (SUCCEEDED(hr)) { pHook->isEnabled = enable; pHook->queueEnable = enable; } else { status = MH_ERROR_DETOURS_TRANSACTION_COMMIT; } } else { status = MH_ERROR_UNSUPPORTED_FUNCTION; SlimDetoursTransactionAbort(); } } else { status = MH_ERROR_DETOURS_TRANSACTION_BEGIN; } } else { status = enable ? MH_ERROR_ENABLED : MH_ERROR_DISABLED; } } else { status = MH_ERROR_NOT_CREATED; } } return status; } static void RemoveDisabledHooks(ULONG_PTR hookIdent, LPVOID pTarget) { UINT pos = FindHookEntryEnabled(hookIdent, pTarget, 0, FALSE); while (pos != INVALID_HOOK_POS) { FreeHookTrampolineIfNeeded(&g_hooks.pItems[pos]); DeleteHookEntry(pos); pos = FindHookEntryEnabled(hookIdent, pTarget, pos, FALSE); } } static MH_STATUS QueueHook(ULONG_PTR hookIdent, LPVOID pTarget, BOOL queueEnable) { MH_STATUS status = MH_OK; if (hookIdent == MH_ALL_IDENTS || pTarget == MH_ALL_HOOKS) { UINT pos = FindHookEntry(hookIdent, pTarget, 0); while (pos != INVALID_HOOK_POS) { PHOOK_ENTRY pHook = &g_hooks.pItems[pos]; pHook->queueEnable = queueEnable; pos = FindHookEntry(hookIdent, pTarget, pos + 1); } } else { UINT pos = FindHookEntry(hookIdent, pTarget, 0); if (pos != INVALID_HOOK_POS) { PHOOK_ENTRY pHook = &g_hooks.pItems[pos]; pHook->queueEnable = queueEnable; } else { status = MH_ERROR_NOT_CREATED; } } return status; } static MH_STATUS ApplyQueued(ULONG_PTR hookIdent) { MH_STATUS status = MH_OK; HRESULT hr; UINT pos = FindHookEntryQueued(hookIdent, MH_ALL_HOOKS, 0); if (pos != INVALID_HOOK_POS) { hr = MHDetoursTransactionBegin(); if (SUCCEEDED(hr)) { do { PHOOK_ENTRY pHook = &g_hooks.pItems[pos]; if (pHook->queueEnable) { hr = MHDetoursAttach(pHook); } else { hr = MHDetoursDetach(pHook); } pHook->bulkLastError = hr; if (g_bulkContinueOnError) { hr = S_OK; } else if (FAILED(hr)) { break; } pos = FindHookEntryQueued(hookIdent, MH_ALL_HOOKS, pos + 1); } while (pos != INVALID_HOOK_POS); if (SUCCEEDED(hr)) { hr = SlimDetoursTransactionCommit(); if (SUCCEEDED(hr)) { UINT pos = FindHookEntryQueued(hookIdent, MH_ALL_HOOKS, 0); while (pos != INVALID_HOOK_POS) { PHOOK_ENTRY pHook = &g_hooks.pItems[pos]; if (SUCCEEDED(pHook->bulkLastError)) { pHook->isEnabled = pHook->queueEnable; } else if (g_bulkErrorCallback) { g_bulkErrorCallback(pHook->pTarget, pHook->bulkLastError); status = MH_ERROR_PARTIAL_FAILURE; } pos = FindHookEntryQueued(hookIdent, MH_ALL_HOOKS, pos + 1); } } else { status = MH_ERROR_DETOURS_TRANSACTION_COMMIT; } } else { status = MH_ERROR_UNSUPPORTED_FUNCTION; SlimDetoursTransactionAbort(); } } else { status = MH_ERROR_DETOURS_TRANSACTION_BEGIN; } } return status; } MH_STATUS WINAPI MH_Initialize(VOID) { if (g_initialized) return MH_ERROR_ALREADY_INITIALIZED; InitializeCriticalSection(&g_criticalSection); g_initialized = TRUE; return MH_OK; } MH_STATUS WINAPI MH_Uninitialize(VOID) { if (!g_initialized) return MH_ERROR_NOT_INITIALIZED; EnterCriticalSection(&g_criticalSection); MH_STATUS status = EnableHook(MH_ALL_IDENTS, MH_ALL_HOOKS, FALSE); RemoveDisabledHooks(MH_ALL_IDENTS, MH_ALL_HOOKS); if (status == MH_OK && g_hooks.size > 0) status = MH_ERROR_UNABLE_TO_UNINITIALIZE; if (status != MH_OK) { LeaveCriticalSection(&g_criticalSection); return status; } SlimDetoursUninitialize(); HeapFree(GetProcessHeap(), 0, g_hooks.pItems); g_hooks.pItems = NULL; g_hooks.capacity = 0; g_hooks.size = 0; g_initialized = FALSE; LeaveCriticalSection(&g_criticalSection); DeleteCriticalSection(&g_criticalSection); return MH_OK; } MH_STATUS WINAPI MH_SetThreadFreezeMethod(MH_THREAD_FREEZE_METHOD method) { if (!g_initialized) return MH_ERROR_NOT_INITIALIZED; EnterCriticalSection(&g_criticalSection); g_threadFreezeMethod = method; LeaveCriticalSection(&g_criticalSection); return MH_OK; } MH_STATUS WINAPI MH_SetBulkOperationMode(BOOL continueOnError, MH_ERROR_CALLBACK errorCallback) { if (!g_initialized) return MH_ERROR_NOT_INITIALIZED; EnterCriticalSection(&g_criticalSection); g_bulkContinueOnError = continueOnError; g_bulkErrorCallback = errorCallback; LeaveCriticalSection(&g_criticalSection); return MH_OK; } MH_STATUS WINAPI MH_CreateHook(LPVOID pTarget, LPVOID pDetour, LPVOID *ppOriginal) { return MH_CreateHookEx(MH_DEFAULT_IDENT, pTarget, pDetour, ppOriginal); } MH_STATUS WINAPI MH_CreateHookEx(ULONG_PTR hookIdent, LPVOID pTarget, LPVOID pDetour, LPVOID *ppOriginal) { if (!g_initialized) return MH_ERROR_NOT_INITIALIZED; EnterCriticalSection(&g_criticalSection); MH_STATUS status = CreateHook(hookIdent, pTarget, pDetour, ppOriginal); LeaveCriticalSection(&g_criticalSection); return status; } MH_STATUS WINAPI MH_CreateHookApi( LPCWSTR pszModule, LPCSTR pszProcName, LPVOID pDetour, LPVOID *ppOriginal) { return MH_CreateHookApiEx(pszModule, pszProcName, pDetour, ppOriginal, NULL); } MH_STATUS WINAPI MH_CreateHookApiEx( LPCWSTR pszModule, LPCSTR pszProcName, LPVOID pDetour, LPVOID *ppOriginal, LPVOID *ppTarget) { HMODULE hModule; LPVOID pTarget; hModule = GetModuleHandleW(pszModule); if (hModule == NULL) return MH_ERROR_MODULE_NOT_FOUND; pTarget = (LPVOID)GetProcAddress(hModule, pszProcName); if (pTarget == NULL) return MH_ERROR_FUNCTION_NOT_FOUND; if (ppTarget != NULL) *ppTarget = pTarget; return MH_CreateHook(pTarget, pDetour, ppOriginal); } MH_STATUS WINAPI MH_RemoveHook(LPVOID pTarget) { return MH_RemoveHookEx(MH_DEFAULT_IDENT, pTarget); } MH_STATUS WINAPI MH_RemoveHookEx(ULONG_PTR hookIdent, LPVOID pTarget) { if (!g_initialized) return MH_ERROR_NOT_INITIALIZED; EnterCriticalSection(&g_criticalSection); MH_STATUS status = EnableHook(hookIdent, pTarget, FALSE); if (status == MH_ERROR_DISABLED) status = MH_OK; RemoveDisabledHooks(hookIdent, pTarget); LeaveCriticalSection(&g_criticalSection); return status; } MH_STATUS WINAPI MH_RemoveDisabledHooks() { return MH_RemoveDisabledHooksEx(MH_DEFAULT_IDENT); } MH_STATUS WINAPI MH_RemoveDisabledHooksEx(ULONG_PTR hookIdent) { if (!g_initialized) return MH_ERROR_NOT_INITIALIZED; EnterCriticalSection(&g_criticalSection); RemoveDisabledHooks(hookIdent, MH_ALL_HOOKS); LeaveCriticalSection(&g_criticalSection); return MH_OK; } MH_STATUS WINAPI MH_EnableHook(LPVOID pTarget) { return MH_EnableHookEx(MH_DEFAULT_IDENT, pTarget); } MH_STATUS WINAPI MH_EnableHookEx(ULONG_PTR hookIdent, LPVOID pTarget) { if (!g_initialized) return MH_ERROR_NOT_INITIALIZED; EnterCriticalSection(&g_criticalSection); MH_STATUS status = EnableHook(hookIdent, pTarget, TRUE); LeaveCriticalSection(&g_criticalSection); return status; } MH_STATUS WINAPI MH_DisableHook(LPVOID pTarget) { return MH_DisableHookEx(MH_DEFAULT_IDENT, pTarget); } MH_STATUS WINAPI MH_DisableHookEx(ULONG_PTR hookIdent, LPVOID pTarget) { if (!g_initialized) return MH_ERROR_NOT_INITIALIZED; EnterCriticalSection(&g_criticalSection); MH_STATUS status = EnableHook(hookIdent, pTarget, FALSE); LeaveCriticalSection(&g_criticalSection); return status; } MH_STATUS WINAPI MH_QueueEnableHook(LPVOID pTarget) { return MH_QueueEnableHookEx(MH_DEFAULT_IDENT, pTarget); } MH_STATUS WINAPI MH_QueueEnableHookEx(ULONG_PTR hookIdent, LPVOID pTarget) { if (!g_initialized) return MH_ERROR_NOT_INITIALIZED; EnterCriticalSection(&g_criticalSection); MH_STATUS status = QueueHook(hookIdent, pTarget, TRUE); LeaveCriticalSection(&g_criticalSection); return status; } MH_STATUS WINAPI MH_QueueDisableHook(LPVOID pTarget) { return MH_QueueDisableHookEx(MH_DEFAULT_IDENT, pTarget); } MH_STATUS WINAPI MH_QueueDisableHookEx(ULONG_PTR hookIdent, LPVOID pTarget) { if (!g_initialized) return MH_ERROR_NOT_INITIALIZED; EnterCriticalSection(&g_criticalSection); MH_STATUS status = QueueHook(hookIdent, pTarget, FALSE); LeaveCriticalSection(&g_criticalSection); return status; } MH_STATUS WINAPI MH_ApplyQueued(VOID) { return MH_ApplyQueuedEx(MH_DEFAULT_IDENT); } MH_STATUS WINAPI MH_ApplyQueuedEx(ULONG_PTR hookIdent) { if (!g_initialized) return MH_ERROR_NOT_INITIALIZED; EnterCriticalSection(&g_criticalSection); MH_STATUS status = ApplyQueued(hookIdent); LeaveCriticalSection(&g_criticalSection); return status; } const char *WINAPI MH_StatusToString(MH_STATUS status) { #define MH_ST2STR(x) \ case x: \ return #x switch (status) { MH_ST2STR(MH_OK); MH_ST2STR(MH_ERROR_ALREADY_INITIALIZED); MH_ST2STR(MH_ERROR_NOT_INITIALIZED); MH_ST2STR(MH_ERROR_ALREADY_CREATED); MH_ST2STR(MH_ERROR_NOT_CREATED); MH_ST2STR(MH_ERROR_ENABLED); MH_ST2STR(MH_ERROR_DISABLED); MH_ST2STR(MH_ERROR_NOT_EXECUTABLE); MH_ST2STR(MH_ERROR_DETOURS_TRANSACTION_BEGIN); MH_ST2STR(MH_ERROR_UNSUPPORTED_FUNCTION); MH_ST2STR(MH_ERROR_MEMORY_ALLOC); MH_ST2STR(MH_ERROR_MODULE_NOT_FOUND); MH_ST2STR(MH_ERROR_FUNCTION_NOT_FOUND); MH_ST2STR(MH_ERROR_PARTIAL_FAILURE); } #undef MH_ST2STR return "(unknown)"; } ================================================ FILE: src/windhawk/engine/libraries/MinHook-Detours/MinHook.h ================================================ /* * MinHook - The Minimalistic API Hooking Library for x64/x86 * Copyright (C) 2009-2017 Tsuda Kageyu. * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #pragma once #include // MinHook Error Codes. typedef enum MH_STATUS { // Successful. MH_OK = 0, // MinHook is already initialized. MH_ERROR_ALREADY_INITIALIZED, // MinHook is not initialized yet, or already uninitialized. MH_ERROR_NOT_INITIALIZED, // MinHook can't be uninitialized due to hooks that failed to be removed. MH_ERROR_UNABLE_TO_UNINITIALIZE, // The hook for the specified target function is already created. MH_ERROR_ALREADY_CREATED, // The hook for the specified target function is not created yet. MH_ERROR_NOT_CREATED, // The hook for the specified target function is already enabled. MH_ERROR_ENABLED, // The hook for the specified target function is not enabled yet, or already // disabled. MH_ERROR_DISABLED, // The specified pointer is invalid. It points the address of non-allocated // and/or non-executable region. MH_ERROR_NOT_EXECUTABLE, // Detours failed to begin the hooking transaction. MH_ERROR_DETOURS_TRANSACTION_BEGIN, // Detours failed to commit the hooking transaction. MH_ERROR_DETOURS_TRANSACTION_COMMIT, // The specified target function cannot be hooked. MH_ERROR_UNSUPPORTED_FUNCTION, // Failed to allocate memory. MH_ERROR_MEMORY_ALLOC, // The specified module is not loaded. MH_ERROR_MODULE_NOT_FOUND, // The specified function is not found. MH_ERROR_FUNCTION_NOT_FOUND, // If continueOnError is TRUE (see MH_SetBulkOperationMode), some errors // occurred during a bulk operation. MH_ERROR_PARTIAL_FAILURE, } MH_STATUS; // The method of suspending and resuming threads. typedef enum MH_THREAD_FREEZE_METHOD { // The default Detours method. MH_FREEZE_METHOD_ORIGINAL = 0, // Currently same as MH_FREEZE_METHOD_ORIGINAL. MH_FREEZE_METHOD_FAST_UNDOCUMENTED, // Threads are not suspended and instruction pointer registers are not // adjusted. Don't use this method unless you understand the implications // and know that it's safe. MH_FREEZE_METHOD_NONE_UNSAFE } MH_THREAD_FREEZE_METHOD; typedef void(WINAPI *MH_ERROR_CALLBACK)(LPVOID pTarget, HRESULT detoursResult); // Can be passed as a parameter to MH_EnableHook, MH_DisableHook, // MH_QueueEnableHook or MH_QueueDisableHook. #define MH_ALL_HOOKS NULL #define MH_ALL_IDENTS 0 #define MH_DEFAULT_IDENT 1 #ifdef __cplusplus extern "C" { #endif // Initializes the MinHook library. You must call this function EXACTLY ONCE // at the beginning of your program. MH_STATUS WINAPI MH_Initialize(VOID); // Uninitializes the MinHook library. You must call this function EXACTLY // ONCE at the end of your program. MH_STATUS WINAPI MH_Uninitialize(VOID); // Sets the method of suspending and resuming threads. MH_STATUS WINAPI MH_SetThreadFreezeMethod(MH_THREAD_FREEZE_METHOD method); // Configures the behavior of bulk operations, e.g. when a function is // called with MH_ALL_HOOKS. By default, execution stops at the first error. // This function allows operations to continue on error and optionally // provides a callback to get notified about errors that occurred. MH_STATUS WINAPI MH_SetBulkOperationMode(BOOL continueOnError, MH_ERROR_CALLBACK errorCallback); // Creates a hook for the specified target function, in disabled state. // Parameters: // hookIdent [in] A hook identifier, can be set to different values for // different hooks to hook the same function more than // once. Default value: MH_DEFAULT_IDENT. // pTarget [in] A pointer to the target function, which will be // overridden by the detour function. // pDetour [in] A pointer to the detour function, which will override // the target function. // ppOriginal [out] A pointer to the trampoline function, which will be // used to call the original target function. // This parameter can be NULL. // If not NULL, the pointer must be valid until the hook // is removed, since its value is updated when the hook // is enabled or disabled. Also, for that reason, the // pointer (and not its copy) must be used to call the // original target function. MH_STATUS WINAPI MH_CreateHook(LPVOID pTarget, LPVOID pDetour, LPVOID *ppOriginal); MH_STATUS WINAPI MH_CreateHookEx(ULONG_PTR hookIdent, LPVOID pTarget, LPVOID pDetour, LPVOID *ppOriginal); // Creates a hook for the specified API function, in disabled state. // Parameters: // pszModule [in] A pointer to the loaded module name which contains the // target function. // pszProcName [in] A pointer to the target function name, which will be // overridden by the detour function. // pDetour [in] A pointer to the detour function, which will override // the target function. // ppOriginal [out] A pointer to the trampoline function, which will be // used to call the original target function. // This parameter can be NULL. // Refer to MH_CreateHook for more important details. MH_STATUS WINAPI MH_CreateHookApi( LPCWSTR pszModule, LPCSTR pszProcName, LPVOID pDetour, LPVOID *ppOriginal); // Creates a hook for the specified API function, in disabled state. // Parameters: // pszModule [in] A pointer to the loaded module name which contains the // target function. // pszProcName [in] A pointer to the target function name, which will be // overridden by the detour function. // pDetour [in] A pointer to the detour function, which will override // the target function. // ppOriginal [out] A pointer to the trampoline function, which will be // used to call the original target function. // This parameter can be NULL. // Refer to MH_CreateHook for more important details. // ppTarget [out] A pointer to the target function, which will be used // with other functions. // This parameter can be NULL. MH_STATUS WINAPI MH_CreateHookApiEx( LPCWSTR pszModule, LPCSTR pszProcName, LPVOID pDetour, LPVOID *ppOriginal, LPVOID *ppTarget); // Removes an already created hook. // Parameters: // hookIdent [in] A hook identifier, can be set to different values for // different hooks to hook the same function more than // once. Default value: MH_DEFAULT_IDENT. // pTarget [in] A pointer to the target function. // If this parameter is MH_ALL_HOOKS, all created hooks are // removed in one go. MH_STATUS WINAPI MH_RemoveHook(LPVOID pTarget); MH_STATUS WINAPI MH_RemoveHookEx(ULONG_PTR hookIdent, LPVOID pTarget); // Removes all disabled hooks. // Parameters: // hookIdent [in] A hook identifier, can be set to different values for // different hooks to hook the same function more than // once. Default value: MH_DEFAULT_IDENT. MH_STATUS WINAPI MH_RemoveDisabledHooks(); MH_STATUS WINAPI MH_RemoveDisabledHooksEx(ULONG_PTR hookIdent); // Enables an already created hook. // Parameters: // hookIdent [in] A hook identifier, can be set to different values for // different hooks to hook the same function more than // once. Default value: MH_DEFAULT_IDENT. // pTarget [in] A pointer to the target function. // If this parameter is MH_ALL_HOOKS, all created hooks are // enabled in one go. MH_STATUS WINAPI MH_EnableHook(LPVOID pTarget); MH_STATUS WINAPI MH_EnableHookEx(ULONG_PTR hookIdent, LPVOID pTarget); // Disables an already created hook. // Parameters: // hookIdent [in] A hook identifier, can be set to different values for // different hooks to hook the same function more than // once. Default value: MH_DEFAULT_IDENT. // pTarget [in] A pointer to the target function. // If this parameter is MH_ALL_HOOKS, all created hooks are // disabled in one go. MH_STATUS WINAPI MH_DisableHook(LPVOID pTarget); MH_STATUS WINAPI MH_DisableHookEx(ULONG_PTR hookIdent, LPVOID pTarget); // Queues to enable an already created hook. // Parameters: // hookIdent [in] A hook identifier, can be set to different values for // different hooks to hook the same function more than // once. Default value: MH_DEFAULT_IDENT. // pTarget [in] A pointer to the target function. // If this parameter is MH_ALL_HOOKS, all created hooks are // queued to be enabled. MH_STATUS WINAPI MH_QueueEnableHook(LPVOID pTarget); MH_STATUS WINAPI MH_QueueEnableHookEx(ULONG_PTR hookIdent, LPVOID pTarget); // Queues to disable an already created hook. // Parameters: // hookIdent [in] A hook identifier, can be set to different values for // different hooks to hook the same function more than // once. Default value: MH_DEFAULT_IDENT. // pTarget [in] A pointer to the target function. // If this parameter is MH_ALL_HOOKS, all created hooks are // queued to be disabled. MH_STATUS WINAPI MH_QueueDisableHook(LPVOID pTarget); MH_STATUS WINAPI MH_QueueDisableHookEx(ULONG_PTR hookIdent, LPVOID pTarget); // Applies all queued changes in one go. // hookIdent [in] A hook identifier, can be set to different values for // different hooks to hook the same function more than // once. Default value: MH_DEFAULT_IDENT. MH_STATUS WINAPI MH_ApplyQueued(VOID); MH_STATUS WINAPI MH_ApplyQueuedEx(ULONG_PTR hookIdent); // Translates the MH_STATUS to its name as a string. const char *WINAPI MH_StatusToString(MH_STATUS status); #ifdef __cplusplus } #endif ================================================ FILE: src/windhawk/engine/libraries/MinHook-Detours/SlimDetours/.editorconfig ================================================ root = true [*.{inc,mac,asm,s}] charset = utf-8 # Visual Studio generated .editorconfig file with C++ settings. [*.{c,c++,cc,cpp,cppm,cxx,h,h++,hh,hpp,hxx,inl,ipp,ixx,tlh,tli}] charset = utf-8-bom indent_style = spaces indent_size = 4 tab_width= 4 end_of_line = crlf insert_final_newline = true max_line_length = 120 # Visual C++ Code Style settings cpp_generate_documentation_comments = xml # Visual C++ Formatting settings cpp_indent_braces = false cpp_indent_multi_line_relative_to = innermost_parenthesis cpp_indent_within_parentheses = align_to_parenthesis cpp_indent_preserve_within_parentheses = false cpp_indent_case_contents = true cpp_indent_case_labels = true cpp_indent_case_contents_when_block = false cpp_indent_lambda_braces_when_parameter = false cpp_indent_goto_labels = leftmost_column cpp_indent_preprocessor = leftmost_column cpp_indent_access_specifiers = false cpp_indent_namespace_contents = true cpp_indent_preserve_comments = true cpp_new_line_before_open_brace_namespace = new_line cpp_new_line_before_open_brace_type = new_line cpp_new_line_before_open_brace_function = new_line cpp_new_line_before_open_brace_block = new_line cpp_new_line_before_open_brace_lambda = new_line cpp_new_line_scope_braces_on_separate_lines = true cpp_new_line_close_brace_same_line_empty_type = true cpp_new_line_close_brace_same_line_empty_function = true cpp_new_line_before_catch = false cpp_new_line_before_else = false cpp_new_line_before_while_in_do_while = false cpp_space_before_function_open_parenthesis = remove cpp_space_within_parameter_list_parentheses = false cpp_space_between_empty_parameter_list_parentheses = false cpp_space_after_keywords_in_control_flow_statements = true cpp_space_within_control_flow_statement_parentheses = false cpp_space_before_lambda_open_parenthesis = false cpp_space_within_cast_parentheses = false cpp_space_after_cast_close_parenthesis = false cpp_space_within_expression_parentheses = false cpp_space_before_block_open_brace = true cpp_space_between_empty_braces = false cpp_space_before_initializer_list_open_brace = false cpp_space_within_initializer_list_braces = true cpp_space_preserve_in_initializer_list = true cpp_space_before_open_square_bracket = false cpp_space_within_square_brackets = false cpp_space_before_empty_square_brackets = false cpp_space_between_empty_square_brackets = false cpp_space_group_square_brackets = true cpp_space_within_lambda_brackets = false cpp_space_between_empty_lambda_brackets = false cpp_space_before_comma = false cpp_space_after_comma = true cpp_space_remove_around_member_operators = true cpp_space_before_inheritance_colon = true cpp_space_before_constructor_colon = true cpp_space_remove_before_semicolon = true cpp_space_after_semicolon = true cpp_space_remove_around_unary_operator = true cpp_space_around_binary_operator = insert cpp_space_around_assignment_operator = insert cpp_space_pointer_reference_alignment = ignore cpp_space_around_ternary_operator = insert cpp_use_unreal_engine_macro_formatting = false cpp_wrap_preserve_blocks = never # Visual C++ Include Cleanup settings cpp_include_cleanup_add_missing_error_tag_type = suggestion cpp_include_cleanup_remove_unused_error_tag_type = dimmed cpp_include_cleanup_optimize_unused_error_tag_type = suggestion cpp_include_cleanup_sort_after_edits = false cpp_sort_includes_error_tag_type = none cpp_sort_includes_priority_case_sensitive = false cpp_sort_includes_priority_style = quoted cpp_includes_style = default cpp_includes_use_forward_slash = true ================================================ FILE: src/windhawk/engine/libraries/MinHook-Detours/SlimDetours/Disassembler.c ================================================ /* * KNSoft.SlimDetours (https://github.com/KNSoft/KNSoft.SlimDetours) Disassembler * Copyright (c) KNSoft.org (https://github.com/KNSoft). All rights reserved. * Licensed under the MIT license. * * Source base on Microsoft Detours: * * Microsoft Research Detours Package, Version 4.0.1 * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT license. */ #include "SlimDetours.inl" #undef ASSERT #define ASSERT(x) ////////////////////////////////////////////////////////////////////////////// // // Function: // SlimDetoursCopyInstruction(PVOID pDst, // PVOID *ppDstPool // PVOID pSrc, // PVOID *ppTarget, // LONG *plExtra) // Purpose: // Copy a single instruction from pSrc to pDst. // // Arguments: // pDst: // Destination address for the instruction. May be NULL in which // case SlimDetoursCopyInstruction is used to measure an instruction. // If not NULL then the source instruction is copied to the // destination instruction and any relative arguments are adjusted. // ppDstPool: // Destination address for the end of the constant pool. The // constant pool works backwards toward pDst. All memory between // pDst and *ppDstPool must be available for use by this function. // ppDstPool may be NULL if pDst is NULL. // pSrc: // Source address of the instruction. // ppTarget: // Out parameter for any target instruction address pointed to by // the instruction. For example, a branch or a jump insruction has // a target, but a load or store instruction doesn't. A target is // another instruction that may be executed as a result of this // instruction. ppTarget may be NULL. // plExtra: // Out parameter for the number of extra bytes needed by the // instruction to reach the target. For example, lExtra = 3 if the // instruction had an 8-bit relative offset, but needs a 32-bit // relative offset. // // Returns: // Returns the address of the next instruction (following in the source) // instruction. By subtracting pSrc from the return value, the caller // can determinte the size of the instruction copied. // // Comments: // By following the pTarget, the caller can follow alternate // instruction streams. However, it is not always possible to determine // the target based on static analysis. For example, the destination of // a jump relative to a register cannot be determined from just the // instruction stream. The output value, pTarget, can have any of the // following outputs: // DETOUR_INSTRUCTION_TARGET_NONE: // The instruction has no targets. // DETOUR_INSTRUCTION_TARGET_DYNAMIC: // The instruction has a non-deterministic (dynamic) target. // (i.e. the jump is to an address held in a register.) // Address: The instruction has the specified target. // // When copying instructions, SlimDetoursCopyInstruction insures that any // targets remain constant. It does so by adjusting any IP relative // offsets. // //////////////////////////////////////////////////// X86 and X64 Disassembler. // // Includes full support for all x86 chips prior to the Pentium III, and some newer stuff. // #if defined(_AMD64_) || defined(_X86_) typedef struct _DETOUR_DISASM { BOOL bOperandOverride; BOOL bAddressOverride; BOOL bRaxOverride; // AMD64 only BOOL bVex; BOOL bEvex; BOOL bF2; BOOL bF3; // x86 only BYTE nSegmentOverride; PBYTE* ppbTarget; LONG* plExtra; LONG lScratchExtra; PBYTE pbScratchTarget; BYTE rbScratchDst[64]; // matches or exceeds rbCode } DETOUR_DISASM, *PDETOUR_DISASM; static VOID detour_disasm_init( _Out_ PDETOUR_DISASM pDisasm, _Out_opt_ PBYTE* ppbTarget, _Out_opt_ LONG* plExtra) { pDisasm->bOperandOverride = FALSE; pDisasm->bAddressOverride = FALSE; pDisasm->bRaxOverride = FALSE; pDisasm->bF2 = FALSE; pDisasm->bF3 = FALSE; pDisasm->bVex = FALSE; pDisasm->bEvex = FALSE; pDisasm->ppbTarget = ppbTarget ? ppbTarget : &pDisasm->pbScratchTarget; pDisasm->plExtra = plExtra ? plExtra : &pDisasm->lScratchExtra; *pDisasm->ppbTarget = (PBYTE)DETOUR_INSTRUCTION_TARGET_NONE; *pDisasm->plExtra = 0; } typedef const struct _COPYENTRY *REFCOPYENTRY; typedef PBYTE(*COPYFUNC)( _In_ PDETOUR_DISASM pDisasm, _In_opt_ REFCOPYENTRY pEntry, _In_ PBYTE pbDst, _In_ PBYTE pbSrc); // nFlagBits flags. enum { DYNAMIC = 0x1u, ADDRESS = 0x2u, NOENLARGE = 0x4u, RAX = 0x8u, }; // ModR/M Flags enum { SIB = 0x10u, RIP = 0x20u, NOTSIB = 0x0fu, }; typedef struct _COPYENTRY { // Many of these fields are often ignored. See ENTRY_DataIgnored. ULONG nFixedSize : 4; // Fixed size of opcode ULONG nFixedSize16 : 4; // Fixed size when 16 bit operand ULONG nModOffset : 4; // Offset to mod/rm byte (0=none) ULONG nRelOffset : 4; // Offset to relative target. ULONG nFlagBits : 4; // Flags for DYNAMIC, etc. COPYFUNC pfCopy; // Function pointer. } COPYENTRY, *PCOPYENTRY; static PBYTE CopyBytes(_In_ PDETOUR_DISASM pDisasm, _In_opt_ REFCOPYENTRY pEntry, _In_ PBYTE pbDst, _In_ PBYTE pbSrc); static PBYTE CopyBytesPrefix(_In_ PDETOUR_DISASM pDisasm, _In_opt_ REFCOPYENTRY pEntry, _In_ PBYTE pbDst, _In_ PBYTE pbSrc); static PBYTE CopyBytesSegment(_In_ PDETOUR_DISASM pDisasm, _In_opt_ REFCOPYENTRY pEntry, _In_ PBYTE pbDst, _In_ PBYTE pbSrc); static PBYTE CopyBytesRax(_In_ PDETOUR_DISASM pDisasm, _In_opt_ REFCOPYENTRY pEntry, _In_ PBYTE pbDst, _In_ PBYTE pbSrc); static PBYTE CopyBytesJump(_In_ PDETOUR_DISASM pDisasm, _In_opt_ REFCOPYENTRY pEntry, _In_ PBYTE pbDst, _In_ PBYTE pbSrc); static PBYTE Invalid(_In_ PDETOUR_DISASM pDisasm, _In_opt_ REFCOPYENTRY pEntry, _In_ PBYTE pbDst, _In_ PBYTE pbSrc); static PBYTE Copy0F(_In_ PDETOUR_DISASM pDisasm, _In_opt_ REFCOPYENTRY pEntry, _In_ PBYTE pbDst, _In_ PBYTE pbSrc); static PBYTE Copy0F78(_In_ PDETOUR_DISASM pDisasm, _In_opt_ REFCOPYENTRY pEntry, _In_ PBYTE pbDst, _In_ PBYTE pbSrc); static PBYTE Copy0F00(_In_ PDETOUR_DISASM pDisasm, _In_opt_ REFCOPYENTRY pEntry, _In_ PBYTE pbDst, _In_ PBYTE pbSrc); static PBYTE Copy0FB8(_In_ PDETOUR_DISASM pDisasm, _In_opt_ REFCOPYENTRY pEntry, _In_ PBYTE pbDst, _In_ PBYTE pbSrc); static PBYTE Copy66(_In_ PDETOUR_DISASM pDisasm, _In_opt_ REFCOPYENTRY pEntry, _In_ PBYTE pbDst, _In_ PBYTE pbSrc); static PBYTE Copy67(_In_ PDETOUR_DISASM pDisasm, _In_opt_ REFCOPYENTRY pEntry, _In_ PBYTE pbDst, _In_ PBYTE pbSrc); static PBYTE CopyF2(_In_ PDETOUR_DISASM pDisasm, _In_opt_ REFCOPYENTRY pEntry, _In_ PBYTE pbDst, _In_ PBYTE pbSrc); static PBYTE CopyF3(_In_ PDETOUR_DISASM pDisasm, _In_opt_ REFCOPYENTRY pEntry, _In_ PBYTE pbDst, _In_ PBYTE pbSrc); static PBYTE CopyF6(_In_ PDETOUR_DISASM pDisasm, _In_opt_ REFCOPYENTRY pEntry, _In_ PBYTE pbDst, _In_ PBYTE pbSrc); static PBYTE CopyF7(_In_ PDETOUR_DISASM pDisasm, _In_opt_ REFCOPYENTRY pEntry, _In_ PBYTE pbDst, _In_ PBYTE pbSrc); static PBYTE CopyFF(_In_ PDETOUR_DISASM pDisasm, _In_opt_ REFCOPYENTRY pEntry, _In_ PBYTE pbDst, _In_ PBYTE pbSrc); static PBYTE CopyVex3(_In_ PDETOUR_DISASM pDisasm, _In_opt_ REFCOPYENTRY pEntry, _In_ PBYTE pbDst, _In_ PBYTE pbSrc); static PBYTE CopyVex2(_In_ PDETOUR_DISASM pDisasm, _In_opt_ REFCOPYENTRY pEntry, _In_ PBYTE pbDst, _In_ PBYTE pbSrc); static PBYTE CopyEvex(_In_ PDETOUR_DISASM pDisasm, _In_opt_ REFCOPYENTRY pEntry, _In_ PBYTE pbDst, _In_ PBYTE pbSrc); static PBYTE CopyXop(_In_ PDETOUR_DISASM pDisasm, _In_opt_ REFCOPYENTRY pEntry, _In_ PBYTE pbDst, _In_ PBYTE pbSrc); ///////////////////////////////////////////////////////// Disassembler Tables. // static const BYTE g_rbModRm[] = { 0,0,0,0, SIB | 1,RIP | 4,0,0, 0,0,0,0, SIB | 1,RIP | 4,0,0, // 0x 0,0,0,0, SIB | 1,RIP | 4,0,0, 0,0,0,0, SIB | 1,RIP | 4,0,0, // 1x 0,0,0,0, SIB | 1,RIP | 4,0,0, 0,0,0,0, SIB | 1,RIP | 4,0,0, // 2x 0,0,0,0, SIB | 1,RIP | 4,0,0, 0,0,0,0, SIB | 1,RIP | 4,0,0, // 3x 1,1,1,1, 2,1,1,1, 1,1,1,1, 2,1,1,1, // 4x 1,1,1,1, 2,1,1,1, 1,1,1,1, 2,1,1,1, // 5x 1,1,1,1, 2,1,1,1, 1,1,1,1, 2,1,1,1, // 6x 1,1,1,1, 2,1,1,1, 1,1,1,1, 2,1,1,1, // 7x 4,4,4,4, 5,4,4,4, 4,4,4,4, 5,4,4,4, // 8x 4,4,4,4, 5,4,4,4, 4,4,4,4, 5,4,4,4, // 9x 4,4,4,4, 5,4,4,4, 4,4,4,4, 5,4,4,4, // Ax 4,4,4,4, 5,4,4,4, 4,4,4,4, 5,4,4,4, // Bx 0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0, // Cx 0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0, // Dx 0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0, // Ex 0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0 // Fx }; enum { eENTRY_CopyBytes1 = 0, eENTRY_CopyBytes1Address, eENTRY_CopyBytes1Dynamic, eENTRY_CopyBytes2, eENTRY_CopyBytes2Jump, eENTRY_CopyBytes2CantJump, eENTRY_CopyBytes2Dynamic, eENTRY_CopyBytes3, eENTRY_CopyBytes3Dynamic, eENTRY_CopyBytes3Or5, eENTRY_CopyBytes3Or5Dynamic, eENTRY_CopyBytes3Or5Rax, eENTRY_CopyBytes3Or5Target, eENTRY_CopyBytes4, eENTRY_CopyBytes5, eENTRY_CopyBytes5Or7Dynamic, eENTRY_CopyBytes7, eENTRY_CopyBytes2Mod, eENTRY_CopyBytes2ModDynamic, eENTRY_CopyBytes2Mod1, eENTRY_CopyBytes2ModOperand, eENTRY_CopyBytes3Mod, eENTRY_CopyBytes3Mod1, eENTRY_CopyBytesPrefix, eENTRY_CopyBytesSegment, eENTRY_CopyBytesRax, eENTRY_CopyF2, eENTRY_CopyF3, eENTRY_Copy0F, eENTRY_Copy0F78, eENTRY_Copy0F00, eENTRY_Copy0FB8, eENTRY_Copy66, eENTRY_Copy67, eENTRY_CopyF6, eENTRY_CopyF7, eENTRY_CopyFF, eENTRY_CopyVex2, eENTRY_CopyVex3, eENTRY_CopyEvex, eENTRY_CopyXop, eENTRY_CopyBytesXop, eENTRY_CopyBytesXop1, eENTRY_CopyBytesXop4, eENTRY_Invalid }; // These macros define common uses of nFixedSize, nFixedSize16, nModOffset, nRelOffset, nFlagBits, pfCopy. #define ENTRY_DataIgnored 0, 0, 0, 0, 0, static const COPYENTRY g_rceCopyMap[] = { /* eENTRY_CopyBytes1 */ { 1, 1, 0, 0, 0, CopyBytes }, #if defined(_AMD64_) /* eENTRY_CopyBytes1Address */ { 9, 5, 0, 0, ADDRESS, CopyBytes }, #else /* eENTRY_CopyBytes1Address */ { 5, 3, 0, 0, ADDRESS, CopyBytes }, #endif /* eENTRY_CopyBytes1Dynamic */ { 1, 1, 0, 0, DYNAMIC, CopyBytes }, /* eENTRY_CopyBytes2 */ { 2, 2, 0, 0, 0, CopyBytes }, /* eENTRY_CopyBytes2Jump */ { ENTRY_DataIgnored CopyBytesJump }, /* eENTRY_CopyBytes2CantJump */ { 2, 2, 0, 1, NOENLARGE, CopyBytes }, /* eENTRY_CopyBytes2Dynamic */ { 2, 2, 0, 0, DYNAMIC, CopyBytes }, /* eENTRY_CopyBytes3 */ { 3, 3, 0, 0, 0, CopyBytes }, /* eENTRY_CopyBytes3Dynamic */ { 3, 3, 0, 0, DYNAMIC, CopyBytes }, /* eENTRY_CopyBytes3Or5 */ { 5, 3, 0, 0, 0, CopyBytes }, /* eENTRY_CopyBytes3Or5Dynamic */ { 5, 3, 0, 0, DYNAMIC, CopyBytes }, // x86 only #if defined(_AMD64_) /* eENTRY_CopyBytes3Or5Rax */ { 5, 3, 0, 0, RAX, CopyBytes }, /* eENTRY_CopyBytes3Or5Target */ { 5, 5, 0, 1, 0, CopyBytes }, #else /* eENTRY_CopyBytes3Or5Rax */ { 5, 3, 0, 0, 0, CopyBytes }, /* eENTRY_CopyBytes3Or5Target */ { 5, 3, 0, 1, 0, CopyBytes }, #endif /* eENTRY_CopyBytes4 */ { 4, 4, 0, 0, 0, CopyBytes }, /* eENTRY_CopyBytes5 */ { 5, 5, 0, 0, 0, CopyBytes }, /* eENTRY_CopyBytes5Or7Dynamic */ { 7, 5, 0, 0, DYNAMIC, CopyBytes }, /* eENTRY_CopyBytes7 */ { 7, 7, 0, 0, 0, CopyBytes }, /* eENTRY_CopyBytes2Mod */ { 2, 2, 1, 0, 0, CopyBytes }, /* eENTRY_CopyBytes2ModDynamic */ { 2, 2, 1, 0, DYNAMIC, CopyBytes }, /* eENTRY_CopyBytes2Mod1 */ { 3, 3, 1, 0, 0, CopyBytes }, /* eENTRY_CopyBytes2ModOperand */ { 6, 4, 1, 0, 0, CopyBytes }, /* eENTRY_CopyBytes3Mod */ { 3, 3, 2, 0, 0, CopyBytes }, // SSE3 0F 38 opcode modrm /* eENTRY_CopyBytes3Mod1 */ { 4, 4, 2, 0, 0, CopyBytes }, // SSE3 0F 3A opcode modrm .. imm8 /* eENTRY_CopyBytesPrefix */ { ENTRY_DataIgnored CopyBytesPrefix }, /* eENTRY_CopyBytesSegment */ { ENTRY_DataIgnored CopyBytesSegment }, /* eENTRY_CopyBytesRax */ { ENTRY_DataIgnored CopyBytesRax }, /* eENTRY_CopyF2 */ { ENTRY_DataIgnored CopyF2 }, /* eENTRY_CopyF3 */ { ENTRY_DataIgnored CopyF3 }, // 32bit x86 only /* eENTRY_Copy0F */ { ENTRY_DataIgnored Copy0F }, /* eENTRY_Copy0F78 */ { ENTRY_DataIgnored Copy0F78 }, /* eENTRY_Copy0F00 */ { ENTRY_DataIgnored Copy0F00 }, // 32bit x86 only /* eENTRY_Copy0FB8 */ { ENTRY_DataIgnored Copy0FB8 }, // 32bit x86 only /* eENTRY_Copy66 */ { ENTRY_DataIgnored Copy66 }, /* eENTRY_Copy67 */ { ENTRY_DataIgnored Copy67 }, /* eENTRY_CopyF6 */ { ENTRY_DataIgnored CopyF6 }, /* eENTRY_CopyF7 */ { ENTRY_DataIgnored CopyF7 }, /* eENTRY_CopyFF */ { ENTRY_DataIgnored CopyFF }, /* eENTRY_CopyVex2 */ { ENTRY_DataIgnored CopyVex2 }, /* eENTRY_CopyVex3 */ { ENTRY_DataIgnored CopyVex3 }, /* eENTRY_CopyEvex */ { ENTRY_DataIgnored CopyEvex }, // 62, 3 byte payload, then normal with implied prefixes like vex /* eENTRY_CopyXop */ { ENTRY_DataIgnored CopyXop }, // 0x8F ... POP /0 or AMD XOP /* eENTRY_CopyBytesXop */ { 5, 5, 4, 0, 0, CopyBytes }, // 0x8F xop1 xop2 opcode modrm /* eENTRY_CopyBytesXop1 */ { 6, 6, 4, 0, 0, CopyBytes }, // 0x8F xop1 xop2 opcode modrm ... imm8 /* eENTRY_CopyBytesXop4 */ { 9, 9, 4, 0, 0, CopyBytes }, // 0x8F xop1 xop2 opcode modrm ... imm32 /* eENTRY_Invalid */ { ENTRY_DataIgnored Invalid } }; static const BYTE g_rceCopyTable[] = { /* 00 */ eENTRY_CopyBytes2Mod, // ADD /r /* 01 */ eENTRY_CopyBytes2Mod, // ADD /r /* 02 */ eENTRY_CopyBytes2Mod, // ADD /r /* 03 */ eENTRY_CopyBytes2Mod, // ADD /r /* 04 */ eENTRY_CopyBytes2, // ADD ib /* 05 */ eENTRY_CopyBytes3Or5, // ADD iw #if defined(_AMD64_) /* 06 */ eENTRY_Invalid, // Invalid /* 07 */ eENTRY_Invalid, // Invalid #else /* 06 */ eENTRY_CopyBytes1, // PUSH /* 07 */ eENTRY_CopyBytes1, // POP #endif /* 08 */ eENTRY_CopyBytes2Mod, // OR /r /* 09 */ eENTRY_CopyBytes2Mod, // OR /r /* 0A */ eENTRY_CopyBytes2Mod, // OR /r /* 0B */ eENTRY_CopyBytes2Mod, // OR /r /* 0C */ eENTRY_CopyBytes2, // OR ib /* 0D */ eENTRY_CopyBytes3Or5, // OR iw #if defined(_AMD64_) /* 0E */ eENTRY_Invalid, // Invalid #else /* 0E */ eENTRY_CopyBytes1, // PUSH #endif /* 0F */ eENTRY_Copy0F, // Extension Ops /* 10 */ eENTRY_CopyBytes2Mod, // ADC /r /* 11 */ eENTRY_CopyBytes2Mod, // ADC /r /* 12 */ eENTRY_CopyBytes2Mod, // ADC /r /* 13 */ eENTRY_CopyBytes2Mod, // ADC /r /* 14 */ eENTRY_CopyBytes2, // ADC ib /* 15 */ eENTRY_CopyBytes3Or5, // ADC id #if defined(_AMD64_) /* 16 */ eENTRY_Invalid, // Invalid /* 17 */ eENTRY_Invalid, // Invalid #else /* 16 */ eENTRY_CopyBytes1, // PUSH /* 17 */ eENTRY_CopyBytes1, // POP #endif /* 18 */ eENTRY_CopyBytes2Mod, // SBB /r /* 19 */ eENTRY_CopyBytes2Mod, // SBB /r /* 1A */ eENTRY_CopyBytes2Mod, // SBB /r /* 1B */ eENTRY_CopyBytes2Mod, // SBB /r /* 1C */ eENTRY_CopyBytes2, // SBB ib /* 1D */ eENTRY_CopyBytes3Or5, // SBB id #if defined(_AMD64_) /* 1E */ eENTRY_Invalid, // Invalid /* 1F */ eENTRY_Invalid, // Invalid #else /* 1E */ eENTRY_CopyBytes1, // PUSH /* 1F */ eENTRY_CopyBytes1, // POP #endif /* 20 */ eENTRY_CopyBytes2Mod, // AND /r /* 21 */ eENTRY_CopyBytes2Mod, // AND /r /* 22 */ eENTRY_CopyBytes2Mod, // AND /r /* 23 */ eENTRY_CopyBytes2Mod, // AND /r /* 24 */ eENTRY_CopyBytes2, // AND ib /* 25 */ eENTRY_CopyBytes3Or5, // AND id /* 26 */ eENTRY_CopyBytesSegment, // ES prefix #if defined(_AMD64_) /* 27 */ eENTRY_Invalid, // Invalid #else /* 27 */ eENTRY_CopyBytes1, // DAA #endif /* 28 */ eENTRY_CopyBytes2Mod, // SUB /r /* 29 */ eENTRY_CopyBytes2Mod, // SUB /r /* 2A */ eENTRY_CopyBytes2Mod, // SUB /r /* 2B */ eENTRY_CopyBytes2Mod, // SUB /r /* 2C */ eENTRY_CopyBytes2, // SUB ib /* 2D */ eENTRY_CopyBytes3Or5, // SUB id /* 2E */ eENTRY_CopyBytesSegment, // CS prefix #if defined(_AMD64_) /* 2F */ eENTRY_Invalid, // Invalid #else /* 2F */ eENTRY_CopyBytes1, // DAS #endif /* 30 */ eENTRY_CopyBytes2Mod, // XOR /r /* 31 */ eENTRY_CopyBytes2Mod, // XOR /r /* 32 */ eENTRY_CopyBytes2Mod, // XOR /r /* 33 */ eENTRY_CopyBytes2Mod, // XOR /r /* 34 */ eENTRY_CopyBytes2, // XOR ib /* 35 */ eENTRY_CopyBytes3Or5, // XOR id /* 36 */ eENTRY_CopyBytesSegment, // SS prefix #if defined(_AMD64_) /* 37 */ eENTRY_Invalid, // Invalid #else /* 37 */ eENTRY_CopyBytes1, // AAA #endif /* 38 */ eENTRY_CopyBytes2Mod, // CMP /r /* 39 */ eENTRY_CopyBytes2Mod, // CMP /r /* 3A */ eENTRY_CopyBytes2Mod, // CMP /r /* 3B */ eENTRY_CopyBytes2Mod, // CMP /r /* 3C */ eENTRY_CopyBytes2, // CMP ib /* 3D */ eENTRY_CopyBytes3Or5, // CMP id /* 3E */ eENTRY_CopyBytesSegment, // DS prefix #if defined(_AMD64_) /* 3F */ eENTRY_Invalid, // Invalid #else /* 3F */ eENTRY_CopyBytes1, // AAS #endif #if defined(_AMD64_) // For Rax Prefix /* 40 */ eENTRY_CopyBytesRax, // Rax /* 41 */ eENTRY_CopyBytesRax, // Rax /* 42 */ eENTRY_CopyBytesRax, // Rax /* 43 */ eENTRY_CopyBytesRax, // Rax /* 44 */ eENTRY_CopyBytesRax, // Rax /* 45 */ eENTRY_CopyBytesRax, // Rax /* 46 */ eENTRY_CopyBytesRax, // Rax /* 47 */ eENTRY_CopyBytesRax, // Rax /* 48 */ eENTRY_CopyBytesRax, // Rax /* 49 */ eENTRY_CopyBytesRax, // Rax /* 4A */ eENTRY_CopyBytesRax, // Rax /* 4B */ eENTRY_CopyBytesRax, // Rax /* 4C */ eENTRY_CopyBytesRax, // Rax /* 4D */ eENTRY_CopyBytesRax, // Rax /* 4E */ eENTRY_CopyBytesRax, // Rax /* 4F */ eENTRY_CopyBytesRax, // Rax #else /* 40 */ eENTRY_CopyBytes1, // INC /* 41 */ eENTRY_CopyBytes1, // INC /* 42 */ eENTRY_CopyBytes1, // INC /* 43 */ eENTRY_CopyBytes1, // INC /* 44 */ eENTRY_CopyBytes1, // INC /* 45 */ eENTRY_CopyBytes1, // INC /* 46 */ eENTRY_CopyBytes1, // INC /* 47 */ eENTRY_CopyBytes1, // INC /* 48 */ eENTRY_CopyBytes1, // DEC /* 49 */ eENTRY_CopyBytes1, // DEC /* 4A */ eENTRY_CopyBytes1, // DEC /* 4B */ eENTRY_CopyBytes1, // DEC /* 4C */ eENTRY_CopyBytes1, // DEC /* 4D */ eENTRY_CopyBytes1, // DEC /* 4E */ eENTRY_CopyBytes1, // DEC /* 4F */ eENTRY_CopyBytes1, // DEC #endif /* 50 */ eENTRY_CopyBytes1, // PUSH /* 51 */ eENTRY_CopyBytes1, // PUSH /* 52 */ eENTRY_CopyBytes1, // PUSH /* 53 */ eENTRY_CopyBytes1, // PUSH /* 54 */ eENTRY_CopyBytes1, // PUSH /* 55 */ eENTRY_CopyBytes1, // PUSH /* 56 */ eENTRY_CopyBytes1, // PUSH /* 57 */ eENTRY_CopyBytes1, // PUSH /* 58 */ eENTRY_CopyBytes1, // POP /* 59 */ eENTRY_CopyBytes1, // POP /* 5A */ eENTRY_CopyBytes1, // POP /* 5B */ eENTRY_CopyBytes1, // POP /* 5C */ eENTRY_CopyBytes1, // POP /* 5D */ eENTRY_CopyBytes1, // POP /* 5E */ eENTRY_CopyBytes1, // POP /* 5F */ eENTRY_CopyBytes1, // POP #if defined(_AMD64_) /* 60 */ eENTRY_Invalid, // Invalid /* 61 */ eENTRY_Invalid, // Invalid /* 62 */ eENTRY_CopyEvex, // EVEX / AVX512 #else /* 60 */ eENTRY_CopyBytes1, // PUSHAD /* 61 */ eENTRY_CopyBytes1, // POPAD /* 62 */ eENTRY_CopyEvex, // BOUND /r and EVEX / AVX512 #endif /* 63 */ eENTRY_CopyBytes2Mod, // 32bit ARPL /r, 64bit MOVSXD /* 64 */ eENTRY_CopyBytesSegment, // FS prefix /* 65 */ eENTRY_CopyBytesSegment, // GS prefix /* 66 */ eENTRY_Copy66, // Operand Prefix /* 67 */ eENTRY_Copy67, // Address Prefix /* 68 */ eENTRY_CopyBytes3Or5, // PUSH /* 69 */ eENTRY_CopyBytes2ModOperand, // IMUL /r iz /* 6A */ eENTRY_CopyBytes2, // PUSH /* 6B */ eENTRY_CopyBytes2Mod1, // IMUL /r ib /* 6C */ eENTRY_CopyBytes1, // INS /* 6D */ eENTRY_CopyBytes1, // INS /* 6E */ eENTRY_CopyBytes1, // OUTS/OUTSB /* 6F */ eENTRY_CopyBytes1, // OUTS/OUTSW /* 70 */ eENTRY_CopyBytes2Jump, // JO // 0f80 /* 71 */ eENTRY_CopyBytes2Jump, // JNO // 0f81 /* 72 */ eENTRY_CopyBytes2Jump, // JB/JC/JNAE // 0f82 /* 73 */ eENTRY_CopyBytes2Jump, // JAE/JNB/JNC // 0f83 /* 74 */ eENTRY_CopyBytes2Jump, // JE/JZ // 0f84 /* 75 */ eENTRY_CopyBytes2Jump, // JNE/JNZ // 0f85 /* 76 */ eENTRY_CopyBytes2Jump, // JBE/JNA // 0f86 /* 77 */ eENTRY_CopyBytes2Jump, // JA/JNBE // 0f87 /* 78 */ eENTRY_CopyBytes2Jump, // JS // 0f88 /* 79 */ eENTRY_CopyBytes2Jump, // JNS // 0f89 /* 7A */ eENTRY_CopyBytes2Jump, // JP/JPE // 0f8a /* 7B */ eENTRY_CopyBytes2Jump, // JNP/JPO // 0f8b /* 7C */ eENTRY_CopyBytes2Jump, // JL/JNGE // 0f8c /* 7D */ eENTRY_CopyBytes2Jump, // JGE/JNL // 0f8d /* 7E */ eENTRY_CopyBytes2Jump, // JLE/JNG // 0f8e /* 7F */ eENTRY_CopyBytes2Jump, // JG/JNLE // 0f8f /* 80 */ eENTRY_CopyBytes2Mod1, // ADD/0 OR/1 ADC/2 SBB/3 AND/4 SUB/5 XOR/6 CMP/7 byte reg, immediate byte /* 81 */ eENTRY_CopyBytes2ModOperand, // ADD/0 OR/1 ADC/2 SBB/3 AND/4 SUB/5 XOR/6 CMP/7 byte reg, immediate word or dword #if defined(_AMD64_) /* 82 */ eENTRY_Invalid, // Invalid #else /* 82 */ eENTRY_CopyBytes2Mod1, // MOV al,x #endif /* 83 */ eENTRY_CopyBytes2Mod1, // ADD/0 OR/1 ADC/2 SBB/3 AND/4 SUB/5 XOR/6 CMP/7 reg, immediate byte /* 84 */ eENTRY_CopyBytes2Mod, // TEST /r /* 85 */ eENTRY_CopyBytes2Mod, // TEST /r /* 86 */ eENTRY_CopyBytes2Mod, // XCHG /r @todo /* 87 */ eENTRY_CopyBytes2Mod, // XCHG /r @todo /* 88 */ eENTRY_CopyBytes2Mod, // MOV /r /* 89 */ eENTRY_CopyBytes2Mod, // MOV /r /* 8A */ eENTRY_CopyBytes2Mod, // MOV /r /* 8B */ eENTRY_CopyBytes2Mod, // MOV /r /* 8C */ eENTRY_CopyBytes2Mod, // MOV /r /* 8D */ eENTRY_CopyBytes2Mod, // LEA /r /* 8E */ eENTRY_CopyBytes2Mod, // MOV /r /* 8F */ eENTRY_CopyXop, // POP /0 or AMD XOP /* 90 */ eENTRY_CopyBytes1, // NOP /* 91 */ eENTRY_CopyBytes1, // XCHG /* 92 */ eENTRY_CopyBytes1, // XCHG /* 93 */ eENTRY_CopyBytes1, // XCHG /* 94 */ eENTRY_CopyBytes1, // XCHG /* 95 */ eENTRY_CopyBytes1, // XCHG /* 96 */ eENTRY_CopyBytes1, // XCHG /* 97 */ eENTRY_CopyBytes1, // XCHG /* 98 */ eENTRY_CopyBytes1, // CWDE /* 99 */ eENTRY_CopyBytes1, // CDQ #if defined(_AMD64_) /* 9A */ eENTRY_Invalid, // Invalid #else /* 9A */ eENTRY_CopyBytes5Or7Dynamic, // CALL cp #endif /* 9B */ eENTRY_CopyBytes1, // WAIT/FWAIT /* 9C */ eENTRY_CopyBytes1, // PUSHFD /* 9D */ eENTRY_CopyBytes1, // POPFD /* 9E */ eENTRY_CopyBytes1, // SAHF /* 9F */ eENTRY_CopyBytes1, // LAHF /* A0 */ eENTRY_CopyBytes1Address, // MOV /* A1 */ eENTRY_CopyBytes1Address, // MOV /* A2 */ eENTRY_CopyBytes1Address, // MOV /* A3 */ eENTRY_CopyBytes1Address, // MOV /* A4 */ eENTRY_CopyBytes1, // MOVS /* A5 */ eENTRY_CopyBytes1, // MOVS/MOVSD /* A6 */ eENTRY_CopyBytes1, // CMPS/CMPSB /* A7 */ eENTRY_CopyBytes1, // CMPS/CMPSW /* A8 */ eENTRY_CopyBytes2, // TEST /* A9 */ eENTRY_CopyBytes3Or5, // TEST /* AA */ eENTRY_CopyBytes1, // STOS/STOSB /* AB */ eENTRY_CopyBytes1, // STOS/STOSW /* AC */ eENTRY_CopyBytes1, // LODS/LODSB /* AD */ eENTRY_CopyBytes1, // LODS/LODSW /* AE */ eENTRY_CopyBytes1, // SCAS/SCASB /* AF */ eENTRY_CopyBytes1, // SCAS/SCASD /* B0 */ eENTRY_CopyBytes2, // MOV B0+rb /* B1 */ eENTRY_CopyBytes2, // MOV B0+rb /* B2 */ eENTRY_CopyBytes2, // MOV B0+rb /* B3 */ eENTRY_CopyBytes2, // MOV B0+rb /* B4 */ eENTRY_CopyBytes2, // MOV B0+rb /* B5 */ eENTRY_CopyBytes2, // MOV B0+rb /* B6 */ eENTRY_CopyBytes2, // MOV B0+rb /* B7 */ eENTRY_CopyBytes2, // MOV B0+rb /* B8 */ eENTRY_CopyBytes3Or5Rax, // MOV B8+rb /* B9 */ eENTRY_CopyBytes3Or5Rax, // MOV B8+rb /* BA */ eENTRY_CopyBytes3Or5Rax, // MOV B8+rb /* BB */ eENTRY_CopyBytes3Or5Rax, // MOV B8+rb /* BC */ eENTRY_CopyBytes3Or5Rax, // MOV B8+rb /* BD */ eENTRY_CopyBytes3Or5Rax, // MOV B8+rb /* BE */ eENTRY_CopyBytes3Or5Rax, // MOV B8+rb /* BF */ eENTRY_CopyBytes3Or5Rax, // MOV B8+rb /* C0 */ eENTRY_CopyBytes2Mod1, // RCL/2 ib, etc. /* C1 */ eENTRY_CopyBytes2Mod1, // RCL/2 ib, etc. /* C2 */ eENTRY_CopyBytes3, // RET /* C3 */ eENTRY_CopyBytes1, // RET /* C4 */ eENTRY_CopyVex3, // LES, VEX 3-byte opcodes. /* C5 */ eENTRY_CopyVex2, // LDS, VEX 2-byte opcodes. /* C6 */ eENTRY_CopyBytes2Mod1, // MOV /* C7 */ eENTRY_CopyBytes2ModOperand, // MOV/0 XBEGIN/7 /* C8 */ eENTRY_CopyBytes4, // ENTER /* C9 */ eENTRY_CopyBytes1, // LEAVE /* CA */ eENTRY_CopyBytes3Dynamic, // RET /* CB */ eENTRY_CopyBytes1Dynamic, // RET /* CC */ eENTRY_CopyBytes1Dynamic, // INT 3 /* CD */ eENTRY_CopyBytes2Dynamic, // INT ib #if defined(_AMD64_) /* CE */ eENTRY_Invalid, // Invalid #else /* CE */ eENTRY_CopyBytes1Dynamic, // INTO #endif /* CF */ eENTRY_CopyBytes1Dynamic, // IRET /* D0 */ eENTRY_CopyBytes2Mod, // RCL/2, etc. /* D1 */ eENTRY_CopyBytes2Mod, // RCL/2, etc. /* D2 */ eENTRY_CopyBytes2Mod, // RCL/2, etc. /* D3 */ eENTRY_CopyBytes2Mod, // RCL/2, etc. #if defined(_AMD64_) /* D4 */ eENTRY_Invalid, // Invalid /* D5 */ eENTRY_Invalid, // Invalid #else /* D4 */ eENTRY_CopyBytes2, // AAM /* D5 */ eENTRY_CopyBytes2, // AAD #endif /* D6 */ eENTRY_Invalid, // Invalid /* D7 */ eENTRY_CopyBytes1, // XLAT/XLATB /* D8 */ eENTRY_CopyBytes2Mod, // FADD, etc. /* D9 */ eENTRY_CopyBytes2Mod, // F2XM1, etc. /* DA */ eENTRY_CopyBytes2Mod, // FLADD, etc. /* DB */ eENTRY_CopyBytes2Mod, // FCLEX, etc. /* DC */ eENTRY_CopyBytes2Mod, // FADD/0, etc. /* DD */ eENTRY_CopyBytes2Mod, // FFREE, etc. /* DE */ eENTRY_CopyBytes2Mod, // FADDP, etc. /* DF */ eENTRY_CopyBytes2Mod, // FBLD/4, etc. /* E0 */ eENTRY_CopyBytes2CantJump, // LOOPNE cb /* E1 */ eENTRY_CopyBytes2CantJump, // LOOPE cb /* E2 */ eENTRY_CopyBytes2CantJump, // LOOP cb /* E3 */ eENTRY_CopyBytes2CantJump, // JCXZ/JECXZ /* E4 */ eENTRY_CopyBytes2, // IN ib /* E5 */ eENTRY_CopyBytes2, // IN id /* E6 */ eENTRY_CopyBytes2, // OUT ib /* E7 */ eENTRY_CopyBytes2, // OUT ib /* E8 */ eENTRY_CopyBytes3Or5Target, // CALL cd /* E9 */ eENTRY_CopyBytes3Or5Target, // JMP cd #if defined(_AMD64_) /* EA */ eENTRY_Invalid, // Invalid #else /* EA */ eENTRY_CopyBytes5Or7Dynamic, // JMP cp #endif /* EB */ eENTRY_CopyBytes2Jump, // JMP cb /* EC */ eENTRY_CopyBytes1, // IN ib /* ED */ eENTRY_CopyBytes1, // IN id /* EE */ eENTRY_CopyBytes1, // OUT /* EF */ eENTRY_CopyBytes1, // OUT /* F0 */ eENTRY_CopyBytesPrefix, // LOCK prefix /* F1 */ eENTRY_CopyBytes1Dynamic, // INT1 / ICEBP somewhat documented by AMD, not by Intel /* F2 */ eENTRY_CopyF2, // REPNE prefix // This does presently suffice for AMD64 but it requires tracing // through a bunch of code to verify and seems not worth maintaining. // For x64: /* F3 */ eENTRY_CopyBytesPrefix /* F3 */ eENTRY_CopyF3, // REPE prefix /* F4 */ eENTRY_CopyBytes1, // HLT /* F5 */ eENTRY_CopyBytes1, // CMC /* F6 */ eENTRY_CopyF6, // TEST/0, DIV/6 /* F7 */ eENTRY_CopyF7, // TEST/0, DIV/6 /* F8 */ eENTRY_CopyBytes1, // CLC /* F9 */ eENTRY_CopyBytes1, // STC /* FA */ eENTRY_CopyBytes1, // CLI /* FB */ eENTRY_CopyBytes1, // STI /* FC */ eENTRY_CopyBytes1, // CLD /* FD */ eENTRY_CopyBytes1, // STD /* FE */ eENTRY_CopyBytes2Mod, // DEC/1,INC/0 /* FF */ eENTRY_CopyFF, // CALL/2 }; static const BYTE g_rceCopyTable0F[] = { #if defined(_X86_) /* 00 */ eENTRY_Copy0F00, // sldt/0 str/1 lldt/2 ltr/3 err/4 verw/5 jmpe/6/dynamic invalid/7 #else /* 00 */ eENTRY_CopyBytes2Mod, // sldt/0 str/1 lldt/2 ltr/3 err/4 verw/5 jmpe/6/dynamic invalid/7 #endif /* 01 */ eENTRY_CopyBytes2Mod, // INVLPG/7, etc. /* 02 */ eENTRY_CopyBytes2Mod, // LAR/r /* 03 */ eENTRY_CopyBytes2Mod, // LSL/r /* 04 */ eENTRY_Invalid, // _04 /* 05 */ eENTRY_CopyBytes1, // SYSCALL /* 06 */ eENTRY_CopyBytes1, // CLTS /* 07 */ eENTRY_CopyBytes1, // SYSRET /* 08 */ eENTRY_CopyBytes1, // INVD /* 09 */ eENTRY_CopyBytes1, // WBINVD /* 0A */ eENTRY_Invalid, // _0A /* 0B */ eENTRY_CopyBytes1, // UD2 /* 0C */ eENTRY_Invalid, // _0C /* 0D */ eENTRY_CopyBytes2Mod, // PREFETCH /* 0E */ eENTRY_CopyBytes1, // FEMMS (3DNow -- not in Intel documentation) /* 0F */ eENTRY_CopyBytes2Mod1, // 3DNow Opcodes /* 10 */ eENTRY_CopyBytes2Mod, // MOVSS MOVUPD MOVSD /* 11 */ eENTRY_CopyBytes2Mod, // MOVSS MOVUPD MOVSD /* 12 */ eENTRY_CopyBytes2Mod, // MOVLPD /* 13 */ eENTRY_CopyBytes2Mod, // MOVLPD /* 14 */ eENTRY_CopyBytes2Mod, // UNPCKLPD /* 15 */ eENTRY_CopyBytes2Mod, // UNPCKHPD /* 16 */ eENTRY_CopyBytes2Mod, // MOVHPD /* 17 */ eENTRY_CopyBytes2Mod, // MOVHPD /* 18 */ eENTRY_CopyBytes2Mod, // PREFETCHINTA... /* 19 */ eENTRY_CopyBytes2Mod, // NOP/r multi byte nop, not documented by Intel, documented by AMD /* 1A */ eENTRY_CopyBytes2Mod, // NOP/r multi byte nop, not documented by Intel, documented by AMD /* 1B */ eENTRY_CopyBytes2Mod, // NOP/r multi byte nop, not documented by Intel, documented by AMD /* 1C */ eENTRY_CopyBytes2Mod, // NOP/r multi byte nop, not documented by Intel, documented by AMD /* 1D */ eENTRY_CopyBytes2Mod, // NOP/r multi byte nop, not documented by Intel, documented by AMD /* 1E */ eENTRY_CopyBytes2Mod, // NOP/r multi byte nop, not documented by Intel, documented by AMD /* 1F */ eENTRY_CopyBytes2Mod, // NOP/r multi byte nop /* 20 */ eENTRY_CopyBytes2Mod, // MOV/r /* 21 */ eENTRY_CopyBytes2Mod, // MOV/r /* 22 */ eENTRY_CopyBytes2Mod, // MOV/r /* 23 */ eENTRY_CopyBytes2Mod, // MOV/r #if defined(_AMD64_) /* 24 */ eENTRY_Invalid, // _24 #else /* 24 */ eENTRY_CopyBytes2Mod, // MOV/r,TR TR is test register on 80386 and 80486, removed in Pentium #endif /* 25 */ eENTRY_Invalid, // _25 #if defined(_AMD64_) /* 26 */ eENTRY_Invalid, // _26 #else /* 26 */ eENTRY_CopyBytes2Mod, // MOV TR/r TR is test register on 80386 and 80486, removed in Pentium #endif /* 27 */ eENTRY_Invalid, // _27 /* 28 */ eENTRY_CopyBytes2Mod, // MOVAPS MOVAPD /* 29 */ eENTRY_CopyBytes2Mod, // MOVAPS MOVAPD /* 2A */ eENTRY_CopyBytes2Mod, // CVPI2PS & /* 2B */ eENTRY_CopyBytes2Mod, // MOVNTPS MOVNTPD /* 2C */ eENTRY_CopyBytes2Mod, // CVTTPS2PI & /* 2D */ eENTRY_CopyBytes2Mod, // CVTPS2PI & /* 2E */ eENTRY_CopyBytes2Mod, // UCOMISS UCOMISD /* 2F */ eENTRY_CopyBytes2Mod, // COMISS COMISD /* 30 */ eENTRY_CopyBytes1, // WRMSR /* 31 */ eENTRY_CopyBytes1, // RDTSC /* 32 */ eENTRY_CopyBytes1, // RDMSR /* 33 */ eENTRY_CopyBytes1, // RDPMC /* 34 */ eENTRY_CopyBytes1, // SYSENTER /* 35 */ eENTRY_CopyBytes1, // SYSEXIT /* 36 */ eENTRY_Invalid, // _36 /* 37 */ eENTRY_CopyBytes1, // GETSEC /* 38 */ eENTRY_CopyBytes3Mod, // SSE3 Opcodes /* 39 */ eENTRY_Invalid, // _39 /* 3A */ eENTRY_CopyBytes3Mod1, // SSE3 Opcodes /* 3B */ eENTRY_Invalid, // _3B /* 3C */ eENTRY_Invalid, // _3C /* 3D */ eENTRY_Invalid, // _3D /* 3E */ eENTRY_Invalid, // _3E /* 3F */ eENTRY_Invalid, // _3F /* 40 */ eENTRY_CopyBytes2Mod, // CMOVO (0F 40) /* 41 */ eENTRY_CopyBytes2Mod, // CMOVNO (0F 41) /* 42 */ eENTRY_CopyBytes2Mod, // CMOVB & CMOVNE (0F 42) /* 43 */ eENTRY_CopyBytes2Mod, // CMOVAE & CMOVNB (0F 43) /* 44 */ eENTRY_CopyBytes2Mod, // CMOVE & CMOVZ (0F 44) /* 45 */ eENTRY_CopyBytes2Mod, // CMOVNE & CMOVNZ (0F 45) /* 46 */ eENTRY_CopyBytes2Mod, // CMOVBE & CMOVNA (0F 46) /* 47 */ eENTRY_CopyBytes2Mod, // CMOVA & CMOVNBE (0F 47) /* 48 */ eENTRY_CopyBytes2Mod, // CMOVS (0F 48) /* 49 */ eENTRY_CopyBytes2Mod, // CMOVNS (0F 49) /* 4A */ eENTRY_CopyBytes2Mod, // CMOVP & CMOVPE (0F 4A) /* 4B */ eENTRY_CopyBytes2Mod, // CMOVNP & CMOVPO (0F 4B) /* 4C */ eENTRY_CopyBytes2Mod, // CMOVL & CMOVNGE (0F 4C) /* 4D */ eENTRY_CopyBytes2Mod, // CMOVGE & CMOVNL (0F 4D) /* 4E */ eENTRY_CopyBytes2Mod, // CMOVLE & CMOVNG (0F 4E) /* 4F */ eENTRY_CopyBytes2Mod, // CMOVG & CMOVNLE (0F 4F) /* 50 */ eENTRY_CopyBytes2Mod, // MOVMSKPD MOVMSKPD /* 51 */ eENTRY_CopyBytes2Mod, // SQRTPS & /* 52 */ eENTRY_CopyBytes2Mod, // RSQRTTS RSQRTPS /* 53 */ eENTRY_CopyBytes2Mod, // RCPPS RCPSS /* 54 */ eENTRY_CopyBytes2Mod, // ANDPS ANDPD /* 55 */ eENTRY_CopyBytes2Mod, // ANDNPS ANDNPD /* 56 */ eENTRY_CopyBytes2Mod, // ORPS ORPD /* 57 */ eENTRY_CopyBytes2Mod, // XORPS XORPD /* 58 */ eENTRY_CopyBytes2Mod, // ADDPS & /* 59 */ eENTRY_CopyBytes2Mod, // MULPS & /* 5A */ eENTRY_CopyBytes2Mod, // CVTPS2PD & /* 5B */ eENTRY_CopyBytes2Mod, // CVTDQ2PS & /* 5C */ eENTRY_CopyBytes2Mod, // SUBPS & /* 5D */ eENTRY_CopyBytes2Mod, // MINPS & /* 5E */ eENTRY_CopyBytes2Mod, // DIVPS & /* 5F */ eENTRY_CopyBytes2Mod, // MASPS & /* 60 */ eENTRY_CopyBytes2Mod, // PUNPCKLBW/r /* 61 */ eENTRY_CopyBytes2Mod, // PUNPCKLWD/r /* 62 */ eENTRY_CopyBytes2Mod, // PUNPCKLWD/r /* 63 */ eENTRY_CopyBytes2Mod, // PACKSSWB/r /* 64 */ eENTRY_CopyBytes2Mod, // PCMPGTB/r /* 65 */ eENTRY_CopyBytes2Mod, // PCMPGTW/r /* 66 */ eENTRY_CopyBytes2Mod, // PCMPGTD/r /* 67 */ eENTRY_CopyBytes2Mod, // PACKUSWB/r /* 68 */ eENTRY_CopyBytes2Mod, // PUNPCKHBW/r /* 69 */ eENTRY_CopyBytes2Mod, // PUNPCKHWD/r /* 6A */ eENTRY_CopyBytes2Mod, // PUNPCKHDQ/r /* 6B */ eENTRY_CopyBytes2Mod, // PACKSSDW/r /* 6C */ eENTRY_CopyBytes2Mod, // PUNPCKLQDQ /* 6D */ eENTRY_CopyBytes2Mod, // PUNPCKHQDQ /* 6E */ eENTRY_CopyBytes2Mod, // MOVD/r /* 6F */ eENTRY_CopyBytes2Mod, // MOV/r /* 70 */ eENTRY_CopyBytes2Mod1, // PSHUFW/r ib /* 71 */ eENTRY_CopyBytes2Mod1, // PSLLW/6 ib,PSRAW/4 ib,PSRLW/2 ib /* 72 */ eENTRY_CopyBytes2Mod1, // PSLLD/6 ib,PSRAD/4 ib,PSRLD/2 ib /* 73 */ eENTRY_CopyBytes2Mod1, // PSLLQ/6 ib,PSRLQ/2 ib /* 74 */ eENTRY_CopyBytes2Mod, // PCMPEQB/r /* 75 */ eENTRY_CopyBytes2Mod, // PCMPEQW/r /* 76 */ eENTRY_CopyBytes2Mod, // PCMPEQD/r /* 77 */ eENTRY_CopyBytes1, // EMMS // extrq/insertq require mode=3 and are followed by two immediate bytes /* 78 */ eENTRY_Copy0F78, // VMREAD/r, 66/EXTRQ/r/ib/ib, F2/INSERTQ/r/ib/ib // extrq/insertq require mod=3, therefore eENTRY_CopyBytes2, but it ends up the same /* 79 */ eENTRY_CopyBytes2Mod, // VMWRITE/r, 66/EXTRQ/r, F2/INSERTQ/r /* 7A */ eENTRY_Invalid, // _7A /* 7B */ eENTRY_Invalid, // _7B /* 7C */ eENTRY_CopyBytes2Mod, // HADDPS /* 7D */ eENTRY_CopyBytes2Mod, // HSUBPS /* 7E */ eENTRY_CopyBytes2Mod, // MOVD/r /* 7F */ eENTRY_CopyBytes2Mod, // MOV/r /* 80 */ eENTRY_CopyBytes3Or5Target, // JO /* 81 */ eENTRY_CopyBytes3Or5Target, // JNO /* 82 */ eENTRY_CopyBytes3Or5Target, // JB,JC,JNAE /* 83 */ eENTRY_CopyBytes3Or5Target, // JAE,JNB,JNC /* 84 */ eENTRY_CopyBytes3Or5Target, // JE,JZ,JZ /* 85 */ eENTRY_CopyBytes3Or5Target, // JNE,JNZ /* 86 */ eENTRY_CopyBytes3Or5Target, // JBE,JNA /* 87 */ eENTRY_CopyBytes3Or5Target, // JA,JNBE /* 88 */ eENTRY_CopyBytes3Or5Target, // JS /* 89 */ eENTRY_CopyBytes3Or5Target, // JNS /* 8A */ eENTRY_CopyBytes3Or5Target, // JP,JPE /* 8B */ eENTRY_CopyBytes3Or5Target, // JNP,JPO /* 8C */ eENTRY_CopyBytes3Or5Target, // JL,NGE /* 8D */ eENTRY_CopyBytes3Or5Target, // JGE,JNL /* 8E */ eENTRY_CopyBytes3Or5Target, // JLE,JNG /* 8F */ eENTRY_CopyBytes3Or5Target, // JG,JNLE /* 90 */ eENTRY_CopyBytes2Mod, // CMOVO (0F 40) /* 91 */ eENTRY_CopyBytes2Mod, // CMOVNO (0F 41) /* 92 */ eENTRY_CopyBytes2Mod, // CMOVB & CMOVC & CMOVNAE (0F 42) /* 93 */ eENTRY_CopyBytes2Mod, // CMOVAE & CMOVNB & CMOVNC (0F 43) /* 94 */ eENTRY_CopyBytes2Mod, // CMOVE & CMOVZ (0F 44) /* 95 */ eENTRY_CopyBytes2Mod, // CMOVNE & CMOVNZ (0F 45) /* 96 */ eENTRY_CopyBytes2Mod, // CMOVBE & CMOVNA (0F 46) /* 97 */ eENTRY_CopyBytes2Mod, // CMOVA & CMOVNBE (0F 47) /* 98 */ eENTRY_CopyBytes2Mod, // CMOVS (0F 48) /* 99 */ eENTRY_CopyBytes2Mod, // CMOVNS (0F 49) /* 9A */ eENTRY_CopyBytes2Mod, // CMOVP & CMOVPE (0F 4A) /* 9B */ eENTRY_CopyBytes2Mod, // CMOVNP & CMOVPO (0F 4B) /* 9C */ eENTRY_CopyBytes2Mod, // CMOVL & CMOVNGE (0F 4C) /* 9D */ eENTRY_CopyBytes2Mod, // CMOVGE & CMOVNL (0F 4D) /* 9E */ eENTRY_CopyBytes2Mod, // CMOVLE & CMOVNG (0F 4E) /* 9F */ eENTRY_CopyBytes2Mod, // CMOVG & CMOVNLE (0F 4F) /* A0 */ eENTRY_CopyBytes1, // PUSH /* A1 */ eENTRY_CopyBytes1, // POP /* A2 */ eENTRY_CopyBytes1, // CPUID /* A3 */ eENTRY_CopyBytes2Mod, // BT (0F A3) /* A4 */ eENTRY_CopyBytes2Mod1, // SHLD /* A5 */ eENTRY_CopyBytes2Mod, // SHLD /* A6 */ eENTRY_CopyBytes2Mod, // XBTS /* A7 */ eENTRY_CopyBytes2Mod, // IBTS /* A8 */ eENTRY_CopyBytes1, // PUSH /* A9 */ eENTRY_CopyBytes1, // POP /* AA */ eENTRY_CopyBytes1, // RSM /* AB */ eENTRY_CopyBytes2Mod, // BTS (0F AB) /* AC */ eENTRY_CopyBytes2Mod1, // SHRD /* AD */ eENTRY_CopyBytes2Mod, // SHRD // 0F AE mod76=mem mod543=0 fxsave // 0F AE mod76=mem mod543=1 fxrstor // 0F AE mod76=mem mod543=2 ldmxcsr // 0F AE mod76=mem mod543=3 stmxcsr // 0F AE mod76=mem mod543=4 xsave // 0F AE mod76=mem mod543=5 xrstor // 0F AE mod76=mem mod543=6 saveopt // 0F AE mod76=mem mod543=7 clflush // 0F AE mod76=11b mod543=5 lfence // 0F AE mod76=11b mod543=6 mfence // 0F AE mod76=11b mod543=7 sfence // F3 0F AE mod76=11b mod543=0 rdfsbase // F3 0F AE mod76=11b mod543=1 rdgsbase // F3 0F AE mod76=11b mod543=2 wrfsbase // F3 0F AE mod76=11b mod543=3 wrgsbase /* AE */ eENTRY_CopyBytes2Mod, // fxsave fxrstor ldmxcsr stmxcsr xsave xrstor saveopt clflush lfence mfence sfence rdfsbase rdgsbase wrfsbase wrgsbase /* AF */ eENTRY_CopyBytes2Mod, // IMUL (0F AF) /* B0 */ eENTRY_CopyBytes2Mod, // CMPXCHG (0F B0) /* B1 */ eENTRY_CopyBytes2Mod, // CMPXCHG (0F B1) /* B2 */ eENTRY_CopyBytes2Mod, // LSS/r /* B3 */ eENTRY_CopyBytes2Mod, // BTR (0F B3) /* B4 */ eENTRY_CopyBytes2Mod, // LFS/r /* B5 */ eENTRY_CopyBytes2Mod, // LGS/r /* B6 */ eENTRY_CopyBytes2Mod, // MOVZX/r /* B7 */ eENTRY_CopyBytes2Mod, // MOVZX/r #if defined(_X86_) /* B8 */ eENTRY_Copy0FB8, // jmpe f3/popcnt #else /* B8 */ eENTRY_CopyBytes2Mod, // f3/popcnt #endif /* B9 */ eENTRY_Invalid, // _B9 /* BA */ eENTRY_CopyBytes2Mod1, // BT & BTC & BTR & BTS (0F BA) /* BB */ eENTRY_CopyBytes2Mod, // BTC (0F BB) /* BC */ eENTRY_CopyBytes2Mod, // BSF (0F BC) /* BD */ eENTRY_CopyBytes2Mod, // BSR (0F BD) /* BE */ eENTRY_CopyBytes2Mod, // MOVSX/r /* BF */ eENTRY_CopyBytes2Mod, // MOVSX/r /* C0 */ eENTRY_CopyBytes2Mod, // XADD/r /* C1 */ eENTRY_CopyBytes2Mod, // XADD/r /* C2 */ eENTRY_CopyBytes2Mod1, // CMPPS & /* C3 */ eENTRY_CopyBytes2Mod, // MOVNTI /* C4 */ eENTRY_CopyBytes2Mod1, // PINSRW /r ib /* C5 */ eENTRY_CopyBytes2Mod1, // PEXTRW /r ib /* C6 */ eENTRY_CopyBytes2Mod1, // SHUFPS & SHUFPD /* C7 */ eENTRY_CopyBytes2Mod, // CMPXCHG8B (0F C7) /* C8 */ eENTRY_CopyBytes1, // BSWAP 0F C8 + rd /* C9 */ eENTRY_CopyBytes1, // BSWAP 0F C8 + rd /* CA */ eENTRY_CopyBytes1, // BSWAP 0F C8 + rd /* CB */ eENTRY_CopyBytes1, // CVTPD2PI BSWAP 0F C8 + rd /* CC */ eENTRY_CopyBytes1, // BSWAP 0F C8 + rd /* CD */ eENTRY_CopyBytes1, // BSWAP 0F C8 + rd /* CE */ eENTRY_CopyBytes1, // BSWAP 0F C8 + rd /* CF */ eENTRY_CopyBytes1, // BSWAP 0F C8 + rd /* D0 */ eENTRY_CopyBytes2Mod, // ADDSUBPS (untestd) /* D1 */ eENTRY_CopyBytes2Mod, // PSRLW/r /* D2 */ eENTRY_CopyBytes2Mod, // PSRLD/r /* D3 */ eENTRY_CopyBytes2Mod, // PSRLQ/r /* D4 */ eENTRY_CopyBytes2Mod, // PADDQ /* D5 */ eENTRY_CopyBytes2Mod, // PMULLW/r /* D6 */ eENTRY_CopyBytes2Mod, // MOVDQ2Q / MOVQ2DQ /* D7 */ eENTRY_CopyBytes2Mod, // PMOVMSKB/r /* D8 */ eENTRY_CopyBytes2Mod, // PSUBUSB/r /* D9 */ eENTRY_CopyBytes2Mod, // PSUBUSW/r /* DA */ eENTRY_CopyBytes2Mod, // PMINUB/r /* DB */ eENTRY_CopyBytes2Mod, // PAND/r /* DC */ eENTRY_CopyBytes2Mod, // PADDUSB/r /* DD */ eENTRY_CopyBytes2Mod, // PADDUSW/r /* DE */ eENTRY_CopyBytes2Mod, // PMAXUB/r /* DF */ eENTRY_CopyBytes2Mod, // PANDN/r /* E0 */ eENTRY_CopyBytes2Mod, // PAVGB /* E1 */ eENTRY_CopyBytes2Mod, // PSRAW/r /* E2 */ eENTRY_CopyBytes2Mod, // PSRAD/r /* E3 */ eENTRY_CopyBytes2Mod, // PAVGW /* E4 */ eENTRY_CopyBytes2Mod, // PMULHUW/r /* E5 */ eENTRY_CopyBytes2Mod, // PMULHW/r /* E6 */ eENTRY_CopyBytes2Mod, // CTDQ2PD & /* E7 */ eENTRY_CopyBytes2Mod, // MOVNTQ /* E8 */ eENTRY_CopyBytes2Mod, // PSUBB/r /* E9 */ eENTRY_CopyBytes2Mod, // PSUBW/r /* EA */ eENTRY_CopyBytes2Mod, // PMINSW/r /* EB */ eENTRY_CopyBytes2Mod, // POR/r /* EC */ eENTRY_CopyBytes2Mod, // PADDSB/r /* ED */ eENTRY_CopyBytes2Mod, // PADDSW/r /* EE */ eENTRY_CopyBytes2Mod, // PMAXSW /r /* EF */ eENTRY_CopyBytes2Mod, // PXOR/r /* F0 */ eENTRY_CopyBytes2Mod, // LDDQU /* F1 */ eENTRY_CopyBytes2Mod, // PSLLW/r /* F2 */ eENTRY_CopyBytes2Mod, // PSLLD/r /* F3 */ eENTRY_CopyBytes2Mod, // PSLLQ/r /* F4 */ eENTRY_CopyBytes2Mod, // PMULUDQ/r /* F5 */ eENTRY_CopyBytes2Mod, // PMADDWD/r /* F6 */ eENTRY_CopyBytes2Mod, // PSADBW/r /* F7 */ eENTRY_CopyBytes2Mod, // MASKMOVQ /* F8 */ eENTRY_CopyBytes2Mod, // PSUBB/r /* F9 */ eENTRY_CopyBytes2Mod, // PSUBW/r /* FA */ eENTRY_CopyBytes2Mod, // PSUBD/r /* FB */ eENTRY_CopyBytes2Mod, // FSUBQ/r /* FC */ eENTRY_CopyBytes2Mod, // PADDB/r /* FD */ eENTRY_CopyBytes2Mod, // PADDW/r /* FE */ eENTRY_CopyBytes2Mod, // PADDD/r /* FF */ eENTRY_Invalid, // _FF }; _STATIC_ASSERT(_countof(g_rbModRm) == 256 && _countof(g_rceCopyMap) == eENTRY_Invalid + 1 && _countof(g_rceCopyTable) == 256 && _countof(g_rceCopyTable0F) == 256); /////////////////////////////////////////////////////////// Disassembler Code. // static PBYTE AdjustTarget( _In_ PDETOUR_DISASM pDisasm, _In_ PBYTE pbDst, _In_ PBYTE pbSrc, UINT cbOp, UINT cbTargetOffset, UINT cbTargetSize) { PBYTE pbTarget = NULL; LONG_PTR nOldOffset; LONG_PTR nNewOffset; PVOID pvTargetAddr = &pbDst[cbTargetOffset]; switch (cbTargetSize) { case 1: nOldOffset = *(signed char*)pvTargetAddr; break; case 2: nOldOffset = *(UNALIGNED SHORT*)pvTargetAddr; break; case 4: nOldOffset = *(UNALIGNED LONG*)pvTargetAddr; break; #if defined(_AMD64_) case 8: nOldOffset = *(UNALIGNED LONGLONG*)pvTargetAddr; break; #endif default: ASSERT(!"cbTargetSize is invalid."); nOldOffset = 0; break; } pbTarget = pbSrc + cbOp + nOldOffset; nNewOffset = nOldOffset - (LONG_PTR)(pbDst - pbSrc); switch (cbTargetSize) { case 1: *(CHAR*)pvTargetAddr = (CHAR)nNewOffset; if (nNewOffset < SCHAR_MIN || nNewOffset > SCHAR_MAX) { *pDisasm->plExtra = sizeof(ULONG) - 1; } break; case 2: *(UNALIGNED SHORT*)pvTargetAddr = (SHORT)nNewOffset; if (nNewOffset < SHRT_MIN || nNewOffset > SHRT_MAX) { *pDisasm->plExtra = sizeof(ULONG) - 2; } break; case 4: *(UNALIGNED LONG*)pvTargetAddr = (LONG)nNewOffset; if (nNewOffset < LONG_MIN || nNewOffset > LONG_MAX) { *pDisasm->plExtra = sizeof(ULONG) - 4; } break; #if defined(_AMD64_) case 8: *(UNALIGNED LONGLONG*)pvTargetAddr = nNewOffset; break; #endif } #if defined(_AMD64_) // When we are only computing size, source and dest can be // far apart, distance not encodable in 32bits. Ok. // At least still check the lower 32bits. if (pbDst >= pDisasm->rbScratchDst && pbDst < (sizeof(pDisasm->rbScratchDst) + pDisasm->rbScratchDst)) { ASSERT((((size_t)pbDst + cbOp + nNewOffset) & 0xFFFFFFFF) == (((size_t)pbTarget) & 0xFFFFFFFF)); } else #endif { ASSERT(pbDst + cbOp + nNewOffset == pbTarget); } return pbTarget; } static PBYTE Invalid( _In_ PDETOUR_DISASM pDisasm, _In_opt_ REFCOPYENTRY pEntry, _In_ PBYTE pbDst, _In_ PBYTE pbSrc) { UNREFERENCED_PARAMETER(pDisasm); UNREFERENCED_PARAMETER(pEntry); UNREFERENCED_PARAMETER(pbDst); UNREFERENCED_PARAMETER(pbSrc); return NULL; } static PBYTE CopyInstruction( _In_ PDETOUR_DISASM pDisasm, _In_opt_ PBYTE pbDst, _In_ PBYTE pbSrc) { // Configure scratch areas if real areas are not available. if (NULL == pbDst) { pbDst = pDisasm->rbScratchDst; } // Figure out how big the instruction is, do the appropriate copy, // and figure out what the target of the instruction is if any. // const COPYENTRY* ce = &g_rceCopyMap[g_rceCopyTable[pbSrc[0]]]; return ce->pfCopy(pDisasm, ce, pbDst, pbSrc); } static PBYTE CopyBytes( _In_ PDETOUR_DISASM pDisasm, _In_opt_ REFCOPYENTRY pEntry, _In_ PBYTE pbDst, _In_ PBYTE pbSrc) { UINT nBytesFixed; if (pDisasm->bVex || pDisasm->bEvex) { ASSERT(pEntry->nFlagBits == 0); ASSERT(pEntry->nFixedSize == pEntry->nFixedSize16); } UINT const nModOffset = pEntry->nModOffset; UINT const nFlagBits = pEntry->nFlagBits; UINT const nFixedSize = pEntry->nFixedSize; UINT const nFixedSize16 = pEntry->nFixedSize16; if (nFlagBits & ADDRESS) { nBytesFixed = pDisasm->bAddressOverride ? nFixedSize16 : nFixedSize; } #if defined(_AMD64_) // REX.W trumps 66 else if (pDisasm->bRaxOverride) { nBytesFixed = nFixedSize + ((nFlagBits & RAX) ? 4 : 0); } #endif else { nBytesFixed = pDisasm->bOperandOverride ? nFixedSize16 : nFixedSize; } UINT nBytes = nBytesFixed; UINT nRelOffset = pEntry->nRelOffset; UINT cbTarget = nBytes - nRelOffset; if (nModOffset > 0) { ASSERT(nRelOffset == 0); BYTE const bModRm = pbSrc[nModOffset]; BYTE const bFlags = g_rbModRm[bModRm]; nBytes += bFlags & NOTSIB; if (bFlags & SIB) { BYTE const bSib = pbSrc[nModOffset + 1]; if ((bSib & 0x07) == 0x05) { if ((bModRm & 0xc0) == 0x00) { nBytes += 4; } else if ((bModRm & 0xc0) == 0x40) { nBytes += 1; } else if ((bModRm & 0xc0) == 0x80) { nBytes += 4; } } cbTarget = nBytes - nRelOffset; } #if defined(_AMD64_) else if (bFlags & RIP) { nRelOffset = nModOffset + 1; cbTarget = 4; } #endif } CopyMemory(pbDst, pbSrc, nBytes); if (nRelOffset) { *pDisasm->ppbTarget = AdjustTarget(pDisasm, pbDst, pbSrc, nBytes, nRelOffset, cbTarget); #if defined(_AMD64_) if (pEntry->nRelOffset == 0) { // This is a data target, not a code target, so we shouldn't return it. *pDisasm->ppbTarget = NULL; } #endif } if (nFlagBits & NOENLARGE) { *pDisasm->plExtra = -*pDisasm->plExtra; } if (nFlagBits & DYNAMIC) { *pDisasm->ppbTarget = (PBYTE)DETOUR_INSTRUCTION_TARGET_DYNAMIC; } return pbSrc + nBytes; } static PBYTE CopyBytesPrefix( _In_ PDETOUR_DISASM pDisasm, _In_opt_ REFCOPYENTRY pEntry, _In_ PBYTE pbDst, _In_ PBYTE pbSrc) { UNREFERENCED_PARAMETER(pEntry); pbDst[0] = pbSrc[0]; REFCOPYENTRY ce = &g_rceCopyMap[g_rceCopyTable[pbSrc[1]]]; return ce->pfCopy(pDisasm, ce, pbDst + 1, pbSrc + 1); } static PBYTE CopyBytesSegment( _In_ PDETOUR_DISASM pDisasm, _In_opt_ REFCOPYENTRY pEntry, _In_ PBYTE pbDst, _In_ PBYTE pbSrc) { UNREFERENCED_PARAMETER(pEntry); pDisasm->nSegmentOverride = pbSrc[0]; return CopyBytesPrefix(pDisasm, NULL, pbDst, pbSrc); } static PBYTE CopyBytesRax( _In_ PDETOUR_DISASM pDisasm, _In_opt_ REFCOPYENTRY pEntry, _In_ PBYTE pbDst, _In_ PBYTE pbSrc) { // AMD64 only UNREFERENCED_PARAMETER(pEntry); if (pbSrc[0] & 0x8) { pDisasm->bRaxOverride = TRUE; } return CopyBytesPrefix(pDisasm, NULL, pbDst, pbSrc); } static PBYTE CopyBytesJump( _In_ PDETOUR_DISASM pDisasm, _In_opt_ REFCOPYENTRY pEntry, _In_ PBYTE pbDst, _In_ PBYTE pbSrc) { UNREFERENCED_PARAMETER(pEntry); PVOID pvSrcAddr = &pbSrc[1]; PVOID pvDstAddr = NULL; LONG_PTR nOldOffset = (LONG_PTR)(*(signed char*)pvSrcAddr); LONG_PTR nNewOffset = 0; *pDisasm->ppbTarget = pbSrc + 2 + nOldOffset; if (pbSrc[0] == 0xeb) { pbDst[0] = 0xe9; pvDstAddr = &pbDst[1]; nNewOffset = nOldOffset - ((pbDst - pbSrc) + 3); *(UNALIGNED LONG*)pvDstAddr = (LONG)nNewOffset; *pDisasm->plExtra = 3; return pbSrc + 2; } ASSERT(pbSrc[0] >= 0x70 && pbSrc[0] <= 0x7f); pbDst[0] = 0x0f; pbDst[1] = 0x80 | (pbSrc[0] & 0xf); pvDstAddr = &pbDst[2]; nNewOffset = nOldOffset - ((pbDst - pbSrc) + 4); *(UNALIGNED LONG*)pvDstAddr = (LONG)nNewOffset; *pDisasm->plExtra = 4; return pbSrc + 2; } ////////////////////////////////////////////////////// Individual Bytes Codes. // static PBYTE Copy0F( _In_ PDETOUR_DISASM pDisasm, _In_opt_ REFCOPYENTRY pEntry, _In_ PBYTE pbDst, _In_ PBYTE pbSrc) { UNREFERENCED_PARAMETER(pEntry); pbDst[0] = pbSrc[0]; REFCOPYENTRY ce = &g_rceCopyMap[g_rceCopyTable0F[pbSrc[1]]]; return ce->pfCopy(pDisasm, ce, pbDst + 1, pbSrc + 1); } static PBYTE Copy0F78( _In_ PDETOUR_DISASM pDisasm, _In_opt_ REFCOPYENTRY pEntry, _In_ PBYTE pbDst, _In_ PBYTE pbSrc) { // vmread, 66/extrq, F2/insertq UNREFERENCED_PARAMETER(pEntry); const BYTE vmread = /* 78 */ eENTRY_CopyBytes2Mod; const BYTE extrq_insertq = /* 78 */ eENTRY_CopyBytes4; ASSERT(!(pDisasm->bF2 && pDisasm->bOperandOverride)); // For insertq and presumably despite documentation extrq, mode must be 11, not checked. // insertq/extrq/78 are followed by two immediate bytes, and given mode == 11, mod/rm byte is always one byte, // and the 0x78 makes 4 bytes (not counting the 66/F2/F which are accounted for elsewhere) REFCOPYENTRY ce = &g_rceCopyMap[((pDisasm->bF2 || pDisasm->bOperandOverride) ? extrq_insertq : vmread)]; return ce->pfCopy(pDisasm, ce, pbDst, pbSrc); } static PBYTE Copy0F00( _In_ PDETOUR_DISASM pDisasm, _In_opt_ REFCOPYENTRY pEntry, _In_ PBYTE pbDst, _In_ PBYTE pbSrc) { // jmpe is 32bit x86 only // Notice that the sizes are the same either way, but jmpe is marked as "dynamic". UNREFERENCED_PARAMETER(pEntry); const BYTE other = /* B8 */ eENTRY_CopyBytes2Mod; // sldt/0 str/1 lldt/2 ltr/3 err/4 verw/5 jmpe/6 invalid/7 const BYTE jmpe = /* B8 */ eENTRY_CopyBytes2ModDynamic; // jmpe/6 x86-on-IA64 syscalls REFCOPYENTRY ce = &g_rceCopyMap[(((6 << 3) == ((7 << 3) & pbSrc[1])) ? jmpe : other)]; return ce->pfCopy(pDisasm, ce, pbDst, pbSrc); } static PBYTE Copy0FB8( _In_ PDETOUR_DISASM pDisasm, _In_opt_ REFCOPYENTRY pEntry, _In_ PBYTE pbDst, _In_ PBYTE pbSrc) { // jmpe is 32bit x86 only UNREFERENCED_PARAMETER(pEntry); const BYTE popcnt = /* B8 */ eENTRY_CopyBytes2Mod; const BYTE jmpe = /* B8 */ eENTRY_CopyBytes3Or5Dynamic; // jmpe x86-on-IA64 syscalls REFCOPYENTRY ce = &g_rceCopyMap[pDisasm->bF3 ? popcnt : jmpe]; return ce->pfCopy(pDisasm, ce, pbDst, pbSrc); } static PBYTE Copy66( _In_ PDETOUR_DISASM pDisasm, _In_opt_ REFCOPYENTRY pEntry, _In_ PBYTE pbDst, _In_ PBYTE pbSrc) { // Operand-size override prefix UNREFERENCED_PARAMETER(pEntry); pDisasm->bOperandOverride = TRUE; return CopyBytesPrefix(pDisasm, NULL, pbDst, pbSrc); } static PBYTE Copy67( _In_ PDETOUR_DISASM pDisasm, _In_opt_ REFCOPYENTRY pEntry, _In_ PBYTE pbDst, _In_ PBYTE pbSrc) { // Address size override prefix UNREFERENCED_PARAMETER(pEntry); pDisasm->bAddressOverride = TRUE; return CopyBytesPrefix(pDisasm, NULL, pbDst, pbSrc); } static PBYTE CopyF2( _In_ PDETOUR_DISASM pDisasm, _In_opt_ REFCOPYENTRY pEntry, _In_ PBYTE pbDst, _In_ PBYTE pbSrc) { UNREFERENCED_PARAMETER(pEntry); pDisasm->bF2 = TRUE; return CopyBytesPrefix(pDisasm, NULL, pbDst, pbSrc); } static PBYTE CopyF3( _In_ PDETOUR_DISASM pDisasm, _In_opt_ REFCOPYENTRY pEntry, _In_ PBYTE pbDst, _In_ PBYTE pbSrc) { // x86 only UNREFERENCED_PARAMETER(pEntry); pDisasm->bF3 = TRUE; return CopyBytesPrefix(pDisasm, NULL, pbDst, pbSrc); } static PBYTE CopyF6( _In_ PDETOUR_DISASM pDisasm, _In_opt_ REFCOPYENTRY pEntry, _In_ PBYTE pbDst, _In_ PBYTE pbSrc) { UNREFERENCED_PARAMETER(pEntry); // TEST BYTE /0 if (0x00 == (0x38 & pbSrc[1])) { // reg(bits 543) of ModR/M == 0 REFCOPYENTRY ce = /* f6 */ &g_rceCopyMap[eENTRY_CopyBytes2Mod1]; return ce->pfCopy(pDisasm, ce, pbDst, pbSrc); } else { // DIV /6 // IDIV /7 // IMUL /5 // MUL /4 // NEG /3 // NOT /2 REFCOPYENTRY ce = /* f6 */ &g_rceCopyMap[eENTRY_CopyBytes2Mod]; return ce->pfCopy(pDisasm, ce, pbDst, pbSrc); } } static PBYTE CopyF7( _In_ PDETOUR_DISASM pDisasm, _In_opt_ REFCOPYENTRY pEntry, _In_ PBYTE pbDst, _In_ PBYTE pbSrc) { UNREFERENCED_PARAMETER(pEntry); // TEST WORD /0 if (0x00 == (0x38 & pbSrc[1])) { // reg(bits 543) of ModR/M == 0 REFCOPYENTRY ce = /* f7 */ &g_rceCopyMap[eENTRY_CopyBytes2ModOperand]; return ce->pfCopy(pDisasm, ce, pbDst, pbSrc); } else { // DIV /6 // IDIV /7 // IMUL /5 // MUL /4 // NEG /3 // NOT /2 REFCOPYENTRY ce = /* f7 */ &g_rceCopyMap[eENTRY_CopyBytes2Mod]; return ce->pfCopy(pDisasm, ce, pbDst, pbSrc); } } static PBYTE CopyFF( _In_ PDETOUR_DISASM pDisasm, _In_opt_ REFCOPYENTRY pEntry, _In_ PBYTE pbDst, _In_ PBYTE pbSrc) { // INC /0 // DEC /1 // CALL /2 // CALL /3 // JMP /4 // JMP /5 // PUSH /6 // invalid/7 UNREFERENCED_PARAMETER(pEntry); REFCOPYENTRY ce = /* ff */ &g_rceCopyMap[eENTRY_CopyBytes2Mod]; PBYTE pbOut = ce->pfCopy(pDisasm, ce, pbDst, pbSrc); BYTE const b1 = pbSrc[1]; if (0x15 == b1 || 0x25 == b1) { // CALL [], JMP [] #if defined(_AMD64_) // All segments but FS and GS are equivalent. if (pDisasm->nSegmentOverride != 0x64 && pDisasm->nSegmentOverride != 0x65) #else if (pDisasm->nSegmentOverride == 0 || pDisasm->nSegmentOverride == 0x2E) #endif { #if defined(_AMD64_) INT32 offset = *(UNALIGNED INT32*) & pbSrc[2]; PBYTE* ppbTarget = (PBYTE*)(pbSrc + 6 + offset); #else PBYTE* ppbTarget = (PBYTE*)(SIZE_T) * (UNALIGNED ULONG*) & pbSrc[2]; #endif // This can access violate on random bytes. Use DetourSetCodeModule. *pDisasm->ppbTarget = *ppbTarget; } else { *pDisasm->ppbTarget = (PBYTE)DETOUR_INSTRUCTION_TARGET_DYNAMIC; } } else if (0x10 == (0x30 & b1) || // CALL /2 or /3 --> reg(bits 543) of ModR/M == 010 or 011 0x20 == (0x30 & b1)) { // JMP /4 or /5 --> reg(bits 543) of ModR/M == 100 or 101 *pDisasm->ppbTarget = (PBYTE)DETOUR_INSTRUCTION_TARGET_DYNAMIC; } return pbOut; } static PBYTE CopyVexEvexCommon( _In_ PDETOUR_DISASM pDisasm, BYTE m, _In_ PBYTE pbDst, _In_ PBYTE pbSrc, BYTE p, _In_opt_ BYTE fp16) // m is first instead of last in the hopes of pbDst/pbSrc being // passed along efficiently in the registers they were already in. { REFCOPYENTRY ce; switch (p & 3) { case 0: break; case 1: pDisasm->bOperandOverride = TRUE; break; case 2: pDisasm->bF3 = TRUE; break; case 3: pDisasm->bF2 = TRUE; break; } // see https://software.intel.com/content/www/us/en/develop/download/intel-avx512-fp16-architecture-specification.html switch (m | fp16) { case 1: ce = &g_rceCopyMap[g_rceCopyTable0F[pbSrc[0]]]; return ce->pfCopy(pDisasm, ce, pbDst, pbSrc); case 5: // fallthrough case 6: // fallthrough case 2: return CopyBytes(pDisasm, &g_rceCopyMap[eENTRY_CopyBytes2Mod], pbDst, pbSrc); /* 38 ceF38 */ case 3: return CopyBytes(pDisasm, &g_rceCopyMap[eENTRY_CopyBytes2Mod1], pbDst, pbSrc); /* 3A ceF3A */ default: return Invalid(pDisasm, &g_rceCopyMap[eENTRY_Invalid], pbDst, pbSrc); /* C4 ceInvalid */ } } static PBYTE CopyVexCommon( _In_ PDETOUR_DISASM pDisasm, BYTE m, _In_ PBYTE pbDst, _In_ PBYTE pbSrc) // m is first instead of last in the hopes of pbDst/pbSrc being // passed along efficiently in the registers they were already in. { pDisasm->bVex = TRUE; return CopyVexEvexCommon(pDisasm, m, pbDst, pbSrc, (BYTE)(pbSrc[-1] & 3), 0); } static PBYTE CopyVex3( _In_ PDETOUR_DISASM pDisasm, _In_opt_ REFCOPYENTRY pEntry, _In_ PBYTE pbDst, _In_ PBYTE pbSrc) // 3 byte VEX prefix 0xC4 { UNREFERENCED_PARAMETER(pEntry); #if defined(_X86_) if ((pbSrc[1] & 0xC0) != 0xC0) { REFCOPYENTRY ce = &g_rceCopyMap[eENTRY_CopyBytes2Mod]; /* C4 ceLES */ return ce->pfCopy(pDisasm, ce, pbDst, pbSrc); } #endif pbDst[0] = pbSrc[0]; pbDst[1] = pbSrc[1]; pbDst[2] = pbSrc[2]; #if defined(_AMD64_) pDisasm->bRaxOverride |= !!(pbSrc[2] & 0x80); // w in last byte, see CopyBytesRax #else // // TODO // // Usually the VEX.W bit changes the size of a general purpose register and is ignored for 32bit. // Sometimes it is an opcode extension. // Look in the Intel manual, in the instruction-by-instruction reference, for ".W1", // without nearby wording saying it is ignored for 32bit. // For example: "VFMADD132PD/VFMADD213PD/VFMADD231PD Fused Multiply-Add of Packed Double-Precision Floating-Point Values". // // Then, go through each such case and determine if W0 vs. W1 affect the size of the instruction. Probably not. // Look for the same encoding but with "W1" changed to "W0". // Here is one such pairing: // VFMADD132PD/VFMADD213PD/VFMADD231PD Fused Multiply-Add of Packed Double-Precision Floating-Point Values // // VEX.DDS.128.66.0F38.W1 98 /r A V/V FMA Multiply packed double-precision floating-point values // from xmm0 and xmm2/mem, add to xmm1 and // put result in xmm0. // VFMADD132PD xmm0, xmm1, xmm2/m128 // // VFMADD132PS/VFMADD213PS/VFMADD231PS Fused Multiply-Add of Packed Single-Precision Floating-Point Values // VEX.DDS.128.66.0F38.W0 98 /r A V/V FMA Multiply packed single-precision floating-point values // from xmm0 and xmm2/mem, add to xmm1 and put // result in xmm0. // VFMADD132PS xmm0, xmm1, xmm2/m128 // #endif return CopyVexCommon(pDisasm, pbSrc[1] & 0x1F, pbDst + 3, pbSrc + 3); } static PBYTE CopyVex2( _In_ PDETOUR_DISASM pDisasm, _In_opt_ REFCOPYENTRY pEntry, _In_ PBYTE pbDst, _In_ PBYTE pbSrc) // 2 byte VEX prefix 0xC5 { #if defined(_X86_) if ((pbSrc[1] & 0xC0) != 0xC0) { REFCOPYENTRY ce = &g_rceCopyMap[eENTRY_CopyBytes2Mod]; /* C5 ceLDS */ return ce->pfCopy(pDisasm, ce, pbDst, pbSrc); } #endif pbDst[0] = pbSrc[0]; pbDst[1] = pbSrc[1]; return CopyVexCommon(pDisasm, 1, pbDst + 2, pbSrc + 2); } static PBYTE CopyEvex( _In_ PDETOUR_DISASM pDisasm, _In_opt_ REFCOPYENTRY pEntry, _In_ PBYTE pbDst, _In_ PBYTE pbSrc) // 62, 3 byte payload, x86 with implied prefixes like Vex // for 32bit, mode 0xC0 else fallback to bound /r { // NOTE: Intel and Wikipedia number these differently. // Intel says 0-2, Wikipedia says 1-3. BYTE const p0 = pbSrc[1]; #if defined(_X86_) if ((p0 & 0xC0) != 0xC0) { return CopyBytes(pDisasm, &g_rceCopyMap[eENTRY_CopyBytes2Mod], pbDst, pbSrc); /* 62 ceBound */ } #endif // This could also be handled by default in CopyVexEvexCommon // if 4u changed to 4|8. if (p0 & 8u) return Invalid(pDisasm, &g_rceCopyMap[eENTRY_Invalid], pbDst, pbSrc); /* 62 ceInvalid */ BYTE const p1 = pbSrc[2]; if ((p1 & 0x04) != 0x04) return Invalid(pDisasm, &g_rceCopyMap[eENTRY_Invalid], pbDst, pbSrc); /* 62 ceInvalid */ // Copy 4 byte prefix. *(UNALIGNED ULONG*)pbDst = *(UNALIGNED ULONG*)pbSrc; pDisasm->bEvex = TRUE; #if defined(_AMD64_) pDisasm->bRaxOverride |= !!(p1 & 0x80); // w #endif return CopyVexEvexCommon(pDisasm, p0 & 3u, pbDst + 4, pbSrc + 4, p1 & 3u, p0 & 4u); } static PBYTE CopyXop( _In_ PDETOUR_DISASM pDisasm, _In_opt_ REFCOPYENTRY pEntry, _In_ PBYTE pbDst, _In_ PBYTE pbSrc) /* 3 byte AMD XOP prefix 0x8F byte0: 0x8F byte1: RXBmmmmm byte2: WvvvvLpp byte3: opcode mmmmm >= 8, else pop mmmmm only otherwise defined for 8, 9, A. pp is like VEX but only instructions with 0 are defined */ { UNREFERENCED_PARAMETER(pEntry); BYTE const m = (BYTE)(pbSrc[1] & 0x1F); ASSERT(m <= 10); switch (m) { case 8: // modrm with 8bit immediate return CopyBytes(pDisasm, &g_rceCopyMap[eENTRY_CopyBytesXop1], pbDst, pbSrc); /* 8F ceXop1 */ case 9: // modrm with no immediate return CopyBytes(pDisasm, &g_rceCopyMap[eENTRY_CopyBytesXop], pbDst, pbSrc); /* 8F ceXop */ case 10: // modrm with 32bit immediate return CopyBytes(pDisasm, &g_rceCopyMap[eENTRY_CopyBytesXop4], pbDst, pbSrc); /* 8F ceXop4 */ default: return CopyBytes(pDisasm, &g_rceCopyMap[eENTRY_CopyBytes2Mod], pbDst, pbSrc); /* 8F cePop */ } } #endif // defined(_AMD64_) || defined(_X86_) #if defined(_ARM64_) typedef struct _DETOUR_DISASM { PBYTE pbTarget; BYTE rbScratchDst[128]; // matches or exceeds rbCode } DETOUR_DISASM, *PDETOUR_DISASM; static VOID detour_disasm_init( _Out_ PDETOUR_DISASM pDisasm) { pDisasm->pbTarget = (PBYTE)DETOUR_INSTRUCTION_TARGET_NONE; } typedef BYTE(*COPYFUNC)( _In_ PBYTE pbDst, _In_ PBYTE pbSrc); #define c_LR 30 // The register number for the Link Register #define c_SP 31 // The register number for the Stack Pointer #define c_NOP 0xd503201f // A nop instruction #define c_BREAK (0xd4200000 | (0xf000 << 5)) // A break instruction // // Problematic instructions: // // ADR 0ll10000 hhhhhhhh hhhhhhhh hhhddddd & 0x9f000000 == 0x10000000 (l = low, h = high, d = Rd) // ADRP 1ll10000 hhhhhhhh hhhhhhhh hhhddddd & 0x9f000000 == 0x90000000 (l = low, h = high, d = Rd) // // B.cond 01010100 iiiiiiii iiiiiiii iii0cccc & 0xff000010 == 0x54000000 (i = delta = SignExtend(imm19:00, 64), c = cond) // // B 000101ii iiiiiiii iiiiiiii iiiiiiii & 0xfc000000 == 0x14000000 (i = delta = SignExtend(imm26:00, 64)) // BL 100101ii iiiiiiii iiiiiiii iiiiiiii & 0xfc000000 == 0x94000000 (i = delta = SignExtend(imm26:00, 64)) // // CBNZ z0110101 iiiiiiii iiiiiiii iiittttt & 0x7f000000 == 0x35000000 (z = size, i = delta = SignExtend(imm19:00, 64), t = Rt) // CBZ z0110100 iiiiiiii iiiiiiii iiittttt & 0x7f000000 == 0x34000000 (z = size, i = delta = SignExtend(imm19:00, 64), t = Rt) // // LDR Wt 00011000 iiiiiiii iiiiiiii iiittttt & 0xff000000 == 0x18000000 (i = SignExtend(imm19:00, 64), t = Rt) // LDR Xt 01011000 iiiiiiii iiiiiiii iiittttt & 0xff000000 == 0x58000000 (i = SignExtend(imm19:00, 64), t = Rt) // LDRSW 10011000 iiiiiiii iiiiiiii iiittttt & 0xff000000 == 0x98000000 (i = SignExtend(imm19:00, 64), t = Rt) // PRFM 11011000 iiiiiiii iiiiiiii iiittttt & 0xff000000 == 0xd8000000 (i = SignExtend(imm19:00, 64), t = Rt) // LDR St 00011100 iiiiiiii iiiiiiii iiittttt & 0xff000000 == 0x1c000000 (i = SignExtend(imm19:00, 64), t = Rt) // LDR Dt 01011100 iiiiiiii iiiiiiii iiittttt & 0xff000000 == 0x5c000000 (i = SignExtend(imm19:00, 64), t = Rt) // LDR Qt 10011100 iiiiiiii iiiiiiii iiittttt & 0xff000000 == 0x9c000000 (i = SignExtend(imm19:00, 64), t = Rt) // LDR inv 11011100 iiiiiiii iiiiiiii iiittttt & 0xff000000 == 0xdc000000 (i = SignExtend(imm19:00, 64), t = Rt) // // TBNZ z0110111 bbbbbiii iiiiiiii iiittttt & 0x7f000000 == 0x37000000 (z = size, b = bitnum, i = SignExtend(imm14:00, 64), t = Rt) // TBZ z0110110 bbbbbiii iiiiiiii iiittttt & 0x7f000000 == 0x36000000 (z = size, b = bitnum, i = SignExtend(imm14:00, 64), t = Rt) // typedef union { DWORD Assembled; struct { DWORD Rd : 5; // Destination register DWORD Rn : 5; // Source register DWORD Imm12 : 12; // 12-bit immediate DWORD Shift : 2; // shift (must be 0 or 1) DWORD Opcode1 : 7; // Must be 0010001 == 0x11 DWORD Size : 1; // 0 = 32-bit, 1 = 64-bit } s; } AddImm12; static DWORD AddImm12_Assemble( DWORD size, DWORD rd, DWORD rn, ULONG imm, DWORD shift) { AddImm12 temp; temp.s.Rd = rd; temp.s.Rn = rn; temp.s.Imm12 = imm & 0xfff; temp.s.Shift = shift; temp.s.Opcode1 = 0x11; temp.s.Size = size; return temp.Assembled; } static DWORD AddImm12_AssembleAdd32( DWORD rd, DWORD rn, ULONG imm, DWORD shift) { return AddImm12_Assemble(0, rd, rn, imm, shift); } static DWORD AddImm12_AssembleAdd64( DWORD rd, DWORD rn, ULONG imm, DWORD shift) { return AddImm12_Assemble(1, rd, rn, imm, shift); } typedef union { DWORD Assembled; struct { DWORD Rd : 5; // Destination register DWORD Imm19 : 19; // 19-bit upper immediate DWORD Opcode1 : 5; // Must be 10000 == 0x10 DWORD Imm2 : 2; // 2-bit lower immediate DWORD Type : 1; // 0 = ADR, 1 = ADRP } s; } Adr19; inline LONG Adr19_Imm( Adr19* p) { DWORD Imm = (p->s.Imm19 << 2) | p->s.Imm2; return (LONG)(Imm << 11) >> 11; } static DWORD Adr19_Assemble( DWORD type, DWORD rd, LONG delta) { Adr19 temp; temp.s.Rd = rd; temp.s.Imm19 = (delta >> 2) & 0x7ffff; temp.s.Opcode1 = 0x10; temp.s.Imm2 = delta & 3; temp.s.Type = type; return temp.Assembled; } static DWORD Adr19_AssembleAdr( DWORD rd, LONG delta) { return Adr19_Assemble(0, rd, delta); } static DWORD Adr19_AssembleAdrp( DWORD rd, LONG delta) { return Adr19_Assemble(1, rd, delta); } typedef union { DWORD Assembled; struct { DWORD Condition : 4; // Condition DWORD Opcode1 : 1; // Must be 0 DWORD Imm19 : 19; // 19-bit immediate DWORD Opcode2 : 8; // Must be 01010100 == 0x54 } s; } Bcc19; inline LONG Bcc19_Imm( Bcc19* p) { return (LONG)(p->s.Imm19 << 13) >> 11; } static DWORD Bcc19_AssembleBcc( DWORD condition, LONG delta) { Bcc19 temp; temp.s.Condition = condition; temp.s.Opcode1 = 0; temp.s.Imm19 = delta >> 2; temp.s.Opcode2 = 0x54; return temp.Assembled; } typedef union { DWORD Assembled; struct { DWORD Imm26 : 26; // 26-bit immediate DWORD Opcode1 : 5; // Must be 00101 == 0x5 DWORD Link : 1; // 0 = B, 1 = BL } s; } Branch26; inline LONG Branch26_Imm( Branch26* p) { return (LONG)(p->s.Imm26 << 6) >> 4; } static DWORD Branch26_Assemble( DWORD link, LONG delta) { Branch26 temp; temp.s.Imm26 = delta >> 2; temp.s.Opcode1 = 0x5; temp.s.Link = link; return temp.Assembled; } static DWORD Branch26_AssembleB( LONG delta) { return Branch26_Assemble(0, delta); } static DWORD Branch26_AssembleBl( LONG delta) { return Branch26_Assemble(1, delta); } typedef union { DWORD Assembled; struct { DWORD Opcode1 : 5; // Must be 00000 == 0 DWORD Rn : 5; // Register number DWORD Opcode2 : 22; // Must be 1101011000011111000000 == 0x3587c0 for Br // 0x358fc0 for Brl } s; } Br; static DWORD Br_Assemble( DWORD rn, BOOL link) { Br temp; temp.s.Opcode1 = 0; temp.s.Rn = rn; temp.s.Opcode2 = 0x3587c0; if (link) temp.Assembled |= 0x00200000; return temp.Assembled; } static DWORD Br_AssembleBr( DWORD rn) { return Br_Assemble(rn, FALSE); } static DWORD Br_AssembleBrl( DWORD rn) { return Br_Assemble(rn, TRUE); } typedef union { DWORD Assembled; struct { DWORD Rt : 5; // Register to test DWORD Imm19 : 19; // 19-bit immediate DWORD Nz : 1; // 0 = CBZ, 1 = CBNZ DWORD Opcode1 : 6; // Must be 011010 == 0x1a DWORD Size : 1; // 0 = 32-bit, 1 = 64-bit } s; } Cbz19; inline LONG Cbz19_Imm( Cbz19* p) { return (LONG)(p->s.Imm19 << 13) >> 11; } static DWORD Cbz19_Assemble( DWORD size, DWORD nz, DWORD rt, LONG delta) { Cbz19 temp; temp.s.Rt = rt; temp.s.Imm19 = delta >> 2; temp.s.Nz = nz; temp.s.Opcode1 = 0x1a; temp.s.Size = size; return temp.Assembled; } typedef union { DWORD Assembled; struct { DWORD Rt : 5; // Destination register DWORD Imm19 : 19; // 19-bit immediate DWORD Opcode1 : 2; // Must be 0 DWORD FpNeon : 1; // 0 = LDR Wt/LDR Xt/LDRSW/PRFM, 1 = LDR St/LDR Dt/LDR Qt DWORD Opcode2 : 3; // Must be 011 = 3 DWORD Size : 2; // 00 = LDR Wt/LDR St, 01 = LDR Xt/LDR Dt, 10 = LDRSW/LDR Qt, 11 = PRFM/invalid } s; } LdrLit19; inline LONG LdrLit19_Imm( LdrLit19* p) { return (LONG)(p->s.Imm19 << 13) >> 11; } static DWORD LdrLit19_Assemble( DWORD size, DWORD fpneon, DWORD rt, LONG delta) { LdrLit19 temp; temp.s.Rt = rt; temp.s.Imm19 = delta >> 2; temp.s.Opcode1 = 0; temp.s.FpNeon = fpneon; temp.s.Opcode2 = 3; temp.s.Size = size; return temp.Assembled; } typedef union { DWORD Assembled; struct { DWORD Rt : 5; // Destination register DWORD Rn : 5; // Base register DWORD Imm12 : 12; // 12-bit immediate DWORD Opcode1 : 1; // Must be 1 == 1 DWORD Opc : 1; // Part of size DWORD Opcode2 : 6; // Must be 111101 == 0x3d DWORD Size : 2; // Size (0=8-bit, 1=16-bit, 2=32-bit, 3=64-bit, 4=128-bit) } s; } LdrFpNeonImm9; static DWORD LdrFpNeonImm9_Assemble( DWORD size, DWORD rt, DWORD rn, ULONG imm) { LdrFpNeonImm9 temp; temp.s.Rt = rt; temp.s.Rn = rn; temp.s.Imm12 = imm; temp.s.Opcode1 = 1; temp.s.Opc = size >> 2; temp.s.Opcode2 = 0x3d; temp.s.Size = size & 3; return temp.Assembled; } typedef union { DWORD Assembled; struct { DWORD Rd : 5; // Destination register DWORD Imm16 : 16; // Immediate DWORD Shift : 2; // Shift amount (0=0, 1=16, 2=32, 3=48) DWORD Opcode : 6; // Must be 100101 == 0x25 DWORD Type : 2; // 0 = MOVN, 1 = reserved, 2 = MOVZ, 3 = MOVK DWORD Size : 1; // 0 = 32-bit, 1 = 64-bit } s; } Mov16; static DWORD Mov16_Assemble( DWORD size, DWORD type, DWORD rd, DWORD imm, DWORD shift) { Mov16 temp; temp.s.Rd = rd; temp.s.Imm16 = imm; temp.s.Shift = shift; temp.s.Opcode = 0x25; temp.s.Type = type; temp.s.Size = size; return temp.Assembled; } static DWORD Mov16_AssembleMovn32( DWORD rd, DWORD imm, DWORD shift) { return Mov16_Assemble(0, 0, rd, imm, shift); } static DWORD Mov16_AssembleMovn64( DWORD rd, DWORD imm, DWORD shift) { return Mov16_Assemble(1, 0, rd, imm, shift); } static DWORD Mov16_AssembleMovz32( DWORD rd, DWORD imm, DWORD shift) { return Mov16_Assemble(0, 2, rd, imm, shift); } static DWORD Mov16_AssembleMovz64( DWORD rd, DWORD imm, DWORD shift) { return Mov16_Assemble(1, 2, rd, imm, shift); } static DWORD Mov16_AssembleMovk32( DWORD rd, DWORD imm, DWORD shift) { return Mov16_Assemble(0, 3, rd, imm, shift); } static DWORD Mov16_AssembleMovk64( DWORD rd, DWORD imm, DWORD shift) { return Mov16_Assemble(1, 3, rd, imm, shift); } typedef union { DWORD Assembled; struct { DWORD Rt : 5; // Register to test DWORD Imm14 : 14; // 14-bit immediate DWORD Bit : 5; // 5-bit index DWORD Nz : 1; // 0 = TBZ, 1 = TBNZ DWORD Opcode1 : 6; // Must be 011011 == 0x1b DWORD Size : 1; // 0 = 32-bit, 1 = 64-bit } s; } Tbz14; inline LONG Tbz14_Imm( Tbz14* p) { return (LONG)(p->s.Imm14 << 18) >> 16; } static DWORD Tbz14_Assemble( DWORD size, DWORD nz, DWORD rt, DWORD bit, LONG delta) { Tbz14 temp; temp.s.Rt = rt; temp.s.Imm14 = delta >> 2; temp.s.Bit = bit; temp.s.Nz = nz; temp.s.Opcode1 = 0x1b; temp.s.Size = size; return temp.Assembled; } inline ULONG GetInstruction( _In_ BYTE* pSource) { return *(PULONG)pSource; } static PULONG EmitInstruction( _In_ PULONG pDstInst, ULONG instruction) { *pDstInst = instruction; return pDstInst + 1; } static PULONG EmitMovImmediate( PULONG pDstInst, BYTE rd, UINT64 immediate) { DWORD piece[4]; piece[3] = (DWORD)((immediate >> 48) & 0xffff); piece[2] = (DWORD)((immediate >> 32) & 0xffff); piece[1] = (DWORD)((immediate >> 16) & 0xffff); piece[0] = (DWORD)((immediate >> 0) & 0xffff); // special case: MOVN with 32-bit dest if (piece[3] == 0 && piece[2] == 0 && piece[1] == 0xffff) { pDstInst = EmitInstruction(pDstInst, Mov16_AssembleMovn32(rd, piece[0] ^ 0xffff, 0)); } // MOVN/MOVZ with 64-bit dest else { int zero_pieces = (piece[3] == 0x0000) + (piece[2] == 0x0000) + (piece[1] == 0x0000) + (piece[0] == 0x0000); int ffff_pieces = (piece[3] == 0xffff) + (piece[2] == 0xffff) + (piece[1] == 0xffff) + (piece[0] == 0xffff); DWORD defaultPiece = (ffff_pieces > zero_pieces) ? 0xffff : 0x0000; BOOL first = TRUE; for (int pieceNum = 3; pieceNum >= 0; pieceNum--) { DWORD curPiece = piece[pieceNum]; if (curPiece != defaultPiece || (pieceNum == 0 && first)) { if (first) { if (defaultPiece == 0xffff) { pDstInst = EmitInstruction(pDstInst, Mov16_AssembleMovn64(rd, curPiece ^ 0xffff, pieceNum)); } else { pDstInst = EmitInstruction(pDstInst, Mov16_AssembleMovz64(rd, curPiece, pieceNum)); } first = FALSE; } else { pDstInst = EmitInstruction(pDstInst, Mov16_AssembleMovk64(rd, curPiece, pieceNum)); } } } } return pDstInst; } static BYTE PureCopy32( _In_ PBYTE pSource, _In_ PBYTE pDest) { *(ULONG*)pDest = *(ULONG*)pSource; return sizeof(ULONG); } /////////////////////////////////////////////////////////// Disassembler Code. // static BYTE CopyAdr( BYTE* pSource, BYTE* pDest, ULONG instruction) { Adr19 decoded = { .Assembled = instruction }; PULONG pDstInst = (PULONG)(pDest); // ADR case if (decoded.s.Type == 0) { BYTE* pTarget = pSource + Adr19_Imm(&decoded); LONG64 delta = pTarget - pDest; LONG64 deltaPage = ((ULONG_PTR)pTarget >> 12) - ((ULONG_PTR)pDest >> 12); // output as ADR if (delta >= -(1 << 20) && delta < (1 << 20)) { pDstInst = EmitInstruction(pDstInst, Adr19_AssembleAdr(decoded.s.Rd, (LONG)delta)); } // output as ADRP; ADD else if (deltaPage >= -(1 << 20) && (deltaPage < (1 << 20))) { pDstInst = EmitInstruction(pDstInst, Adr19_AssembleAdrp(decoded.s.Rd, (LONG)deltaPage)); pDstInst = EmitInstruction(pDstInst, AddImm12_AssembleAdd32(decoded.s.Rd, decoded.s.Rd, ((ULONG)(ULONG_PTR)pTarget) & 0xfff, 0)); } // output as immediate move else { pDstInst = EmitMovImmediate(pDstInst, (BYTE)decoded.s.Rd, (ULONG_PTR)pTarget); } } // ADRP case else { BYTE* pTarget = (BYTE*)((((ULONG_PTR)pSource >> 12) + Adr19_Imm(&decoded)) << 12); LONG64 deltaPage = ((ULONG_PTR)pTarget >> 12) - ((ULONG_PTR)pDest >> 12); // output as ADRP if (deltaPage >= -(1 << 20) && (deltaPage < (1 << 20))) { pDstInst = EmitInstruction(pDstInst, Adr19_AssembleAdrp(decoded.s.Rd, (LONG)deltaPage)); } // output as immediate move else { pDstInst = EmitMovImmediate(pDstInst, (BYTE)decoded.s.Rd, (ULONG_PTR)pTarget); } } return (BYTE)((BYTE*)pDstInst - pDest); } static BYTE CopyBcc( _In_ PDETOUR_DISASM pDisasm, BYTE* pSource, BYTE* pDest, ULONG instruction) { Bcc19 decoded = { .Assembled = instruction }; PULONG pDstInst = (PULONG)(pDest); BYTE* pTarget = pSource + Bcc19_Imm(&decoded); pDisasm->pbTarget = pTarget; LONG64 delta = pTarget - pDest; LONG64 delta4 = pTarget - (pDest + 4); // output as BCC if (delta >= -(1 << 20) && delta < (1 << 20)) { pDstInst = EmitInstruction(pDstInst, Bcc19_AssembleBcc(decoded.s.Condition, (LONG)delta)); } // output as BCC ; B else if (delta4 >= -(1 << 27) && (delta4 < (1 << 27))) { pDstInst = EmitInstruction(pDstInst, Bcc19_AssembleBcc(decoded.s.Condition ^ 1, 8)); pDstInst = EmitInstruction(pDstInst, Branch26_AssembleB((LONG)delta4)); } // output as MOV x17, Target; BCC ; BR x17 (BIG assumption that x17 isn't being used for anything!!) else { pDstInst = EmitMovImmediate(pDstInst, 17, (ULONG_PTR)pTarget); pDstInst = EmitInstruction(pDstInst, Bcc19_AssembleBcc(decoded.s.Condition ^ 1, 8)); pDstInst = EmitInstruction(pDstInst, Br_AssembleBr(17)); } return (BYTE)((BYTE*)pDstInst - pDest); } static BYTE CopyB_or_Bl( _In_ PDETOUR_DISASM pDisasm, BYTE* pSource, BYTE* pDest, ULONG instruction, BOOL link) { Branch26 decoded = { .Assembled = instruction }; PULONG pDstInst = (PULONG)(pDest); BYTE* pTarget = pSource + Branch26_Imm(&decoded); pDisasm->pbTarget = pTarget; LONG64 delta = pTarget - pDest; // output as B or BRL if (delta >= -(1 << 27) && (delta < (1 << 27))) { pDstInst = EmitInstruction(pDstInst, Branch26_Assemble(link, (LONG)delta)); } // output as MOV x17, Target; BR or BRL x17 (BIG assumption that x17 isn't being used for anything!!) else { pDstInst = EmitMovImmediate(pDstInst, 17, (ULONG_PTR)pTarget); pDstInst = EmitInstruction(pDstInst, Br_Assemble(17, link)); } return (BYTE)((BYTE*)pDstInst - pDest); } static BYTE CopyB( _In_ PDETOUR_DISASM pDisasm, BYTE* pSource, BYTE* pDest, ULONG instruction) { return CopyB_or_Bl(pDisasm, pSource, pDest, instruction, FALSE); } static BYTE CopyBl( _In_ PDETOUR_DISASM pDisasm, BYTE* pSource, BYTE* pDest, ULONG instruction) { return CopyB_or_Bl(pDisasm, pSource, pDest, instruction, FALSE); } static BYTE CopyCbz( _In_ PDETOUR_DISASM pDisasm, BYTE* pSource, BYTE* pDest, ULONG instruction) { Cbz19 decoded = { .Assembled = instruction }; PULONG pDstInst = (PULONG)(pDest); BYTE* pTarget = pSource + Cbz19_Imm(&decoded); pDisasm->pbTarget = pTarget; LONG64 delta = pTarget - pDest; LONG64 delta4 = pTarget - (pDest + 4); // output as CBZ/NZ if (delta >= -(1 << 20) && delta < (1 << 20)) { pDstInst = EmitInstruction(pDstInst, Cbz19_Assemble(decoded.s.Size, decoded.s.Nz, decoded.s.Rt, (LONG)delta)); } // output as CBNZ/Z ; B else if (delta4 >= -(1 << 27) && (delta4 < (1 << 27))) { pDstInst = EmitInstruction(pDstInst, Cbz19_Assemble(decoded.s.Size, decoded.s.Nz ^ 1, decoded.s.Rt, 8)); pDstInst = EmitInstruction(pDstInst, Branch26_AssembleB((LONG)delta4)); } // output as MOV x17, Target; CBNZ/Z ; BR x17 (BIG assumption that x17 isn't being used for anything!!) else { pDstInst = EmitMovImmediate(pDstInst, 17, (ULONG_PTR)pTarget); pDstInst = EmitInstruction(pDstInst, Cbz19_Assemble(decoded.s.Size, decoded.s.Nz ^ 1, decoded.s.Rt, 8)); pDstInst = EmitInstruction(pDstInst, Br_AssembleBr(17)); } return (BYTE)((BYTE*)pDstInst - pDest); } static BYTE CopyTbz( _In_ PDETOUR_DISASM pDisasm, BYTE* pSource, BYTE* pDest, ULONG instruction) { Tbz14 decoded = { .Assembled = instruction }; PULONG pDstInst = (PULONG)(pDest); BYTE* pTarget = pSource + Tbz14_Imm(&decoded); pDisasm->pbTarget = pTarget; LONG64 delta = pTarget - pDest; LONG64 delta4 = pTarget - (pDest + 4); // output as TBZ/NZ if (delta >= -(1 << 13) && delta < (1 << 13)) { pDstInst = EmitInstruction(pDstInst, Tbz14_Assemble(decoded.s.Size, decoded.s.Nz, decoded.s.Rt, decoded.s.Bit, (LONG)delta)); } // output as TBNZ/Z ; B else if (delta4 >= -(1 << 27) && (delta4 < (1 << 27))) { pDstInst = EmitInstruction(pDstInst, Tbz14_Assemble(decoded.s.Size, decoded.s.Nz ^ 1, decoded.s.Rt, decoded.s.Bit, 8)); pDstInst = EmitInstruction(pDstInst, Branch26_AssembleB((LONG)delta4)); } // output as MOV x17, Target; TBNZ/Z ; BR x17 (BIG assumption that x17 isn't being used for anything!!) else { pDstInst = EmitMovImmediate(pDstInst, 17, (ULONG_PTR)pTarget); pDstInst = EmitInstruction(pDstInst, Tbz14_Assemble(decoded.s.Size, decoded.s.Nz ^ 1, decoded.s.Rt, decoded.s.Bit, 8)); pDstInst = EmitInstruction(pDstInst, Br_AssembleBr(17)); } return (BYTE)((BYTE*)pDstInst - pDest); } static BYTE CopyLdrLiteral( BYTE* pSource, BYTE* pDest, ULONG instruction) { LdrLit19 decoded = { .Assembled = instruction }; PULONG pDstInst = (PULONG)(pDest); BYTE* pTarget = pSource + LdrLit19_Imm(&decoded); LONG64 delta = pTarget - pDest; // output as LDR if (delta >= -(1 << 21) && delta < (1 << 21)) { pDstInst = EmitInstruction(pDstInst, LdrLit19_Assemble(decoded.s.Size, decoded.s.FpNeon, decoded.s.Rt, (LONG)delta)); } // output as move immediate else if (decoded.s.FpNeon == 0) { UINT64 value = 0; switch (decoded.s.Size) { case 0: value = *(ULONG*)pTarget; break; case 1: value = *(UINT64*)pTarget; break; case 2: value = *(LONG*)pTarget; break; } pDstInst = EmitMovImmediate(pDstInst, (BYTE)decoded.s.Rt, value); } // FP/NEON register: compute address in x17 and load from there (BIG assumption that x17 isn't being used for anything!!) else { pDstInst = EmitMovImmediate(pDstInst, 17, (ULONG_PTR)pTarget); pDstInst = EmitInstruction(pDstInst, LdrFpNeonImm9_Assemble(2 + decoded.s.Size, decoded.s.Rt, 17, 0)); } return (BYTE)((BYTE*)pDstInst - pDest); } static PBYTE CopyInstruction( _In_ PDETOUR_DISASM pDisasm, _In_opt_ PBYTE pDst, _In_ PBYTE pSrc, PBYTE* ppTarget, LONG* plExtra) { if (pDst == NULL) { pDst = pDisasm->rbScratchDst; } DWORD Instruction = GetInstruction(pSrc); ULONG CopiedSize; if ((Instruction & 0x1f000000) == 0x10000000) { CopiedSize = CopyAdr(pSrc, pDst, Instruction); } else if ((Instruction & 0xff000010) == 0x54000000) { CopiedSize = CopyBcc(pDisasm, pSrc, pDst, Instruction); } else if ((Instruction & 0x7c000000) == 0x14000000) { CopiedSize = CopyB_or_Bl(pDisasm, pSrc, pDst, Instruction, (Instruction & 0x80000000) != 0); } else if ((Instruction & 0x7e000000) == 0x34000000) { CopiedSize = CopyCbz(pDisasm, pSrc, pDst, Instruction); } else if ((Instruction & 0x7e000000) == 0x36000000) { CopiedSize = CopyTbz(pDisasm, pSrc, pDst, Instruction); } else if ((Instruction & 0x3b000000) == 0x18000000) { CopiedSize = CopyLdrLiteral(pSrc, pDst, Instruction); } else { CopiedSize = PureCopy32(pSrc, pDst); } // If the target is needed, store our target if (ppTarget) { *ppTarget = pDisasm->pbTarget; } if (plExtra) { *plExtra = CopiedSize - sizeof(DWORD); } return pSrc + 4; } #endif // defined(_ARM64_) PVOID NTAPI SlimDetoursCopyInstruction( _In_opt_ PVOID pDst, _In_ PVOID pSrc, _Out_opt_ PVOID* ppTarget, _Out_opt_ LONG* plExtra) { DETOUR_DISASM Disasm; #if defined(_AMD64_) || defined(_X86_) detour_disasm_init(&Disasm, (PBYTE*)ppTarget, plExtra); return (PVOID)CopyInstruction(&Disasm, (PBYTE)pDst, (PBYTE)pSrc); #elif defined(_ARM64_) detour_disasm_init(&Disasm); return (PVOID)CopyInstruction(&Disasm, (PBYTE)pDst, (PBYTE)pSrc, (PBYTE*)ppTarget, plExtra); #else return NULL; #endif } ================================================ FILE: src/windhawk/engine/libraries/MinHook-Detours/SlimDetours/InlineHook.c ================================================ /* * KNSoft.SlimDetours (https://github.com/KNSoft/KNSoft.SlimDetours) Inline Hook Wrappers * Copyright (c) KNSoft.org (https://github.com/KNSoft). All rights reserved. * Licensed under the MIT license. */ #include "SlimDetours.inl" HRESULT NTAPI SlimDetoursInlineHook( _In_ BOOL bEnable, _Inout_ PVOID* ppPointer, _In_ PVOID pDetour) { HRESULT hr; hr = SlimDetoursTransactionBegin(); if (FAILED(hr)) { return hr; } hr = bEnable ? SlimDetoursAttach(ppPointer, pDetour) : SlimDetoursDetach(ppPointer, pDetour); if (FAILED(hr)) { SlimDetoursTransactionAbort(); return hr; } return SlimDetoursTransactionCommit(); } HRESULT NTAPI SlimDetoursInitInlineHooks( _In_ HMODULE hModule, _In_ ULONG ulCount, _Inout_updates_(ulCount) PDETOUR_INLINE_HOOK pHooks) { NTSTATUS Status; ULONG i, uOridinal; ANSI_STRING FuncName, *pFuncName; for (i = 0; i < ulCount; i++) { if ((ULONG_PTR)pHooks[i].pszFuncName > MAXWORD) { Status = RtlInitAnsiStringEx(&FuncName, pHooks[i].pszFuncName); if (!NT_SUCCESS(Status)) { return HRESULT_FROM_NT(Status); } pFuncName = &FuncName; uOridinal = 0; } else { pFuncName = NULL; uOridinal = (ULONG)(ULONG_PTR)pHooks[i].pszFuncName; } Status = LdrGetProcedureAddress(hModule, pFuncName, uOridinal, pHooks[i].ppPointer); if (!NT_SUCCESS(Status)) { return HRESULT_FROM_NT(Status); } } return HRESULT_FROM_NT(STATUS_SUCCESS); } HRESULT NTAPI SlimDetoursInlineHooks( _In_ BOOL bEnable, _In_ ULONG ulCount, _Inout_updates_(ulCount) PDETOUR_INLINE_HOOK pHooks) { HRESULT hr; ULONG i; hr = SlimDetoursTransactionBegin(); if (FAILED(hr)) { return hr; } for (i = 0; i < ulCount; i++) { hr = bEnable ? SlimDetoursAttach(pHooks[i].ppPointer, pHooks[i].pDetour) : SlimDetoursDetach(pHooks[i].ppPointer, pHooks[i].pDetour); if (FAILED(hr)) { SlimDetoursTransactionAbort(); return hr; } } return SlimDetoursTransactionCommit(); } ================================================ FILE: src/windhawk/engine/libraries/MinHook-Detours/SlimDetours/Instruction.c ================================================ /* * KNSoft.SlimDetours (https://github.com/KNSoft/KNSoft.SlimDetours) Instruction Utility * Copyright (c) KNSoft.org (https://github.com/KNSoft). All rights reserved. * Licensed under the MIT license. * * Source base on Microsoft Detours: * * Microsoft Research Detours Package, Version 4.0.1 * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT license. */ #include "SlimDetours.inl" static BOOL detour_is_imported( _In_ PVOID pbCode, _In_ PVOID pbAddress) { NTSTATUS Status; MEMORY_BASIC_INFORMATION mbi; PIMAGE_DOS_HEADER pDosHeader; PIMAGE_NT_HEADERS pNtHeader; PVOID pEndOfMem; WORD wNtMagic; Status = NtQueryVirtualMemory(NtCurrentProcess(), pbCode, MemoryBasicInformation, &mbi, sizeof(mbi), NULL); if (!NT_SUCCESS(Status)) { return FALSE; } /* Type should be MEM_IMAGE */ if (mbi.Type != MEM_IMAGE) { return FALSE; } /* Cannot be uncommitted regions or guard pages */ if ((mbi.State != MEM_COMMIT) || ((mbi.Protect & 0xFF) == PAGE_NOACCESS) || (mbi.Protect & PAGE_GUARD)) { return FALSE; } /* * RegionSize should >= PAGE_SIZE and PAGE_SIZE always >= sizeof(IMAGE_DOS_HEADER), * so we can access IMAGE_DOS_HEADER safely without boundary check. */ _STATIC_ASSERT(PAGE_SIZE >= sizeof(IMAGE_DOS_HEADER)); if (mbi.RegionSize < PAGE_SIZE) { return FALSE; } /* Check IMAGE_DOS_HEADER */ pDosHeader = (PIMAGE_DOS_HEADER)mbi.AllocationBase; if (pDosHeader->e_magic != IMAGE_DOS_SIGNATURE) { return FALSE; } if (pDosHeader->e_lfanew < sizeof(*pDosHeader) || (ULONG)pDosHeader->e_lfanew > mbi.RegionSize) { return FALSE; } /* Now we need perform boundary check in every single step */ pEndOfMem = Add2Ptr(mbi.AllocationBase, mbi.RegionSize); /* * Step forward to IMAGE_NT_HEADERS and check IMAGE_NT_SIGNATURE, * check FileHeader.SizeOfOptionalHeader == 0 seems pointless * unless compare it with sizeof(IMAGE_OPTIONAL_HEADER) explicitly. */ pNtHeader = (PIMAGE_NT_HEADERS)Add2Ptr(pDosHeader, pDosHeader->e_lfanew); if (Add2Ptr(pNtHeader, sizeof(*pNtHeader)) > pEndOfMem) { return FALSE; } if (pNtHeader->Signature != IMAGE_NT_SIGNATURE) { return FALSE; } /* Step forward to IMAGE_OPTIONAL_HEADER and check magic */ _STATIC_ASSERT(UFIELD_OFFSET(IMAGE_OPTIONAL_HEADER, Magic) == 0); wNtMagic = pNtHeader->OptionalHeader.Magic; if (wNtMagic != IMAGE_NT_OPTIONAL_HDR_MAGIC || pNtHeader->FileHeader.SizeOfOptionalHeader != sizeof(IMAGE_OPTIONAL_HEADER)) { return FALSE; } if (pNtHeader->OptionalHeader.NumberOfRvaAndSizes <= IMAGE_DIRECTORY_ENTRY_IAT || pbAddress < Add2Ptr(mbi.AllocationBase, pNtHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IAT].VirtualAddress) || pbAddress >= Add2Ptr(mbi.AllocationBase, pNtHeader->OptionalHeader .DataDirectory[IMAGE_DIRECTORY_ENTRY_IAT].VirtualAddress + pNtHeader->OptionalHeader .DataDirectory[IMAGE_DIRECTORY_ENTRY_IAT].Size)) { return FALSE; } return TRUE; } #if defined(_X86_) || defined(_AMD64_) _Ret_notnull_ PBYTE detour_gen_jmp_immediate( _In_ PBYTE pbCode, _In_ PBYTE pbJmpVal) { PBYTE pbJmpSrc = pbCode + 5; *pbCode++ = 0xe9; // jmp +imm32 *((INT32*)pbCode) = (INT32)(pbJmpVal - pbJmpSrc); return pbCode + sizeof(INT32); } BOOL detour_is_jmp_immediate_to( _In_ PBYTE pbCode, _In_ PBYTE pbJmpVal) { PBYTE pbJmpSrc = pbCode + 5; if (*pbCode++ != 0xe9) // jmp +imm32 { return FALSE; } INT32 offset = *((INT32*)pbCode); return offset == (INT32)(pbJmpVal - pbJmpSrc); } _Ret_notnull_ PBYTE detour_gen_jmp_indirect( _In_ PBYTE pbCode, _In_ PBYTE* ppbJmpVal) { #if defined(_AMD64_) PBYTE pbJmpSrc = pbCode + 6; #endif *pbCode++ = 0xff; // jmp [+imm32] *pbCode++ = 0x25; #if defined(_AMD64_) *((INT32*)pbCode) = (INT32)((PBYTE)ppbJmpVal - pbJmpSrc); #else *((INT32*)pbCode) = (INT32)((PBYTE)ppbJmpVal); #endif return pbCode + sizeof(INT32); } BOOL detour_is_jmp_indirect_to( _In_ PBYTE pbCode, _In_ PBYTE* ppbJmpVal) { #if defined(_AMD64_) PBYTE pbJmpSrc = pbCode + 6; #endif if (*pbCode++ != 0xff) // jmp [+imm32] { return FALSE; } if (*pbCode++ != 0x25) { return FALSE; } INT32 offset = *((INT32*)pbCode); #if defined(_AMD64_) return offset == (INT32)((PBYTE)ppbJmpVal - pbJmpSrc); #else return offset == (INT32)((PBYTE)ppbJmpVal); #endif } _Ret_notnull_ PBYTE detour_gen_brk( _In_ PBYTE pbCode, _In_ PBYTE pbLimit) { while (pbCode < pbLimit) { *pbCode++ = 0xcc; // brk; } return pbCode; } _Ret_notnull_ PBYTE detour_skip_jmp( _In_ PBYTE pbCode) { PBYTE pbCodeOriginal; // First, skip over the import vector if there is one. if (pbCode[0] == 0xff && pbCode[1] == 0x25) { // Looks like an import alias jump, then get the code it points to. #if defined(_X86_) // jmp [imm32] PBYTE pbTarget = *(UNALIGNED PBYTE*) & pbCode[2]; #else // jmp [+imm32] PBYTE pbTarget = pbCode + 6 + *(UNALIGNED INT32*) & pbCode[2]; #endif if (detour_is_imported(pbCode, pbTarget)) { PBYTE pbNew = *(UNALIGNED PBYTE*)pbTarget; DETOUR_TRACE("%p->%p: skipped over import table.\n", pbCode, pbNew); pbCode = pbNew; } } // Then, skip over a patch jump if (pbCode[0] == 0xeb) { // jmp +imm8 PBYTE pbNew = pbCode + 2 + *(CHAR*)&pbCode[1]; DETOUR_TRACE("%p->%p: skipped over short jump.\n", pbCode, pbNew); pbCode = pbNew; pbCodeOriginal = pbCode; // First, skip over the import vector if there is one. if (pbCode[0] == 0xff && pbCode[1] == 0x25) { // Looks like an import alias jump, then get the code it points to. #if defined(_X86_) // jmp [imm32] PBYTE pbTarget = *(UNALIGNED PBYTE*) & pbCode[2]; #else // jmp [+imm32] PBYTE pbTarget = pbCode + 6 + *(UNALIGNED INT32*) & pbCode[2]; #endif if (detour_is_imported(pbCode, pbTarget)) { pbNew = *(UNALIGNED PBYTE*)pbTarget; DETOUR_TRACE("%p->%p: skipped over import table.\n", pbCode, pbNew); pbCode = pbNew; } } // Finally, skip over a long jump if it is the target of the patch jump. else if (pbCode[0] == 0xe9) { // jmp +imm32 pbNew = pbCode + 5 + *(UNALIGNED INT32*) & pbCode[1]; DETOUR_TRACE("%p->%p: skipped over long jump.\n", pbCode, pbNew); pbCode = pbNew; // Patches applied by the OS will jump through an HPAT page to get // the target function in the patch image. The jump is always performed // to the target function found at the current instruction pointer + PAGE_SIZE - 6 (size of jump). // If this is an OS patch, we want to detour at the point of the target function in the base image. if (pbCode[0] == 0xff && pbCode[1] == 0x25 && #if defined(_X86_) // Ideally, we would detour at the target function, but // since it's patched it begins with a short jump (to padding) which isn't long // enough to hold the detour code bytes. *(UNALIGNED INT32*)&pbCode[2] == (UNALIGNED INT32)(pbCode + PAGE_SIZE)) #else // Since we need 5 bytes to perform the jump, detour at the // point of the long jump instead of the short jump at the start of the target. *(UNALIGNED INT32*)&pbCode[2] == PAGE_SIZE - 6) #endif { // jmp [+PAGE_SIZE-6] DETOUR_TRACE("%p->%p: OS patch encountered, reset back to long jump 5 bytes prior to target function.\n", pbCode, pbCodeOriginal); pbCode = pbCodeOriginal; } } } return pbCode; } VOID detour_find_jmp_bounds( _In_ PBYTE pbCode, _Outptr_ PVOID* ppLower, _Outptr_ PVOID* ppUpper) { // We have to place trampolines within +/- 2GB of code. PVOID lo = detour_memory_2gb_below(pbCode); PVOID hi = detour_memory_2gb_above(pbCode); DETOUR_TRACE("[%p..%p..%p]\n", lo, pbCode, hi); // And, within +/- 2GB of relative jmp targets. if (pbCode[0] == 0xe9) { // jmp +imm32 PBYTE pbNew = pbCode + 5 + *(UNALIGNED INT32*) & pbCode[1]; if (pbNew < pbCode) { hi = detour_memory_2gb_above(pbNew); } else { lo = detour_memory_2gb_below(pbNew); } DETOUR_TRACE("[%p..%p..%p] +imm32\n", lo, pbCode, hi); } #if defined(_AMD64_) // And, within +/- 2GB of relative jmp vectors. else if (pbCode[0] == 0xff && pbCode[1] == 0x25) { // jmp [+imm32] PBYTE pbNew = pbCode + 6 + *(UNALIGNED INT32*) & pbCode[2]; if (pbNew < pbCode) { hi = detour_memory_2gb_above(pbNew); } else { lo = detour_memory_2gb_below(pbNew); } DETOUR_TRACE("[%p..%p..%p] [+imm32]\n", lo, pbCode, hi); } #endif *ppLower = lo; *ppUpper = hi; } BOOL detour_does_code_end_function( _In_ PBYTE pbCode) { if (pbCode[0] == 0xeb || // jmp +imm8 pbCode[0] == 0xe9 || // jmp +imm32 pbCode[0] == 0xe0 || // jmp eax pbCode[0] == 0xc2 || // ret +imm8 pbCode[0] == 0xc3 || // ret pbCode[0] == 0xcc) { // brk return TRUE; } else if (pbCode[0] == 0xf3 && pbCode[1] == 0xc3) { // rep ret return TRUE; } else if (pbCode[0] == 0xff && pbCode[1] == 0x25) { // jmp [+imm32] return TRUE; } else if ((pbCode[0] == 0x26 || // jmp es: pbCode[0] == 0x2e || // jmp cs: pbCode[0] == 0x36 || // jmp ss: pbCode[0] == 0x3e || // jmp ds: pbCode[0] == 0x64 || // jmp fs: pbCode[0] == 0x65) && // jmp gs: pbCode[1] == 0xff && // jmp [+imm32] pbCode[2] == 0x25) { return TRUE; } return FALSE; } ULONG detour_is_code_filler( _In_ PBYTE pbCode) { // 1-byte through 11-byte NOPs. if (pbCode[0] == 0x90) { return 1; } if (pbCode[0] == 0x66 && pbCode[1] == 0x90) { return 2; } if (pbCode[0] == 0x0F && pbCode[1] == 0x1F && pbCode[2] == 0x00) { return 3; } if (pbCode[0] == 0x0F && pbCode[1] == 0x1F && pbCode[2] == 0x40 && pbCode[3] == 0x00) { return 4; } if (pbCode[0] == 0x0F && pbCode[1] == 0x1F && pbCode[2] == 0x44 && pbCode[3] == 0x00 && pbCode[4] == 0x00) { return 5; } if (pbCode[0] == 0x66 && pbCode[1] == 0x0F && pbCode[2] == 0x1F && pbCode[3] == 0x44 && pbCode[4] == 0x00 && pbCode[5] == 0x00) { return 6; } if (pbCode[0] == 0x0F && pbCode[1] == 0x1F && pbCode[2] == 0x80 && pbCode[3] == 0x00 && pbCode[4] == 0x00 && pbCode[5] == 0x00 && pbCode[6] == 0x00) { return 7; } if (pbCode[0] == 0x0F && pbCode[1] == 0x1F && pbCode[2] == 0x84 && pbCode[3] == 0x00 && pbCode[4] == 0x00 && pbCode[5] == 0x00 && pbCode[6] == 0x00 && pbCode[7] == 0x00) { return 8; } if (pbCode[0] == 0x66 && pbCode[1] == 0x0F && pbCode[2] == 0x1F && pbCode[3] == 0x84 && pbCode[4] == 0x00 && pbCode[5] == 0x00 && pbCode[6] == 0x00 && pbCode[7] == 0x00 && pbCode[8] == 0x00) { return 9; } if (pbCode[0] == 0x66 && pbCode[1] == 0x66 && pbCode[2] == 0x0F && pbCode[3] == 0x1F && pbCode[4] == 0x84 && pbCode[5] == 0x00 && pbCode[6] == 0x00 && pbCode[7] == 0x00 && pbCode[8] == 0x00 && pbCode[9] == 0x00) { return 10; } if (pbCode[0] == 0x66 && pbCode[1] == 0x66 && pbCode[2] == 0x66 && pbCode[3] == 0x0F && pbCode[4] == 0x1F && pbCode[5] == 0x84 && pbCode[6] == 0x00 && pbCode[7] == 0x00 && pbCode[8] == 0x00 && pbCode[9] == 0x00 && pbCode[10] == 0x00) { return 11; } // int 3. if (pbCode[0] == 0xCC) { return 1; } return 0; } #endif // defined(_X86_) || defined(_AMD64_) #if defined(_ARM64_) inline ULONG fetch_opcode( PBYTE pbCode) { return *(ULONG*)pbCode; } inline PBYTE write_opcode( PBYTE pbCode, ULONG Opcode) { *(ULONG*)pbCode = Opcode; return pbCode + 4; } struct ARM64_INDIRECT_JMP { struct { ULONG Rd : 5; ULONG immhi : 19; ULONG iop : 5; ULONG immlo : 2; ULONG op : 1; } ardp; struct { ULONG Rt : 5; ULONG Rn : 5; ULONG imm : 12; ULONG opc : 2; ULONG iop1 : 2; ULONG V : 1; ULONG iop2 : 3; ULONG size : 2; } ldr; ULONG br; }; union ARM64_INDIRECT_IMM { struct { ULONG64 pad : 12; ULONG64 adrp_immlo : 2; ULONG64 adrp_immhi : 19; }; LONG64 value; }; _Ret_notnull_ PBYTE detour_gen_jmp_indirect( _In_ PBYTE pbCode, _In_ PULONG64 pbJmpVal) { // adrp x17, [jmpval] // ldr x17, [x17, jmpval] // br x17 struct ARM64_INDIRECT_JMP* pIndJmp; union ARM64_INDIRECT_IMM jmpIndAddr; jmpIndAddr.value = (((LONG64)pbJmpVal) & 0xFFFFFFFFFFFFF000) - (((LONG64)pbCode) & 0xFFFFFFFFFFFFF000); pIndJmp = (struct ARM64_INDIRECT_JMP*)pbCode; pbCode = (PBYTE)(pIndJmp + 1); pIndJmp->ardp.Rd = 17; pIndJmp->ardp.immhi = (ULONG)jmpIndAddr.adrp_immhi; pIndJmp->ardp.iop = 0x10; pIndJmp->ardp.immlo = (ULONG)jmpIndAddr.adrp_immlo; pIndJmp->ardp.op = 1; pIndJmp->ldr.Rt = 17; pIndJmp->ldr.Rn = 17; pIndJmp->ldr.imm = (((ULONG64)pbJmpVal) & 0xFFF) / 8; pIndJmp->ldr.opc = 1; pIndJmp->ldr.iop1 = 1; pIndJmp->ldr.V = 0; pIndJmp->ldr.iop2 = 7; pIndJmp->ldr.size = 3; pIndJmp->br = 0xD61F0220; return pbCode; } BOOL detour_is_jmp_indirect_to( _In_ PBYTE pbCode, _In_ PULONG64 pbJmpVal) { const struct ARM64_INDIRECT_JMP* pIndJmp; union ARM64_INDIRECT_IMM jmpIndAddr; jmpIndAddr.value = (((LONG64)pbJmpVal) & 0xFFFFFFFFFFFFF000) - (((LONG64)pbCode) & 0xFFFFFFFFFFFFF000); pIndJmp = (const struct ARM64_INDIRECT_JMP*)pbCode; return pIndJmp->ardp.Rd == 17 && pIndJmp->ardp.immhi == (ULONG)jmpIndAddr.adrp_immhi && pIndJmp->ardp.iop == 0x10 && pIndJmp->ardp.immlo == (ULONG)jmpIndAddr.adrp_immlo && pIndJmp->ardp.op == 1 && pIndJmp->ldr.Rt == 17 && pIndJmp->ldr.Rn == 17 && pIndJmp->ldr.imm == (((ULONG64)pbJmpVal) & 0xFFF) / 8 && pIndJmp->ldr.opc == 1 && pIndJmp->ldr.iop1 == 1 && pIndJmp->ldr.V == 0 && pIndJmp->ldr.iop2 == 7 && pIndJmp->ldr.size == 3 && pIndJmp->br == 0xD61F0220; } _Ret_notnull_ PBYTE detour_gen_jmp_immediate( _In_ PBYTE pbCode, _In_opt_ PBYTE* ppPool, _In_ PBYTE pbJmpVal) { PBYTE pbLiteral; if (ppPool != NULL) { *ppPool = *ppPool - 8; pbLiteral = *ppPool; } else { pbLiteral = pbCode + 8; } *((PBYTE*)pbLiteral) = pbJmpVal; LONG delta = (LONG)(pbLiteral - pbCode); pbCode = write_opcode(pbCode, 0x58000011 | ((delta / 4) << 5)); // LDR X17,[PC+n] pbCode = write_opcode(pbCode, 0xd61f0000 | (17 << 5)); // BR X17 if (ppPool == NULL) { pbCode += 8; } return pbCode; } _Ret_notnull_ PBYTE detour_gen_brk( _In_ PBYTE pbCode, _In_ PBYTE pbLimit) { while (pbCode < pbLimit) { pbCode = write_opcode(pbCode, 0xd4100000 | (0xf000 << 5)); } return pbCode; } inline INT64 detour_sign_extend( UINT64 value, UINT bits) { const UINT left = 64 - bits; const INT64 m1 = -1; const INT64 wide = (INT64)(value << left); const INT64 sign = (wide < 0) ? (m1 << left) : 0; return value | sign; } _Ret_notnull_ PBYTE detour_skip_jmp( _In_ PBYTE pbCode) { // Skip over the import jump if there is one. pbCode = (PBYTE)pbCode; ULONG Opcode = fetch_opcode(pbCode); if ((Opcode & 0x9f00001f) == 0x90000010) { // adrp x16, IAT ULONG Opcode2 = fetch_opcode(pbCode + 4); if ((Opcode2 & 0xffe003ff) == 0xf9400210) { // ldr x16, [x16, IAT] ULONG Opcode3 = fetch_opcode(pbCode + 8); if (Opcode3 == 0xd61f0200) { // br x16 /* https://static.docs.arm.com/ddi0487/bb/DDI0487B_b_armv8_arm.pdf The ADRP instruction shifts a signed, 21-bit immediate left by 12 bits, adds it to the value of the program counter with the bottom 12 bits cleared to zero, and then writes the result to a general-purpose register. This permits the calculation of the address at a 4KB aligned memory region. In conjunction with an ADD (immediate) instruction, or a Load/Store instruction with a 12-bit immediate offset, this allows for the calculation of, or access to, any address within +/- 4GB of the current PC. PC-rel. addressing This section describes the encoding of the PC-rel. addressing instruction class. The encodings in this section are decoded from Data Processing -- Immediate on page C4-226. Add/subtract (immediate) This section describes the encoding of the Add/subtract (immediate) instruction class. The encodings in this section are decoded from Data Processing -- Immediate on page C4-226. Decode fields Instruction page op 0 ADR 1 ADRP C6.2.10 ADRP Form PC-relative address to 4KB page adds an immediate value that is shifted left by 12 bits, to the PC value to form a PC-relative address, with the bottom 12 bits masked out, and writes the result to the destination register. ADRP ,