├── .github ├── ISSUE_TEMPLATE │ ├── config.yml │ ├── question.md │ ├── feature_request.yml │ └── bug_report.yml └── FUNDING.yml ├── .gitattributes ├── .gitignore ├── README.md ├── src ├── copy_locale.py ├── build_simplewall_rules.py ├── build_locale.py ├── helper.py ├── build_package.py └── setup_script.nsi └── pubkey.asc /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | blank_issues_enabled: false 2 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | *.c diff 2 | *.cpp diff 3 | *.h diff 4 | *.rc diff 5 | *.ini diff 6 | *.txt diff 7 | *.xml diff 8 | *.lng diff 9 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | custom: ["https://www.paypal.me/henrypp", "https://yoomoney.ru/to/4100115776040583", "https://www.blockchain.com/btc/address/1LrRTXPsvHcQWCNZotA9RcwjsGcRghG96c", "https://www.blockchain.com/eth/address/0xe2C84A62eb2a4EF154b19bec0c1c106734B95960"] 2 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/question.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Question 3 | about: Ask a question about application. 4 | title: '[Question]' 5 | labels: [ "question" ] 6 | assignees: '' 7 | 8 | --- 9 | 10 | Describe the question with as much detail as possible. 11 | 12 | --- 13 | 14 | App version: 15 | Windows version: -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .vs/ 2 | .vscode/ 3 | temp/ 4 | packages/ 5 | bin/32/ 6 | bin/64/ 7 | bin/arm64/ 8 | x64/ 9 | win32/ 10 | arm64/ 11 | __pycache__ 12 | 13 | thumbs.db 14 | ehthumbs.db 15 | desktop.ini 16 | 17 | *.cab 18 | *.msi 19 | *.msm 20 | *.msp 21 | *.lnk 22 | *.jpg 23 | *.zip 24 | *.log 25 | *.pdb 26 | *.exp 27 | *.sdf 28 | *.opensdf 29 | *.db 30 | *.opendb 31 | *.suo 32 | *.exp 33 | *.iobj 34 | *.ipch 35 | *.ipdb 36 | *.ilk 37 | *.obj 38 | *.res 39 | *.tlog 40 | 41 | *PVS-Studio* 42 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## builder 2 | 3 | ### Short description of what doing this scripts: 4 | 5 | - Build project portable `7z` package. 6 | - Build project installation package. 7 | - Update localization `.lng` file from `.ini` files ([as example](https://github.com/henrypp/builder#how-to-update-localization)). 8 | 9 | ### System requirements: 10 | 11 | - Python 3.13+ 12 | - 7-Zip 24+ (used from `%path%`) 13 | - GPG 2.5+ (used from `%path%`) 14 | - NSIS 3.10+ (used from `%path%`) 15 | 16 | ### How to update localization: 17 | 18 | - Sync this repository via:
19 | `git clone https://github.com/henrypp/builder.git`. 20 | - Sync one of my project via:
21 | `git clone https://github.com/henrypp/simplewall.git`. 22 | - Open cloned directory `simplewall\bin\i18n`. 23 | - Open any available `.ini` file or copy `!example.txt` into new localization filename (eg. `Russian.ini`) and save modified file. 24 | - Run `simplewall\build_locale.bat` 25 | - The script will update `simplewall\bin\simplewall.lng` localization file. 26 | --- 27 | - Website: [github.com/henrypp](https://github.com/henrypp) 28 | - Support: sforce5@mail.ru 29 | --- 30 | (c) 2018-2024 Henry++ 31 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.yml: -------------------------------------------------------------------------------- 1 | name: Feature request 2 | title: "[Feature]" 3 | description: Suggest an idea for this project. 4 | labels: [ "feature" ] 5 | body: 6 | - type: checkboxes 7 | id: checklist 8 | attributes: 9 | label: Checklist 10 | options: 11 | - label: I have used the search function to see if someone else has already submitted the same feature request. 12 | required: true 13 | - label: I will describe the problem with as much detail as possible. 14 | required: true 15 | - label: This issue only contains a request for one single feature, **not** multiple (related) features. 16 | required: true 17 | - type: input 18 | id: version 19 | attributes: 20 | label: App version 21 | description: What the application version do you use. 22 | placeholder: x.y.z 23 | validations: 24 | required: true 25 | - type: textarea 26 | id: problem 27 | attributes: 28 | label: Problem you are trying to solve 29 | description: Give a brief explanation of the problem you are trying to solve. 30 | validations: 31 | required: true 32 | - type: textarea 33 | id: solution 34 | attributes: 35 | label: Suggested solution 36 | description: Describe how you would like the app to help you solve that problem. Try to be as specific as possible. 37 | validations: 38 | required: true 39 | - type: textarea 40 | id: screenshots 41 | attributes: 42 | label: Screenshots / Drawings / Technical details 43 | description: | 44 | If your request is about (or includes) changing or extending the user interface (UI), describe what the UI would look like and how the user would interact with it. 45 | 46 | Tip: You can attach images or log files by clicking this area to highlight it and then dragging files in. 47 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.yml: -------------------------------------------------------------------------------- 1 | name: Bug report 2 | title: "[Bug]" 3 | description: Let me know about crash or existing functionality not working like it should. 4 | labels: [ "bug" ] 5 | body: 6 | - type: markdown 7 | attributes: 8 | value: Thanks for taking the time to fill out this bug report! 9 | - type: checkboxes 10 | id: checklist 11 | attributes: 12 | label: Checklist 13 | options: 14 | - label: I have used the search function to see if someone else has already submitted the same bug report. 15 | required: true 16 | - label: I will describe the problem with as much detail as possible. 17 | required: true 18 | - type: input 19 | id: version 20 | attributes: 21 | label: App version 22 | description: What the application version do you use. 23 | placeholder: x.y.z 24 | validations: 25 | required: true 26 | - type: input 27 | id: windows_version 28 | attributes: 29 | label: Windows version 30 | description: What Windows version do you use. 31 | validations: 32 | required: true 33 | - type: textarea 34 | id: steps 35 | attributes: 36 | label: Steps to reproduce 37 | placeholder: | 38 | 1. Go to '…' 39 | 2. Click on '…' 40 | 3. Scroll down to '…' 41 | 4. etc. 42 | validations: 43 | required: true 44 | - type: textarea 45 | id: expected 46 | attributes: 47 | label: Expected behavior 48 | description: After following the steps, what did you think application would do? 49 | validations: 50 | required: false 51 | - type: textarea 52 | id: actual 53 | attributes: 54 | label: Actual behavior 55 | description: What did application do instead? Screenshots might help. 56 | validations: 57 | required: true 58 | - type: textarea 59 | id: logs 60 | attributes: 61 | label: Logs 62 | description: | 63 | - If application have debug log insert it here. 64 | - If application have crash dump please attach it here. 65 | 66 | Tip: You can attach images, log files or crash dumps by clicking this area to highlight it and then dragging files in. 67 | -------------------------------------------------------------------------------- /src/copy_locale.py: -------------------------------------------------------------------------------- 1 | import configparser 2 | import argparse 3 | from helper import * 4 | 5 | # Colored terminal fix 6 | os.system ('') 7 | 8 | parser = argparse.ArgumentParser (add_help=False, description='Copy project locale.') 9 | parser.add_argument ('--name-src', help='project short name source', required=True) 10 | parser.add_argument ('--name-dst', help='project short name destination', required=True) 11 | parser.add_argument ('--locale-key', help='key to found and replace', required=True) 12 | 13 | args = parser.parse_args () 14 | 15 | APP_NAME_SHORT_SRC=args.name_src 16 | APP_NAME_SHORT_DST=args.name_dst 17 | LOCALE_KEY=args.locale_key 18 | 19 | # Set current script configuration 20 | CURRENT_DIRECTORY = os.path.dirname (os.path.abspath (__file__)) 21 | PROJECT_DIRECTORY_SRC = os.path.join (CURRENT_DIRECTORY, '..', '..', APP_NAME_SHORT_SRC) 22 | PROJECT_DIRECTORY_DST = os.path.join (CURRENT_DIRECTORY, '..', '..', APP_NAME_SHORT_DST) 23 | I18N_DIRECTORY_SRC = os.path.join (PROJECT_DIRECTORY_SRC, 'bin', 'i18n') 24 | I18N_DIRECTORY_DST = os.path.join (PROJECT_DIRECTORY_DST, 'bin', 'i18n') 25 | 26 | # Checking configuration 27 | log_status (status.TITLE, 'Checking configuration') 28 | 29 | check_path_with_status ('Project name (src)', PROJECT_DIRECTORY_SRC) 30 | check_path_with_status ('Project name (dst)', PROJECT_DIRECTORY_DST) 31 | check_path_with_status ('i18n directory (src)', I18N_DIRECTORY_SRC) 32 | check_path_with_status ('i18n directory (dst)', I18N_DIRECTORY_DST) 33 | 34 | log_status (status.SUCCESS, 'Key to copy "' + LOCALE_KEY + '" is set') 35 | 36 | log_status (status.TITLE, 'Start parsing localization') 37 | 38 | for name in os.listdir (I18N_DIRECTORY_SRC): 39 | if not name.endswith ('.ini'): 40 | continue 41 | 42 | locale_name = os.path.splitext (name)[0] 43 | locale_path_src = os.path.join (I18N_DIRECTORY_SRC, name) 44 | 45 | locale_ini_src = configparser.ConfigParser (delimiters='=', interpolation=None) 46 | locale_ini_src.optionxform = str 47 | 48 | locale_ini_src.read (locale_path_src, encoding='utf-16') 49 | 50 | if not locale_name in locale_ini_src or not LOCALE_KEY in locale_ini_src[locale_name]: 51 | log_status (status.FAILED, 'Locale "' + locale_name + '" key "' + LOCALE_KEY + '" was not found') 52 | 53 | else: 54 | locale_path_dst = os.path.join (I18N_DIRECTORY_DST, name) 55 | 56 | locale_ini_dst = configparser.ConfigParser (delimiters='=', interpolation=None) 57 | locale_ini_dst.optionxform = str 58 | 59 | locale_ini_dst.read (locale_path_dst, encoding='utf-16') 60 | 61 | if not locale_name in locale_ini_dst: 62 | log_status (status.FAILED, 'Locale "' + LOCALE_KEY + '" was incorrect') 63 | continue 64 | 65 | if not LOCALE_KEY in locale_ini_src[locale_name]: 66 | log_status (status.FAILED, 'Locale "' + LOCALE_KEY + '" was not found') 67 | continue 68 | 69 | locale_value = locale_ini_src.get (locale_name, LOCALE_KEY, fallback=None, raw=True) 70 | 71 | if not locale_value: 72 | log_status (status.FAILED, 'Locale "' + LOCALE_KEY + '" was empty in "' + locale_name + '"') 73 | continue 74 | 75 | if LOCALE_KEY in locale_ini_dst[locale_name] and locale_ini_dst[locale_name][LOCALE_KEY] == locale_value: 76 | log_status (status.WARNING, 'Locale "' + locale_name + '" was already the same') 77 | continue 78 | 79 | log_status (status.SUCCESS, 'Locale "' + locale_name + '" key was updated') 80 | 81 | locale_ini_dst.set (locale_name, LOCALE_KEY, locale_value) 82 | 83 | # Update locale 84 | locale_content = '' 85 | 86 | with open (locale_path_dst, mode='r', encoding='utf-16') as ini_file: 87 | lines = ini_file.readlines () 88 | ini_file.close () 89 | 90 | for line in lines: 91 | if line.startswith (';'): 92 | locale_content = locale_content + line 93 | else: 94 | break 95 | 96 | if locale_content: 97 | locale_content = locale_content + '\n' 98 | 99 | locale_content = locale_content + '[' + locale_name + ']\n' 100 | ini_file.close () 101 | 102 | for name,value in locale_ini_dst.items (locale_name, raw=True): 103 | locale_content = locale_content + name + '=' + value + '\n' 104 | 105 | with open (locale_path_dst, 'w', encoding='utf-16') as ini_file: 106 | ini_file.write (locale_content) 107 | ini_file.close () 108 | -------------------------------------------------------------------------------- /pubkey.asc: -------------------------------------------------------------------------------- 1 | -----BEGIN PGP PUBLIC KEY BLOCK----- 2 | 3 | mQINBFk9AoMBEADKsC82IjmxgX0keVaK/A9V2FJc8tuVxYNsm9iwPur/rjd5Cnea 4 | zbCcdGW5vQplydkbfUB9abN9YsR8uqLOUKq9dpfslrts2f3k3T3Y77ShYasgVgCG 5 | 6p1QfRYGoWAbeh/zD6I/CVnvHYYaJwb7oK6iDBplSWeMsfYaxo02Mf/Zth6sMUK0 6 | c9ym7gqB5fvDP/1l8EBG3T63i9BbIcoCntJixpo7t1vzqYfr0tBBR7h4PX2UIJeH 7 | ddrZ+ZEqECLmzLcIWzydmg6so8RghdesojOigQWtCjxnMjgeuy9GlytR8ulpv+Vs 8 | Gvq1expTaaNRLX2DtcxZ9jnJt4nGB4x3k59CQsvLh45AF6EBLuWQOqGuWSjdMjm1 9 | huVxFyrKW8AMvmdFKIo0y7IZ0Og370WUNjc9gPufrrXZCU/7f2m1scZTsDqeqvdy 10 | xzHM+lsNzfPRaGAdpQYROlcuf9TW82jr8v/twe1UfIUhPczZ6H0UZa2NTiUADvE0 11 | D9YPTkyE4s9cjSbLYyYFVAyiaDyj+Yc0H44JPC25JKqRO5N7IN07HaIUy07GOHDS 12 | 1F9Brhp0bYlVA/vOFZXn4TQ5gDRBtx3SDYh77LHZfHcxOTij6IbKR+igwJExlr20 13 | GZalCtVaZMwhmA1kfWahlvu3ahJVoP+crxxJCdULBUccwqqBK/kqmOXmowARAQAB 14 | tB1IZW5yeSsrIDxzdXBwb3J0QGhlbnJ5cHAub3JnPokCTwQTAQgAOQIbAwYLCQgH 15 | AwIGFQgCCQoLBBYCAwECHgECF4AWIQTZhSNhFSSrKb5zMKwogSCnVjW1/QUCYX4O 16 | 9AAKCRAogSCnVjW1/ZneD/9zubLZazpIn4uCsfvhAjO07K0UV7ZwwrD0DvOG4vwK 17 | NiqkoZ6Tvwk9mD/hpOgFpZMRd9Y4PUepW2Yk6PD6bFHmxN6sr/4myVELMPVumrJa 18 | vSHUZaWFIsGS8x6ZKhnwvHK3ITC3+uMCL4UZ9qTyusGGzgHNee3aRZMIteJUv/Qv 19 | jVbC2bT0eLtzrSx1UiKBxIA+8USjXIebVkgio3fvMv+p7XqisamVJNIT0Y/EyQZr 20 | d7JWCFqIrf1XAfyFPOiTaQpQCixu2mF1VcejCD+3Husvp5pujk9/Yd745syoTfTq 21 | 6lZJECjNz9yNqvU5pZLbjXLoN67rjdLQMGxklEpmbMVZs/B/JPerKLWwryk8jhWV 22 | urZ4PFxC0IFwzYefGL6qfa+uxDLdmCJyfubmskr3c5kbNJPqAoNYQcJuRUj/MINe 23 | UcuS59HxpkerA1GzqLe+8tBrNQ/LOKK/cme+3QONVKrU5TMnM4QOA24TrKYMKNbP 24 | U77arJ6i3/mTc9dMR5ThHb+mEnCf1uj2Gcl2TxliTX1KuPGLx8LQAoURSAlG51C4 25 | SLa1u3ZYXzp/UDwvhMq9O625OcDtvQjMGDT2iv9fucsR+ClpbYLSK5UpyFcAV5nA 26 | 57MlQVIeVLdQ0GKCbuQDXxopfXekv2959BpEst1Z86Ow3/sMQXHQTR7Ghc5P4QDO 27 | PIkBIgQQAQIADAUCWVwsPwUDABJ1AAAKCRCXELibyletfJMbCADE4iDOQkTw5Guf 28 | DXncu8hsyDNs/898MTyZMSViVsWYxugY2BVHxH9UOzZQMMKNbQ5N8BKlZKpXV813 29 | V/0CDf2vevDUXkTxrFY833HNE3eDRdorCS+c1xzJ0L/ItTWaN60PUrhhaztDGOrh 30 | P9dd9eQkZSWu/GKRCH73TWOaXJna2ZUQbpns14OHh/+XTYew6oXw8/KStflfmKSM 31 | ydfQAD1KuV1+Os15IUl7JC5oUK91Si40EkWbxQBPy2MvD8vQ8E/ZDAaIqXBDpGwB 32 | j2e8fdcPYbDAPVUwJCxVzHPTtKEih/yX1fgcEWLiEDY2A9A450NskG7FBHdmPu3K 33 | R2Os2BnbiQIcBBABCAAGBQJZelLvAAoJEJf54hNiDwcdQ5QQALTfFNmWqHF0/AEk 34 | MMXXr6nYMeibtsLuZdPpuNbca+06lConyrKrZgM/0E3SXg4cC2KvtAQj+vh0O155 35 | OFrR4UKa94T6TCMSI33MtqpK+NJjkjlHevyUW0YQM1w1kWrcKEDyJwjqs7+p2qMo 36 | GRtU+893OdTV8g6n/WA2qjE0VN1QLp75bgqRu4CYNPzzGQUz/MdpvT9NRrnMl33d 37 | HAbuu1fij8ZAoaooNypJqm7UVs1dDYC8YCNuWC2a/uPMRr57KEE6tGX9W30OMwC7 38 | k6urU9mMcfExrXEo0PoF9+DZ0rC4Vjq2ASy2jj1vVX6T0l7Kxp83zaHBh+mCVO0R 39 | eipGQzf9L2iExVF/QzymHg0396JCa7QUkZUhDsp8xBkOm9yHZ4h+2dt2KLhgMysl 40 | Khsz9ErZ0xIDuWg4/yZN6YVGgqPBj4vDbSI7atW9HB4nBlEgBcJ5rIPhwC9yYkBx 41 | EgTfjGXdiNtaHdWPAL3LBIkT2sS6Rph3AGQxE22en0au8+CvFcP/tHHVw4tefJvr 42 | WWmgLa0Tq8/MH+zz8Vo36xFH/KykWrQ5KZQGXvmgGO1Ma74J5yWifufSbfKU/oDd 43 | ZdUQ2Gt5rMoBrbrXQlw8WuLROqhRUASaYnYfzwZcKkugHuD7Trh8AF3ylCsR6mHb 44 | ZgTJMdNaglRpZGJKaX1QozvthXKziQJPBBMBAgA5AhsDBgsJCAcDAgYVCAIJCgsE 45 | FgIDAQIeAQIXgBYhBNmFI2EVJKspvnMwrCiBIKdWNbX9BQJbMIetAAoJECiBIKdW 46 | NbX96CIP/0E/Gecf6nxG9lyzg3NyCprFb93vuvvzUGKjB3W4Jgx755lodDPvqnbv 47 | 0aKOu4K6kdvp6av0g7UqPVbm2GohDJjwHsc4kO1fd8nOkSbkidM8rXhto+kUWIcK 48 | pDHTuI4/E+Xq/WPVcm60etrhQXkyFK25qBx7SNntyUVe+gP/kGl90oolH5svCObv 49 | z44jEG7NQc2JlHB69FWbq84YNNf+r6IgWe1LicmiI+qqv8649F17uYzg1E1KxYPZ 50 | dHEP9L89SniV936/92W5pKkcgBXOw+j1RJZkM27B+0LBGOIy3+aciZIg3OnL1fRR 51 | S56vNVXXeplm4o/2XmnlkhYWaBtBeCpUCtciUVp2gNJhRmvLseZNWpY5RBNWTU7E 52 | FsJyylm82fs2v2XZxXZbYVb4LuxbQd6f42N9sO63dRYgUnxabBO7Gb/03RFbKFdh 53 | 5GJVi+M5GnXiXQuJU67PB7RcuJiS8rw7YrbV2ZJC6LeNuPpUMeKPFJI3eJwCmwcY 54 | dVjowCiXZKYTWUNkusIPHoZsPprv5FQXYcBJleK0Lc0kqwtPlPRlCxSWxtl2F455 55 | g5visCmVKJmvK5pH/U81NoyuS3F6v7P2cS+Qpk3c+7pE14H4SalbE4GgGrlzVM/c 56 | DbgLv9LPhaPyc9I8dUp9PrlX8aRh0sbDhYXmH7oDJb4ePVW3nohSuQINBFk9AoMB 57 | EACpxmWGionK4leLYu09TOHuPJWcCGIEXrZPF1eeCkN53dt9k+pAMuTaMEx8IFua 58 | 9GziJvWh82k0hcMyQyPJBvj78aSz55bsZo9mUnG6P/Mq4135aR1tvovC5Qt+hYeQ 59 | xwqwlZRoQILBTF67Dt923Vo03emfP1V9iuINf2HvyBCxMo8hiTUDSMjAoQbD9NMn 60 | /xjcJ6+AUvKH99rEF24G9T3z5J7gfDrR9YoHoeVKKWbmHwZMjnBrfAk6nere2T08 61 | xqf0cfr6J7tNNB3W0cwHkXGGlxTmM88EEmxb/6W32CPXjyS/z+Lt+/Pt7dqbYoON 62 | vNSw3knytSlSVZD7fRdmRXWMbmqvexQJB0kPrOY72bAcznasCgZjTENsHhe+FYkA 63 | Ifrq6IsI1VqqWmwmZhCYhbJzK2TYBMz0U5Pa+mPdgiRrJf2lNrJgubDy4mo+6yJ3 64 | OmJ4/1TzZzZR4myYIMMK8hvLbFz7PLwDvjs9ov6QmtMTw78GrBXMNd4b01nQ4l19 65 | ak12G9h8Nysw/MR/t7gMd1yBSt8qV/6xxTuSJH09FNA9Qp4JbntmIqXHnytXhLXN 66 | VdT1ZC+LYfX84DVhj1shBLv0JlXkh8S1mBAm70uA+YOC3/RY5PETeCF3fY31xDNS 67 | 2kbQfcND2cXDho9JFrKKO0a088OnpQ76+EnDEFXTlhPGVwARAQABiQIlBBgBAgAP 68 | BQJZPQKDAhsMBQkB4TOAAAoJECiBIKdWNbX99g8P/RbwgjwJoz5yiCDnHbrGYbrl 69 | T4BWExmAYOPnScOz/VUw6VH5kyAT0BNhH4KeEkFSvbmNrhCkM4/SBVeo6pJZTfgb 70 | ItG75j1sJeaRpB6sIspIWpf5yXs+qC84XCXUi/FeLxvP2Xiu0QiAOVjAmD/aRajY 71 | U9OMKN/Fg4Pn4UZjyUmIpVLwQteKOPXuXpmkGwzxwpa6AqHxEheDWk0ZTI7zmKba 72 | gh5X3BlQ22ByrsZqfmEdpHKn7wFaYWEJRaCMWAuqDMYONNOTHtkFPXkTOONYmIHh 73 | 60gYmjIVFPM8n8xYDH0tD4KgbmyfGiOg0s4XgtHshXFxAoTmFURYfzMZqRqo3xsT 74 | TvpnfECJNtseyoFrVr/VtnNKEnx4/UWUGgk+2YctrOm2iEGzroYOT5ZOntlWw1mT 75 | qHO0aLy05ahcWOIJ7G25h8gpRa7K0p+0ajro5/GR1+gBvRat0D+InZAnZG89GWPd 76 | ucvud3+IeQUpbk0n/PIJy3RlGr/4rF9lCa4voW7iHxjQPjRrQXRSctqjIVLNuS8L 77 | vQDfcwRQW2zs0Edp1HO3kgD5Fi3IuRfnG6wJQPiocM0ViTzBI00J9Hd4AbMMtpQw 78 | k1W5euifv5X2FwpX9UfS70b8lJWzLEaLmPPNCcB+psBRlxhg/APAlmXvjAwoN5dL 79 | w/9uRUrh5iWZZCg2L385iQI2BBgBAgAgAhsMFiEE2YUjYRUkqym+czCsKIEgp1Y1 80 | tf0FAlswiA8ACgkQKIEgp1Y1tf3WhBAApgjbECuneV1H5L3j6V5dc3AwJTlQa+Pl 81 | V24EHsr18SYG3OAq3YGXh4uYtcA6OpQ3RF3XX6yxVqIfkghfOHW11Ai5tgKpfTs5 82 | OubvI8JUvJCirj/qsudbRSQbq9SztKspx5yLT+Q+VL/w4IyN+JAP2xKS5drZvf4L 83 | JEGuEHxBQb47a/PiyR9lKF4j9iQuJIpl0+Jtj+y4gzLHYKwB5Hv1Iiomal1Ig7Dm 84 | diL7LPNYxiaFK9fMYDXTGIsLWa9f+Hw/29SUdhsvL5zrPozJQ/tZWyRiO58f0TXL 85 | MdkKK0ZyppvfmDzASrsEcflZFlqXiiKBNACFP84Jd9gpD1HyGQn7oKUS52HUi84H 86 | QEkOOJt75nDlEqHsC6iVmJj9F9fv8FWP5rjXSVGHq4+9CC+UHdPdYbD+/V6XYcb6 87 | /x0E/EF1UPpjwJV/WYiWolNkqSxThR5acr1VXwUnhdbdWJDH6oc7T25dlkTQ3rqB 88 | zXYvfQ/QkT0Wt/p3ZX9usqosG3dCZPL4RJmLR2K0oLmmDiiRbw15DwyWcQpjVFZv 89 | MtO4sO23+HzMwwqWKZjaZKHgFGqqPtSC/koFdrKpiikZnpk5ncRmb2Xi/D+tjYQM 90 | xvYTs94FVQuYtiTfgsfYhyJ1hz38zdEFz0KqVDTtblVyGmi/8Gq/Yr8SF1+KBrii 91 | xQfe3cfwUi8= 92 | =1zZe 93 | -----END PGP PUBLIC KEY BLOCK----- 94 | -------------------------------------------------------------------------------- /src/build_simplewall_rules.py: -------------------------------------------------------------------------------- 1 | import xml.dom.minidom 2 | import hashlib 3 | 4 | import argparse 5 | from helper import * 6 | 7 | parser = argparse.ArgumentParser (add_help=False, description='Update internal rules.') 8 | parser.add_argument ('--mode', help='working mode (update/unpack/pack)', required=True) 9 | parser.add_argument ('--i', help='in file', required=True) 10 | parser.add_argument ('--o', help='out file', required=True) 11 | 12 | args = parser.parse_args () 13 | 14 | ARG_MODE=args.mode 15 | ARG_FILE_IN=args.i 16 | ARG_FILE_OUT=args.o 17 | 18 | CURRENT_DIRECTORY = os.path.dirname (os.path.abspath (__file__)) 19 | PROJECT_DIRECTORY = os.path.join (CURRENT_DIRECTORY, '..', '..', ARG_FILE_IN) 20 | WSB_DIR = os.path.join (CURRENT_DIRECTORY, '..', '..', '!repos', 'WindowsSpyBlocker', 'data', 'firewall') 21 | 22 | # File information 23 | PROFILE2_FOURCC = b'\x73\x77\x63\x31' 24 | PROFILE2_FOURCC_LENGTH = len (PROFILE2_FOURCC) 25 | PROFILE2_HASH_LENGTH = 32 26 | 27 | check_path_with_status ('Profile file', ARG_FILE_IN, True) 28 | check_path_with_status ('WSB directory', WSB_DIR) 29 | 30 | if ARG_MODE != 'update' and ARG_MODE != 'pack' and ARG_MODE != 'unpack': 31 | log_status (status.FAILED, 'Unknown mode specified "%s"' % (ARG_MODE)) 32 | os.sys.exit ('') 33 | 34 | def get_hash (buffer): 35 | return hashlib.sha256 (buffer).digest () 36 | 37 | def is_valid_hash (buffer, compare_hash): 38 | current_hash = get_hash (buffer) 39 | 40 | return (current_hash == compare_hash) 41 | 42 | def is_valid_signature (signature): 43 | return (signature == PROFILE2_FOURCC) 44 | 45 | def parse_xml (data): 46 | # toprettyxml() hack 47 | data = data.replace ('\n', '') 48 | data = data.replace ('\t', '') 49 | 50 | xml_doc = xml.dom.minidom.parseString (data) 51 | xml_root = xml_doc.getElementsByTagName ('root') 52 | 53 | return xml_doc, xml_root 54 | 55 | def save_file (file_path, buffer): 56 | with open (file_path, 'wb') as fn: 57 | fn.write (buffer) 58 | fn.close () 59 | 60 | def load_profile (file_path, mode): 61 | with open (file_path, 'rb') as fn: 62 | 63 | # read file size 64 | fn.seek (0, os.SEEK_END) 65 | 66 | if fn.tell () <= (PROFILE2_FOURCC_LENGTH + PROFILE2_HASH_LENGTH): 67 | log_status (status.FAILED, 'Length failure: "' + get_file_name (file_path) + '"') 68 | fn.close () 69 | 70 | return None 71 | 72 | fn.seek (0, os.SEEK_SET) # restore position 73 | 74 | # read signature value 75 | signature_value = fn.read (PROFILE2_FOURCC_LENGTH) 76 | 77 | if not is_valid_signature (signature_value): 78 | log_status (status.FAILED, 'Signature failure: "' + get_file_name (file_path) + '"') 79 | fn.close () 80 | 81 | return None 82 | 83 | # read sha256 value 84 | hash_value = fn.read (PROFILE2_HASH_LENGTH) 85 | lznt_buffer = fn.read () 86 | 87 | fn.close () 88 | 89 | buffer_data = (len (lznt_buffer) * ctypes.c_ubyte).from_buffer_copy (lznt_buffer) 90 | buffer_data = unpack_buffer_lznt (buffer_data) 91 | 92 | if not buffer_data: 93 | log_status (status.FAILED, 'Decompression failure: "' + get_file_name (file_path) + '"') 94 | return None 95 | 96 | if not is_valid_hash (buffer_data, hash_value): 97 | log_status (status.FAILED, 'Hash failure: "' + get_file_name (file_path) + '"') 98 | return None 99 | 100 | return buffer_data 101 | 102 | def save_profile (file_path, hash_value, xml_buffer, mode): 103 | if mode == 'unpack': 104 | log_status (status.SUCCESS, 'Write %s size...' % (format_size (len (xml_buffer)))) 105 | 106 | with open (file_path, 'wb') as fn: 107 | fn.write (xml_buffer) 108 | fn.close () 109 | elif mode == 'update' or mode == 'pack': 110 | buffer_length = len (xml_buffer) 111 | buffer_data = (buffer_length * ctypes.c_ubyte).from_buffer_copy (xml_buffer) 112 | 113 | buffer_data = pack_buffer_lznt (buffer_data) 114 | 115 | if not buffer_data: 116 | log_status (status.FAILED, 'Compression failed...') 117 | return 118 | 119 | log_status (status.SUCCESS, 'Compress buffer with %s size to %s (%s saved)...' % (format_size (buffer_length), format_size (len (buffer_data)), format_size (buffer_length - len (buffer_data)))) 120 | 121 | with open (file_path, 'wb') as fn: 122 | fn.write (PROFILE2_FOURCC) # compressed profile signature 123 | fn.write (hash_value) # sha256 hash of uncompressed data 124 | 125 | fn.write (buffer_data) 126 | 127 | fn.close () 128 | 129 | # Start parse 130 | buffer = load_profile (ARG_FILE_IN, ARG_MODE) 131 | 132 | if not buffer: 133 | log_status (status.FAILED, 'Loading failure...') 134 | os.sys.exit ('') 135 | 136 | buffer_hash = get_hash (buffer) 137 | buffer = bytes (buffer).decode ('utf-8') 138 | 139 | xml_doc, xml_root = parse_xml (buffer) 140 | 141 | # Get timestamp 142 | timestamp = int (xml_root[0].getAttribute ('timestamp')) 143 | total_rules_count = 0 144 | 145 | if ARG_MODE == 'update': 146 | # Cleanup xml 147 | for node in xml_doc.getElementsByTagName ('rules_blocklist'): 148 | parent = node.parentNode 149 | parent.removeChild (node) 150 | 151 | xml_root[0].appendChild (xml_doc.createElement ('rules_blocklist')) 152 | xml_section = xml_doc.getElementsByTagName ('rules_blocklist') 153 | 154 | if not xml_section: 155 | raise Exception ('Parse xml failure.') 156 | 157 | # Enumerate Windows Spy Blocker spy/extra and update rules 158 | for file_name in os.listdir (WSB_DIR): 159 | module_name = os.path.splitext (file_name)[0] 160 | module_path = os.path.join (WSB_DIR, file_name) 161 | 162 | log_status (status.TITLE, 'Parsing "' + module_name + '" rules') 163 | 164 | lastmod = int (os.path.getmtime (module_path)) 165 | 166 | if lastmod > timestamp: 167 | timestamp = lastmod 168 | 169 | with open (module_path, 'r') as fn: 170 | rows = fn.readlines () 171 | natural_sort (rows) 172 | fn.close () 173 | 174 | count = 0 175 | 176 | for string in rows: 177 | line = string.strip ('\n\r\t ') 178 | 179 | if not line or line.startswith ('#') or line.startswith ('<') or line.startswith ('>') or line.startswith ('='): 180 | continue 181 | 182 | count += 1 183 | 184 | new_item = xml_doc.createElement ('item') 185 | 186 | new_item.setAttribute ('name', module_name + '_' + line) 187 | new_item.setAttribute ('rule', line) 188 | 189 | xml_section[0].appendChild (new_item) 190 | 191 | if count != 0: 192 | log_status (status.SUCCESS, 'Found %i %s rules...' % (count, module_name)) 193 | else: 194 | log_status (status.WARNING, 'Not found %s rules...' % (module_name)) 195 | 196 | total_rules_count += count 197 | 198 | # Set new rule timestamp 199 | xml_root[0].setAttribute ('timestamp', str (timestamp)) 200 | 201 | # Generate xml string 202 | data = xml_doc.toprettyxml (indent='\t', newl='\n') 203 | 204 | if not data: 205 | raise Exception ('Write xml failure.') 206 | 207 | data = data.replace ('/>', ' />') 208 | data = bytes (data, 'utf-8') 209 | 210 | # Save profile file 211 | log_status (status.TITLE, 'Saving file') 212 | 213 | save_profile (ARG_FILE_OUT, buffer_hash, data, ARG_MODE) 214 | 215 | print ('\nBlocklist rules count: %s\nBlocklist timestamp: %s\n' % (str (total_rules_count), str (timestamp))) 216 | -------------------------------------------------------------------------------- /src/build_locale.py: -------------------------------------------------------------------------------- 1 | import argparse 2 | import stat 3 | from helper import * 4 | 5 | def find_and_set (dct, name, value): 6 | for k,v in dct.items (): 7 | if v['name'] == name: 8 | v['value'] = value 9 | return 10 | 11 | log_status (status.FAILED, 'String was not found: "' + name + '"') 12 | 13 | # https://www.alchemysoftware.com/livedocs/ezscript/Topics/Catalyst/Language.htm 14 | def get_locale_name (name): 15 | ldict = { 16 | 'Azerbaijani': 'az', 17 | 'Belarusian': 'be', 18 | 'Bosnian': 'bs', 19 | 'Bulgarian': 'bg', 20 | 'Catalan': 'ca', 21 | 'Chinese': 'zh', 22 | 'Chinese (Simplified)': 'zh-CN', 23 | 'Chinese (Traditional)': 'zh-TW', 24 | 'Czech': 'cs', 25 | 'Dutch': 'nl', 26 | 'Finnish': 'fi', 27 | 'French': 'fr', 28 | 'German': 'de', 29 | 'Hungarian': 'hu', 30 | 'Indonesian': 'id', 31 | 'Italian': 'it', 32 | 'Japanese': 'ja', 33 | 'Kazakh': 'kk', 34 | 'Korean': 'ko', 35 | 'Persian': 'fa', 36 | 'Polish': 'pl', 37 | 'Portuguese': 'pt', 38 | 'Portuguese (Brazil)': 'pt-BR', 39 | 'Portuguese (Portugal)': 'pt-PT', 40 | 'Romanian': 'ro', 41 | 'Russian': 'ru', 42 | 'Serbian': 'sr', 43 | 'Slovak': 'sk', 44 | 'Spanish': 'es', 45 | 'Swedish': 'sv', 46 | 'Turkish': 'tr', 47 | 'Ukrainian': 'uk', 48 | } 49 | 50 | new_name = None 51 | 52 | for lname, lcode in ldict.items(): 53 | if name.lower () == lname.lower (): 54 | new_name = lcode 55 | break 56 | 57 | if not new_name: 58 | log_status (status.WARNING, 'Locale name was not found: "' + name + '"') 59 | new_name = name 60 | 61 | return new_name 62 | 63 | parser = argparse.ArgumentParser (add_help=False, description='Build project locale.') 64 | parser.add_argument ('--name-short', help='project short name', required=True) 65 | 66 | args = parser.parse_args () 67 | 68 | APP_NAME_SHORT=args.name_short 69 | 70 | # Set current script configuration 71 | CURRENT_DIRECTORY = os.path.dirname (os.path.abspath (__file__)) 72 | PROJECT_DIRECTORY = os.path.join (CURRENT_DIRECTORY, '..', '..', APP_NAME_SHORT) 73 | I18N_DIRECTORY = os.path.join (PROJECT_DIRECTORY, 'bin', 'i18n') 74 | EXAMPLE_DIRECTORY = os.path.join (I18N_DIRECTORY, '!example.txt') 75 | LOCALE_FILE = os.path.join (PROJECT_DIRECTORY, 'bin', APP_NAME_SHORT + '.lng') 76 | RESOURCE_RC = os.path.join (PROJECT_DIRECTORY, 'src', 'resource.rc') 77 | RESOURCE_H = os.path.join (PROJECT_DIRECTORY, 'src', 'resource.h') 78 | 79 | # Checking configuration 80 | log_status (status.TITLE, 'Checking configuration') 81 | 82 | check_path_with_status ('Project name', PROJECT_DIRECTORY) 83 | check_path_with_status ('Resource header', RESOURCE_H, True) 84 | check_path_with_status ('Resource code', RESOURCE_RC, True) 85 | 86 | ID_ACCELERATOR = 1 87 | ID_CONTROL = 100 88 | ID_DIALOG = 100 89 | ID_ICON = 100 90 | ID_RCDATA = 1 91 | ID_STRING = 1 92 | ID_PTR = 100 93 | 94 | strings_array = {} 95 | 96 | # Update resource header 97 | log_status (status.TITLE, 'Update resource header') 98 | 99 | clr = status.FAILED 100 | 101 | with open (RESOURCE_H, 'r') as fn: 102 | lines = fn.readlines () 103 | fn.close () 104 | 105 | header_content = '' 106 | 107 | for line in lines: 108 | line = line.strip ('\r\n') 109 | 110 | if not line.startswith ('#define '): 111 | header_content += line + '\n' 112 | 113 | else: 114 | var = line.split (' ') 115 | 116 | if len (var) < 3 or not var[1] or not var[2] or not var[2].strip ().isdigit (): 117 | header_content += line + '\n' 118 | 119 | else: 120 | res_name = var[1].strip () 121 | res_id = int (var[2].strip ()) 122 | 123 | if res_name.startswith ('IDC_') or res_name.startswith ('IDM_'): 124 | res_id = ID_CONTROL 125 | ID_CONTROL += 1 126 | 127 | elif res_name.startswith ('IDS_'): 128 | res_id = ID_STRING 129 | ID_STRING += 1 130 | 131 | strings_array[str (res_id)] = {'name': res_name, 'value': ''} 132 | 133 | elif res_name.startswith ('IDI_'): 134 | res_id = ID_ICON 135 | ID_ICON += 1 136 | 137 | elif res_name.startswith ('IDD_'): 138 | res_id = ID_DIALOG 139 | ID_DIALOG += 1 140 | 141 | elif res_name.startswith ('IDR_'): 142 | res_id = ID_RCDATA 143 | ID_RCDATA += 1 144 | 145 | elif res_name.startswith ('IDA_'): 146 | res_id = ID_ACCELERATOR 147 | ID_ACCELERATOR += 1 148 | 149 | elif res_name.startswith ('IDP_'): 150 | res_id = ID_PTR 151 | ID_PTR += 1 152 | 153 | header_content += '#define ' + res_name + ' ' + str (res_id) + '\n' 154 | 155 | if header_content: 156 | with open (RESOURCE_H, 'w', newline='\r\n') as fn: 157 | clr = status.SUCCESS 158 | 159 | fn.write (header_content) 160 | fn.close () 161 | 162 | log_status (clr, 'Update header content "' + get_file_name (RESOURCE_H) + '"') 163 | 164 | log_status (status.TITLE, 'Checking strings resources') 165 | 166 | if len (strings_array): 167 | clr = status.SUCCESS 168 | else: 169 | clr = status.FAILED 170 | 171 | log_status (clr, str (len (strings_array)) + ' string(s) was found') 172 | 173 | if clr == status.FAILED: 174 | os.sys.exit () 175 | 176 | # Parse strings from resource code 177 | log_status (status.TITLE, 'Parse strings from resource code') 178 | 179 | clr = status.FAILED 180 | 181 | with open (RESOURCE_RC, 'r') as fn: 182 | lines = fn.readlines () 183 | 184 | for line in lines: 185 | line = line.strip ('\t\r\n ') 186 | 187 | if line.startswith ('IDS_'): 188 | part = line.partition (' ') 189 | 190 | if part[0] and part[2]: 191 | key = part[0].strip ('\t "') 192 | 193 | start_pos = str.index (part[2], '"') 194 | end_pos= str.rindex (part[2], '"') 195 | 196 | val = part[2][start_pos:end_pos] 197 | val = val.replace ('""', '"').strip ('\t "') 198 | 199 | if key and val: 200 | find_and_set (strings_array, key, val) 201 | clr = status.SUCCESS 202 | 203 | fn.close () 204 | 205 | log_status (clr, 'Parsing was finished') 206 | 207 | if clr != status.SUCCESS: 208 | os.sys.exit () 209 | 210 | # Create locale example file 211 | log_status (status.TITLE, 'Create locale example file') 212 | 213 | clr = status.FAILED 214 | 215 | with open (EXAMPLE_DIRECTORY, 'w', encoding='utf-16', newline='\r\n') as fn: 216 | fn.write ('; \n' + '; ' + '\n\n' + '[]\n') 217 | 218 | for k,v in strings_array.items (): 219 | if v['value']: 220 | clr = status.SUCCESS 221 | fn.write (v['name'] + '=' + v['value'] + '\n') 222 | 223 | fn.close () 224 | 225 | log_status (clr, 'Write file "' + get_file_name (EXAMPLE_DIRECTORY) + '" was finished') 226 | 227 | if not os.path.isdir (I18N_DIRECTORY): 228 | log_status (bcolor.FAILED, 'Locale directory "' + I18N_DIRECTORY + '" was not found.') 229 | os.sys.exit () 230 | 231 | # Get localization timestamp 232 | i18n_files = os.listdir (I18N_DIRECTORY) 233 | 234 | locale_timestamp = 0 235 | locale_lastname = '' 236 | 237 | for name in i18n_files: 238 | if not name.endswith ('.ini'): 239 | continue 240 | 241 | locale_name = os.path.splitext (name)[0] 242 | locale_path = os.path.join (I18N_DIRECTORY, name) 243 | 244 | lastmod = os.path.getmtime (locale_path); 245 | 246 | if lastmod > locale_timestamp: 247 | locale_timestamp = lastmod 248 | locale_lastname = locale_name 249 | 250 | locale_timestamp = int (locale_timestamp) 251 | 252 | # Enumerate localizations 253 | log_status (status.TITLE, 'Enumerate localizations') 254 | 255 | lng_content = '; ' + APP_NAME_SHORT + '\n' 256 | lng_content = lng_content + '; https://github.com/henrypp/' + APP_NAME_SHORT + '/tree/master/bin/i18n\n' 257 | lng_content = lng_content + ';\n; DO NOT MODIFY THIS FILE -- YOUR CHANGES WILL BE ERASED!\n\n' 258 | 259 | for name in i18n_files: 260 | if not name.endswith ('.ini'): 261 | continue 262 | 263 | clr = status.FAILED 264 | 265 | locale_name = os.path.splitext (name)[0] 266 | locale_path = os.path.join (I18N_DIRECTORY, name) 267 | locale_sname = locale_name 268 | #locale_sname = get_locale_name (locale_name) 269 | 270 | try: 271 | ini_file = open (locale_path, mode='r', encoding='utf-16') 272 | lines = ini_file.readlines () 273 | ini_file.close () 274 | 275 | except Exception as e: 276 | log_status (status.FAILED, 'Parsing "' + name + '" (' + str (e) + ')') 277 | 278 | else: 279 | # Get locale structure 280 | locale_content = '' 281 | locale_desc = '' 282 | locale_array = {} 283 | 284 | for line in lines: 285 | if line.startswith (';'): 286 | locale_desc += line 287 | else: 288 | break 289 | 290 | if locale_desc: 291 | locale_desc += '\n' 292 | 293 | # Write locale header 294 | locale_content += locale_desc + '[' + locale_name + ']\n' 295 | lng_content += locale_desc + '[' + locale_sname + ']\n' 296 | 297 | # Write locale timestamp 298 | if locale_name.lower () == 'russian': 299 | lng_content += '000=' + str (locale_timestamp) + '\n' 300 | 301 | # Parse locale string array 302 | for line in lines: 303 | line = line.strip ('\t\n ') 304 | 305 | if line.startswith (';'): 306 | continue 307 | 308 | part = line.partition ('=') 309 | 310 | if part[0] and part[2]: 311 | key = part[0].strip ('\t\n '); 312 | value = part[2].strip ('\t\n '); 313 | 314 | locale_array[key] = value 315 | 316 | # Write parsed locale array into a file 317 | for k,v in strings_array.items (): 318 | if not v['value']: 319 | continue 320 | 321 | key = v['name'] 322 | number_key = '{:03}'.format (int (k)) 323 | 324 | if number_key in locale_array: 325 | value = locale_array[number_key] 326 | 327 | elif key in locale_array: 328 | value = locale_array[key] 329 | 330 | else: 331 | value = v['value'] 332 | 333 | locale_content += key + '=' + value + '\n' 334 | 335 | if value != v['value']: # write only localized strings 336 | lng_content += number_key + '=' + value + '\n' 337 | 338 | clr = status.SUCCESS 339 | 340 | # Line-ending hack 341 | if i18n_files[-1] != name: 342 | lng_content += '\n' 343 | 344 | # Write updated language template 345 | filemtime = os.path.getmtime (locale_path); 346 | 347 | with open (locale_path, 'w', encoding='utf-16', newline='\r\n') as ini_file: 348 | ini_file.write (locale_content) 349 | ini_file.close () 350 | 351 | os.utime (locale_path, (filemtime, filemtime)) 352 | 353 | log_status (clr, 'Parsing "' + name + '"') 354 | 355 | # Write updated language content 356 | log_status (status.TITLE, 'Write updated language content') 357 | 358 | if os.path.isfile (LOCALE_FILE): 359 | os.chmod (LOCALE_FILE, stat.S_IWRITE) 360 | 361 | try: 362 | lng_file = open (LOCALE_FILE, 'w', encoding='utf-16', newline='\r\n') 363 | lng_file.write (lng_content) 364 | lng_file.close () 365 | 366 | except Exception as e: 367 | log_status (status.FAILED, 'Write locale "' + get_file_name (LOCALE_FILE) + '" (' + str (e) + ')') 368 | 369 | else: 370 | log_status (status.SUCCESS, 'Write locale "' + get_file_name (LOCALE_FILE) + '"') 371 | 372 | # Copy localization to binaries folders 373 | file_copy (LOCALE_FILE, os.path.join (PROJECT_DIRECTORY, 'bin', '32', os.path.basename (LOCALE_FILE)), made_dir=False) 374 | file_copy (LOCALE_FILE, os.path.join (PROJECT_DIRECTORY, 'bin', '64', os.path.basename (LOCALE_FILE)), made_dir=False) 375 | file_copy (LOCALE_FILE, os.path.join (PROJECT_DIRECTORY, 'bin', 'ARM64', os.path.basename (LOCALE_FILE)), made_dir=False) 376 | 377 | print ('\nLocale timestamp: ' + str (locale_timestamp) + ' (' + locale_lastname + ').') 378 | -------------------------------------------------------------------------------- /src/helper.py: -------------------------------------------------------------------------------- 1 | import glob 2 | import hashlib 3 | import logging 4 | import math 5 | import os 6 | import time 7 | import platform 8 | import re 9 | import shutil 10 | 11 | import ctypes 12 | from ctypes import wintypes 13 | 14 | class status: 15 | TITLE = 'xx' 16 | FAILED = '\033[31m' # red 17 | SUCCESS = '\033[32m' # green 18 | WARNING = '\033[33m' # orange 19 | BLUE = '\033[34m' # blue 20 | PURPLE = '\033[35m' # purple 21 | WHITE = '\033[0m' # white (normal) 22 | 23 | COMPRESS_FORMAT = { 24 | 'COMPRESSION_FORMAT_LZNT1' : ctypes.c_uint16 (2), 25 | 'COMPRESSION_FORMAT_XPRESS' : ctypes.c_uint16 (3), 26 | 'COMPRESSION_FORMAT_XPRESS_HUFF' : ctypes.c_uint16 (4) 27 | } 28 | 29 | COMPRESS_ENGINE = { 30 | 'COMPRESSION_ENGINE_STANDARD' : ctypes.c_uint16 (0), 31 | 'COMPRESSION_ENGINE_MAXIMUM' : ctypes.c_uint16 (256) 32 | } 33 | 34 | def is_os_64bit (): 35 | return platform.machine ().endswith ('64') 36 | 37 | def clr_to_console (clr): 38 | if clr == status.TITLE: 39 | return '\n' 40 | 41 | elif clr == status.FAILED: 42 | return status.FAILED + '[failed]' + status.WHITE + ' - ' 43 | 44 | elif clr == status.SUCCESS: 45 | return status.SUCCESS + '[success]' + status.WHITE + ' - ' 46 | 47 | elif clr == status.WARNING: 48 | return status.WARNING + '[warning]' + status.WHITE + ' - ' 49 | 50 | return '[debug] - ' 51 | 52 | def log_status (clr, text): 53 | if clr == status.TITLE: 54 | text += ':' 55 | 56 | print (clr_to_console (clr) + text) 57 | 58 | def check_path_with_status (title, path, is_file=False): 59 | is_exists = False 60 | 61 | if is_file: 62 | is_exists = os.path.isfile (path) 63 | else: 64 | is_exists = os.path.isdir (path) 65 | 66 | if is_exists: 67 | log_status (status.SUCCESS, title + ' "' + get_file_name (path) + '" was found') 68 | else: 69 | log_status (status.FAILED, title + ' "' + get_file_name (path) + '" was not found') 70 | os.sys.exit ('') 71 | 72 | def natural_sort (list, key=lambda s:s): 73 | def get_alphanum_key_func (key): 74 | convert = lambda text: int (text) if text.isdigit () else text 75 | return lambda s: [convert (c) for c in re.split ('([0-9]+)', key (s))] 76 | 77 | list.sort (key=get_alphanum_key_func (key)) 78 | 79 | def format_size (size, decimal_places=2): 80 | for unit in ['B', 'KiB', 'MiB', 'GiB', 'TiB', 'PiB']: 81 | if size < 1024.0 or unit == 'PiB': 82 | break 83 | 84 | size /= 1024.0 85 | 86 | return f"{size:.{decimal_places}f} {unit}" 87 | 88 | def pack_buffer_lznt (buffer_data): 89 | format_and_engine = wintypes.USHORT (COMPRESS_FORMAT['COMPRESSION_FORMAT_LZNT1'].value | COMPRESS_ENGINE['COMPRESSION_ENGINE_MAXIMUM'].value) 90 | 91 | workspace_buffer_size = wintypes.ULONG () 92 | workspace_fragment_size = wintypes.ULONG () 93 | 94 | # RtlGetCompressionWorkSpaceSize 95 | ctypes.windll.ntdll.RtlGetCompressionWorkSpaceSize.restype = wintypes.LONG 96 | ctypes.windll.ntdll.RtlGetCompressionWorkSpaceSize.argtypes = ( 97 | wintypes.USHORT, 98 | wintypes.PULONG, 99 | wintypes.PULONG 100 | ) 101 | 102 | result = ctypes.windll.ntdll.RtlGetCompressionWorkSpaceSize ( 103 | format_and_engine, 104 | ctypes.byref(workspace_buffer_size), 105 | ctypes.byref(workspace_fragment_size) 106 | ) 107 | 108 | if result != 0: 109 | log_status (status.FAILED, 'RtlGetCompressionWorkSpaceSize failed: 0x{0:X} {0:d} ({1:s})'.format (result, ctypes.FormatError (result))) 110 | return None 111 | 112 | # Allocate memory 113 | compressed_buffer = ctypes.create_string_buffer (len (buffer_data)) 114 | compressed_length = wintypes.ULONG () 115 | 116 | workspace = ctypes.create_string_buffer (workspace_fragment_size.value) 117 | 118 | # RtlCompressBuffer 119 | ctypes.windll.ntdll.RtlCompressBuffer.restype = wintypes.LONG 120 | ctypes.windll.ntdll.RtlCompressBuffer.argtypes = ( 121 | wintypes.USHORT, 122 | wintypes.LPVOID, 123 | wintypes.ULONG, 124 | wintypes.LPVOID, 125 | wintypes.ULONG, 126 | wintypes.ULONG, 127 | wintypes.PULONG, 128 | wintypes.LPVOID 129 | ) 130 | 131 | result = ctypes.windll.ntdll.RtlCompressBuffer ( 132 | format_and_engine, 133 | ctypes.addressof (buffer_data), 134 | ctypes.sizeof (buffer_data), 135 | ctypes.addressof (compressed_buffer), 136 | ctypes.sizeof (compressed_buffer), 137 | wintypes.ULONG (4096), 138 | ctypes.byref (compressed_length), 139 | ctypes.addressof (workspace) 140 | ) 141 | if result != 0: 142 | log_status (status.FAILED, 'RtlCompressBuffer failed: 0x{0:X} {0:d} ({1:s})'.format (result, ctypes.FormatError (result))) 143 | return None 144 | 145 | buffer = (compressed_length.value * ctypes.c_ubyte).from_buffer_copy (compressed_buffer) 146 | 147 | return buffer 148 | 149 | def unpack_buffer_lznt (buffer_data): 150 | format_and_engine = wintypes.USHORT (COMPRESS_FORMAT['COMPRESSION_FORMAT_XPRESS'].value) 151 | 152 | # Allocate memory 153 | uncompressed_buffer = ctypes.create_string_buffer (len (buffer_data) * 8) 154 | uncompressed_length = wintypes.ULONG () 155 | 156 | # RtlDecompressBuffer 157 | ctypes.windll.ntdll.RtlDecompressBuffer.restype = wintypes.LONG 158 | ctypes.windll.ntdll.RtlDecompressBuffer.argtypes = ( 159 | wintypes.USHORT, 160 | wintypes.LPVOID, 161 | wintypes.ULONG, 162 | wintypes.LPVOID, 163 | wintypes.ULONG, 164 | wintypes.PULONG 165 | ) 166 | 167 | result = ctypes.windll.ntdll.RtlDecompressBuffer ( 168 | format_and_engine, 169 | ctypes.addressof (uncompressed_buffer), 170 | ctypes.sizeof (uncompressed_buffer), 171 | ctypes.addressof (buffer_data), 172 | ctypes.sizeof (buffer_data), 173 | ctypes.byref (uncompressed_length) 174 | ) 175 | 176 | if result != 0: 177 | log_status (status.FAILED, 'RtlDecompressBuffer failed: 0x{0:X} {0:d} ({1:s})'.format (result, ctypes.FormatError (result))) 178 | return None 179 | 180 | buffer = (uncompressed_length.value * ctypes.c_ubyte).from_buffer_copy (uncompressed_buffer) 181 | 182 | return buffer 183 | 184 | def compress_buffer_to_file_lznt (buffer_data, buffer_length, file): 185 | buffer = pack_buffer_lznt (buffer_data) 186 | 187 | if buffer == None: 188 | log_status (status.FAILED, 'Compression failed...') 189 | return 190 | 191 | log_status (status.SUCCESS, 'Compress buffer with %s size to %s (%s saved)...' % (format_size (buffer_length), format_size (len (buffer)), format_size (buffer_length - len (buffer)))) 192 | 193 | with open (file, 'wb') as fn: 194 | fn.write (buffer) 195 | fn.close () 196 | 197 | def compress_file_lznt (file_o, file_s): 198 | with open (file_o, 'rb') as fn: 199 | data = fn.read () 200 | fn.close () 201 | 202 | if not data: 203 | log_status (status.WARNING, 'File is empty: "' + get_file_name (file_o) + '"') 204 | 205 | else: 206 | buffer_length = len (data) 207 | buffer_data = (buffer_length * ctypes.c_ubyte).from_buffer_copy (data) 208 | 209 | compress_buffer_to_file_lznt (buffer_data, buffer_length, file_s) 210 | 211 | def get_file_name (path): 212 | dir_name = os.path.basename (os.path.dirname (path)) 213 | 214 | dir_name = os.path.basename (os.path.dirname (path)) 215 | 216 | if not dir_name: 217 | dir_name = os.path.basename (path) 218 | 219 | if not dir_name: 220 | return path 221 | 222 | dir_sub_name = os.path.join (dir_name, os.path.basename (path)) 223 | 224 | if not dir_sub_name: 225 | dir_sub_name = os.path.basename (path) 226 | 227 | return dir_sub_name 228 | 229 | def get_sha256 (path): 230 | with open (path, 'rb') as fn: 231 | buff = fn.read () 232 | fn.close () 233 | 234 | return hashlib.sha256 (buff).hexdigest () 235 | 236 | return None 237 | 238 | def file_get_sha256 (path): 239 | file_name = get_file_name (path) 240 | 241 | if not os.path.isfile (path): 242 | log_status (status.WARNING, 'File hash. Not found: "' + file_name + '"') 243 | return None 244 | 245 | hash_code = get_sha256 (path) 246 | 247 | if hash_code: 248 | log_status (status.SUCCESS, 'File hash: "' + os.path.basename (path) + '" - ' + hash_code) 249 | return hash_code + ' *' + os.path.basename (path) + '\n' 250 | 251 | log_status (status.FAILED, 'File hash. Failed: "' + file_name + '"') 252 | 253 | return None 254 | 255 | def file_create (path, data): 256 | try: 257 | fn = open (path, 'w', newline='') 258 | 259 | if data: 260 | fn.write (data) 261 | 262 | fn.close () 263 | 264 | except Exception as e: 265 | log_status (status.FAILED, 'Create file. Exception: "' + get_file_name (path) + '" (' + str (e) + ')') 266 | 267 | else: 268 | log_status (status.SUCCESS, 'Create file: "' + get_file_name (path) + '"') 269 | 270 | def file_copy (src_file, dst_file, made_dir=True): 271 | if not os.path.isfile (src_file): 272 | log_status (status.FAILED, 'File copy. Not found: "' + get_file_name (src_file) + '"') 273 | return 274 | 275 | dst_dir = os.path.dirname (os.path.abspath (dst_file)) 276 | file_name = get_file_name (dst_file) 277 | 278 | if not os.path.isdir (dst_dir): 279 | if made_dir: 280 | os.makedirs (dst_dir, exist_ok=True) 281 | else: 282 | log_status (status.WARNING, 'File copy. Dest directory: "' + get_file_name (dst_dir) + '" was not found.') 283 | return 284 | 285 | # Check file existence 286 | if os.path.isfile (dst_file): 287 | if get_sha256 (dst_file) == get_sha256 (src_file): 288 | log_status (status.WARNING, 'File copy. Not modified: "' + file_name + '"') 289 | return 290 | 291 | try: 292 | shutil.copyfile (src_file, dst_file) 293 | 294 | except Exception as e: 295 | log_status (status.FAILED, 'File copy. Exception: "' + file_name + '" (' + str (e) + ')') 296 | 297 | else: 298 | log_status (status.SUCCESS, 'File copy: "' + file_name + '"') 299 | 300 | def file_remove (path): 301 | file_name = get_file_name (path) 302 | 303 | if not os.path.isfile (path): 304 | log_status (status.WARNING, 'File delete. Not found: "' + file_name + '"') 305 | return 306 | 307 | try: 308 | os.remove (path) 309 | 310 | except Exception as e: 311 | log_status (status.FAILED, 'File delete. Exception: "' + file_name + '" (' + str (e) + ')') 312 | 313 | else: 314 | log_status (status.SUCCESS, 'File delete: "' + file_name + '"') 315 | 316 | def file_sign (path): 317 | if not os.path.isfile (path): 318 | log_status (status.FAILED, 'File sign. Not found: "' + get_file_name (path) + '"') 319 | return 320 | 321 | path_sign = path + '.sig' 322 | 323 | file_remove (path_sign) 324 | 325 | log_status (status.SUCCESS, 'File sign: "' + get_file_name (path) + '"') 326 | 327 | os.system ('gpg.exe --output "' + path_sign + '" --detach-sign "' + path + '"') 328 | 329 | def file_copy_mask (mask, dst_dir): 330 | for fn in glob.glob (mask): 331 | file_copy (fn, os.path.join (dst_dir, os.path.basename (fn))) 332 | 333 | def file_sign_mask (mask): 334 | for fn in glob.glob (mask): 335 | file_sign (fn) 336 | 337 | def dir_remove (path): 338 | if not os.path.isdir (path): 339 | log_status (status.FAILED, 'Directory delete. Not found: "' + get_file_name (path) + '"') 340 | return 341 | 342 | for fn in glob.glob (os.path.join (path, '*')): 343 | if os.path.isdir (fn): 344 | dir_remove (fn); 345 | else: 346 | file_remove (fn) 347 | 348 | def file_pack_directory (out_file, directory): 349 | # МАТЬ ЕБАЛ ДОКУМЕНТАЦИЮ КОМАНДНОЙ СТРОКИ "7z.exe a -h" 350 | # ТАМ ПОНЯТНО РОВНО - НИ-ХУ-Я БЛЯТЬ! 351 | os.system ('7z.exe a -mm=LZMA2 -mx=9 -mfb=257 -mtc=off -slp -bb1 "' + out_file + '" -r "' + directory + '"') 352 | 353 | def initialize_helper (): 354 | # Colored terminal fix 355 | os.system ('') 356 | 357 | logging.basicConfig (level=logging.INFO) 358 | 359 | #if __name__ == 'helper': 360 | initialize_helper () 361 | -------------------------------------------------------------------------------- /src/build_package.py: -------------------------------------------------------------------------------- 1 | import argparse 2 | from helper import * 3 | 4 | parser = argparse.ArgumentParser (add_help=False, description='Build project packages.') 5 | parser.add_argument ('--name', help='project full name') 6 | parser.add_argument ('--name-short', help='project short name', required=True) 7 | parser.add_argument ('--version', help='project short name', required=True) 8 | 9 | args = parser.parse_args () 10 | 11 | APP_NAME=args.name 12 | APP_NAME_SHORT=args.name_short 13 | APP_VERSION=args.version 14 | 15 | # Set script configuration 16 | CURRENT_DIRECTORY = os.path.dirname (os.path.abspath (__file__)) 17 | PROJECT_DIRECTORY = os.path.join (CURRENT_DIRECTORY, '..', '..', APP_NAME_SHORT) 18 | BIN_DIRECTORY = os.path.join (PROJECT_DIRECTORY, 'bin') 19 | OUT_DIRECTORY = os.path.join (os.path.join (os.environ['USERPROFILE']), 'Desktop') 20 | TMP_DIRECTORY = os.path.join (os.environ['TEMP'], 'builder', APP_NAME_SHORT) 21 | 22 | PORTABLE_FILE = os.path.join (OUT_DIRECTORY, APP_NAME_SHORT + '-' + APP_VERSION + '-bin.7z') 23 | PDB_PACKAGE_FILE = os.path.join (OUT_DIRECTORY, APP_NAME_SHORT + '-' + APP_VERSION + '-pdb.7z') 24 | SETUP_FILE = os.path.join (OUT_DIRECTORY, APP_NAME_SHORT + '-' + APP_VERSION + '-setup.exe') 25 | CHECKSUM_FILE = os.path.join (OUT_DIRECTORY, APP_NAME_SHORT + '-' + APP_VERSION + '.sha256') 26 | 27 | # Checking configuration 28 | log_status (status.TITLE, 'Checking configuration') 29 | 30 | check_path_with_status ('Project name', PROJECT_DIRECTORY) 31 | check_path_with_status ('Binaries directory', BIN_DIRECTORY) 32 | 33 | # Remove previous packages 34 | log_status (status.TITLE, 'Remove previous packages') 35 | 36 | file_remove (PORTABLE_FILE) 37 | file_remove (PDB_PACKAGE_FILE) 38 | file_remove (SETUP_FILE) 39 | file_remove (SETUP_FILE + '.sig') 40 | file_remove (CHECKSUM_FILE) 41 | 42 | # Copy GIT configuration 43 | log_status (status.TITLE, 'Copy GIT configuration') 44 | 45 | if os.path.isfile (os.path.join (CURRENT_DIRECTORY, '.github', 'FUNDING.yml')): 46 | file_copy (os.path.join (CURRENT_DIRECTORY, '.github', 'FUNDING.yml'), os.path.join (PROJECT_DIRECTORY, '.github', 'FUNDING.yml')) 47 | 48 | #if os.path.isfile (os.path.join (CURRENT_DIRECTORY, '.editorconfig')): 49 | # file_copy (os.path.join (CURRENT_DIRECTORY, '.editorconfig'), os.path.join (PROJECT_DIRECTORY, '.editorconfig')) 50 | 51 | if os.path.isfile (os.path.join (CURRENT_DIRECTORY, '.gitattributes')): 52 | file_copy (os.path.join (CURRENT_DIRECTORY, '.gitattributes'), os.path.join (PROJECT_DIRECTORY, '.gitattributes')) 53 | 54 | if os.path.isfile (os.path.join (CURRENT_DIRECTORY, '.gitignore')): 55 | file_copy (os.path.join (CURRENT_DIRECTORY, '.gitignore'), os.path.join (PROJECT_DIRECTORY, '.gitignore')) 56 | 57 | if os.path.isfile (os.path.join (CURRENT_DIRECTORY, '.gitmodules')): 58 | file_copy (os.path.join (CURRENT_DIRECTORY, '.gitmodules'), os.path.join (PROJECT_DIRECTORY, '.gitmodules')) 59 | 60 | if os.path.isfile (os.path.join (BIN_DIRECTORY, 'History.txt')): 61 | file_copy (os.path.join (BIN_DIRECTORY, 'History.txt'), os.path.join (PROJECT_DIRECTORY, 'CHANGELOG.md')) 62 | 63 | # Cleaning temporary files 64 | if os.path.isdir (TMP_DIRECTORY): 65 | log_status (status.TITLE, 'Cleaning temporary files') 66 | dir_remove (TMP_DIRECTORY) 67 | 68 | is_buildfor_32 = os.path.isdir (os.path.join (BIN_DIRECTORY, '32')) 69 | is_buildfor_64 = os.path.isdir (os.path.join (BIN_DIRECTORY, '64')) 70 | is_buildfor_arm64 = os.path.isdir (os.path.join (BIN_DIRECTORY, 'arm64')) 71 | 72 | is_readme_exist = os.path.isfile (os.path.join (BIN_DIRECTORY, 'Readme.txt')) 73 | is_history_exist = os.path.isfile (os.path.join (BIN_DIRECTORY, 'History.txt')) 74 | is_license_exist = os.path.isfile (os.path.join (BIN_DIRECTORY, 'License.txt')) 75 | 76 | is_config_exist = os.path.isfile (os.path.join (BIN_DIRECTORY, APP_NAME_SHORT + '.ini')) 77 | is_locale_exist = os.path.isfile (os.path.join (BIN_DIRECTORY, APP_NAME_SHORT + '.lng')) 78 | 79 | # Copy files 80 | log_status (status.TITLE, 'Copy files') 81 | 82 | if not is_readme_exist: 83 | log_status (status.WARNING, 'Readme.txt was not found') 84 | 85 | if not is_history_exist: 86 | log_status (status.WARNING, 'History.txt was not found') 87 | 88 | if not is_license_exist: 89 | log_status (status.WARNING, 'License.txt was not found') 90 | 91 | if not is_config_exist: 92 | log_status (status.WARNING, 'Default configuration was not found') 93 | 94 | if not is_locale_exist: 95 | log_status (status.WARNING, 'Locale was not found') 96 | 97 | if is_buildfor_32: 98 | os.makedirs (os.path.join (TMP_DIRECTORY, '32'), exist_ok=True) 99 | 100 | file_create (os.path.join (TMP_DIRECTORY, '32', 'portable.dat'), '#PORTABLE#') 101 | 102 | file_copy_mask (os.path.join (BIN_DIRECTORY, '32', '*.exe'), os.path.join (TMP_DIRECTORY, '32')); 103 | file_copy_mask (os.path.join (BIN_DIRECTORY, '32', '*.scr'), os.path.join (TMP_DIRECTORY, '32')); 104 | file_copy_mask (os.path.join (BIN_DIRECTORY, '32', '*.dll'), os.path.join (TMP_DIRECTORY, '32')); 105 | 106 | 107 | file_copy_mask (os.path.join (BIN_DIRECTORY, '*.bat'), os.path.join (TMP_DIRECTORY, '32')); 108 | file_copy_mask (os.path.join (BIN_DIRECTORY, '*.reg'), os.path.join (TMP_DIRECTORY, '32')); 109 | file_copy_mask (os.path.join (BIN_DIRECTORY, '*.dat'), os.path.join (TMP_DIRECTORY, '32')); 110 | 111 | if is_readme_exist: 112 | file_copy (os.path.join (BIN_DIRECTORY, 'Readme.txt'), os.path.join (TMP_DIRECTORY, '32', 'Readme.txt')) 113 | 114 | if is_history_exist: 115 | file_copy (os.path.join (BIN_DIRECTORY, 'History.txt'), os.path.join (TMP_DIRECTORY, '32', 'History.txt')) 116 | 117 | if is_license_exist: 118 | file_copy (os.path.join (BIN_DIRECTORY, 'License.txt'), os.path.join (TMP_DIRECTORY, '32', 'License.txt')) 119 | 120 | if is_config_exist: 121 | file_copy (os.path.join (BIN_DIRECTORY, APP_NAME_SHORT + '.ini'), os.path.join (TMP_DIRECTORY, '32', APP_NAME_SHORT + '.ini')) 122 | 123 | if is_locale_exist: 124 | file_copy (os.path.join (BIN_DIRECTORY, APP_NAME_SHORT + '.lng'), os.path.join (TMP_DIRECTORY, '32', APP_NAME_SHORT + '.lng')) 125 | 126 | if is_buildfor_64: 127 | os.makedirs (os.path.join (TMP_DIRECTORY, '64'), exist_ok=True) 128 | 129 | file_create (os.path.join (TMP_DIRECTORY, '64', 'portable.dat'), '#PORTABLE#') 130 | 131 | file_copy_mask (os.path.join (BIN_DIRECTORY, '64', '*.exe'), os.path.join (TMP_DIRECTORY, '64')); 132 | file_copy_mask (os.path.join (BIN_DIRECTORY, '64', '*.scr'), os.path.join (TMP_DIRECTORY, '64')); 133 | file_copy_mask (os.path.join (BIN_DIRECTORY, '64', '*.dll'), os.path.join (TMP_DIRECTORY, '64')); 134 | 135 | file_copy_mask (os.path.join (BIN_DIRECTORY, '*.bat'), os.path.join (TMP_DIRECTORY, '64')); 136 | file_copy_mask (os.path.join (BIN_DIRECTORY, '*.reg'), os.path.join (TMP_DIRECTORY, '64')); 137 | file_copy_mask (os.path.join (BIN_DIRECTORY, '*.dat'), os.path.join (TMP_DIRECTORY, '64')); 138 | 139 | if is_readme_exist: 140 | file_copy (os.path.join (BIN_DIRECTORY, 'Readme.txt'), os.path.join (TMP_DIRECTORY, '64', 'Readme.txt')) 141 | 142 | if is_history_exist: 143 | file_copy (os.path.join (BIN_DIRECTORY, 'History.txt'), os.path.join (TMP_DIRECTORY, '64', 'History.txt')) 144 | 145 | if is_license_exist: 146 | file_copy (os.path.join (BIN_DIRECTORY, 'License.txt'), os.path.join (TMP_DIRECTORY, '64', 'License.txt')) 147 | 148 | if is_config_exist: 149 | file_copy (os.path.join (BIN_DIRECTORY, APP_NAME_SHORT + '.ini'), os.path.join (TMP_DIRECTORY, '64', APP_NAME_SHORT + '.ini')) 150 | 151 | if is_locale_exist: 152 | file_copy (os.path.join (BIN_DIRECTORY, APP_NAME_SHORT + '.lng'), os.path.join (TMP_DIRECTORY, '64', APP_NAME_SHORT + '.lng')) 153 | 154 | if is_buildfor_arm64: 155 | os.makedirs (os.path.join (TMP_DIRECTORY, 'ARM64'), exist_ok=True) 156 | 157 | file_create (os.path.join (TMP_DIRECTORY, 'ARM64', 'portable.dat'), '#PORTABLE#') 158 | 159 | file_copy_mask (os.path.join (BIN_DIRECTORY, 'ARM64', '*.exe'), os.path.join (TMP_DIRECTORY, 'ARM64')); 160 | file_copy_mask (os.path.join (BIN_DIRECTORY, 'ARM64', '*.scr'), os.path.join (TMP_DIRECTORY, 'ARM64')); 161 | file_copy_mask (os.path.join (BIN_DIRECTORY, 'ARM64', '*.dll'), os.path.join (TMP_DIRECTORY, 'ARM64')); 162 | 163 | file_copy_mask (os.path.join (BIN_DIRECTORY, '*.bat'), os.path.join (TMP_DIRECTORY, 'ARM64')); 164 | file_copy_mask (os.path.join (BIN_DIRECTORY, '*.reg'), os.path.join (TMP_DIRECTORY, 'ARM64')); 165 | file_copy_mask (os.path.join (BIN_DIRECTORY, '*.dat'), os.path.join (TMP_DIRECTORY, 'ARM64')); 166 | 167 | if is_readme_exist: 168 | file_copy (os.path.join (BIN_DIRECTORY, 'Readme.txt'), os.path.join (TMP_DIRECTORY, 'ARM64', 'Readme.txt')) 169 | 170 | if is_history_exist: 171 | file_copy (os.path.join (BIN_DIRECTORY, 'History.txt'), os.path.join (TMP_DIRECTORY, 'ARM64', 'History.txt')) 172 | 173 | if is_license_exist: 174 | file_copy (os.path.join (BIN_DIRECTORY, 'License.txt'), os.path.join (TMP_DIRECTORY, 'ARM64', 'License.txt')) 175 | 176 | if is_config_exist: 177 | file_copy (os.path.join (BIN_DIRECTORY, APP_NAME_SHORT + '.ini'), os.path.join (TMP_DIRECTORY, 'ARM64', APP_NAME_SHORT + '.ini')) 178 | 179 | if is_locale_exist: 180 | file_copy (os.path.join (BIN_DIRECTORY, APP_NAME_SHORT + '.lng'), os.path.join (TMP_DIRECTORY, 'ARM64', APP_NAME_SHORT + '.lng')) 181 | 182 | # Calculate binaries hash 183 | binary_hash_32 = None 184 | binary_hash_64 = None 185 | binary_hash_arm64 = None 186 | 187 | if os.path.isfile (os.path.join (TMP_DIRECTORY, '32', APP_NAME_SHORT + '.exe')): 188 | binary_hash_32 = file_get_sha256 (os.path.join (TMP_DIRECTORY, '32', APP_NAME_SHORT + '.exe')) 189 | 190 | elif os.path.isfile (os.path.join (TMP_DIRECTORY, '32', APP_NAME_SHORT + '.scr')): 191 | binary_hash_32 = file_get_sha256 (os.path.join (TMP_DIRECTORY, '32', APP_NAME_SHORT + '.scr')) 192 | 193 | if os.path.isfile (os.path.join (TMP_DIRECTORY, '64', APP_NAME_SHORT + '.exe')): 194 | binary_hash_64 = file_get_sha256 (os.path.join (TMP_DIRECTORY, '64', APP_NAME_SHORT + '.exe')) 195 | 196 | elif os.path.isfile (os.path.join (TMP_DIRECTORY, '64', APP_NAME_SHORT + '.scr')): 197 | binary_hash_64 = file_get_sha256 (os.path.join (TMP_DIRECTORY, '64', APP_NAME_SHORT + '.scr')) 198 | 199 | if os.path.isfile (os.path.join (TMP_DIRECTORY, 'ARM64', APP_NAME_SHORT + '.exe')): 200 | binary_hash_arm64 = file_get_sha256 (os.path.join (TMP_DIRECTORY, 'ARM64', APP_NAME_SHORT + '.exe')) 201 | 202 | elif os.path.isfile (os.path.join (TMP_DIRECTORY, 'ARM64', APP_NAME_SHORT + '.scr')): 203 | binary_hash_arm64 = file_get_sha256 (os.path.join (TMP_DIRECTORY, 'ARM64', APP_NAME_SHORT + '.scr')) 204 | 205 | # Sign binaries with GPG 206 | log_status (status.TITLE, 'Sign binaries with GPG') 207 | 208 | if is_buildfor_32: 209 | file_sign_mask (os.path.join (TMP_DIRECTORY, '32', '*.exe')) 210 | file_sign_mask (os.path.join (TMP_DIRECTORY, '32', '*.scr')) 211 | file_sign_mask (os.path.join (TMP_DIRECTORY, '32', '*.dll')) 212 | 213 | if is_buildfor_64: 214 | file_sign_mask (os.path.join (TMP_DIRECTORY, '64', '*.exe')) 215 | file_sign_mask (os.path.join (TMP_DIRECTORY, '64', '*.scr')) 216 | file_sign_mask (os.path.join (TMP_DIRECTORY, '64', '*.dll')) 217 | 218 | if is_buildfor_arm64: 219 | file_sign_mask (os.path.join (TMP_DIRECTORY, 'ARM64', '*.exe')) 220 | file_sign_mask (os.path.join (TMP_DIRECTORY, 'ARM64', '*.scr')) 221 | file_sign_mask (os.path.join (TMP_DIRECTORY, 'ARM64', '*.dll')) 222 | 223 | # Create portable package 224 | log_status (status.TITLE, 'Create portable package') 225 | 226 | log_status (status.SUCCESS, 'Running 7z.exe') 227 | 228 | file_pack_directory (PORTABLE_FILE, TMP_DIRECTORY) 229 | 230 | # Create setup package (optional) 231 | log_status (status.TITLE, 'Create setup package (optional)') 232 | 233 | if APP_NAME != '': 234 | log_status (status.SUCCESS, 'Running makensis.exe') 235 | 236 | os.system ('makensis.exe /V2 /DAPP_FILES_DIR="' + TMP_DIRECTORY + '" /DAPP_NAME="' + APP_NAME + '" /DAPP_NAME_SHORT="' + APP_NAME_SHORT + '" /DAPP_VERSION="' + APP_VERSION + '" /X"OutFile ' + SETUP_FILE +'" src\setup_script.nsi') 237 | 238 | log_status (status.TITLE, 'Sign installer with GPG') 239 | 240 | file_sign (SETUP_FILE) 241 | else: 242 | log_status (status.WARNING, 'Disabled by command line') 243 | 244 | log_status (status.TITLE, 'Cleaning temporary files') 245 | 246 | dir_remove (TMP_DIRECTORY) 247 | 248 | # Create debug symbols package 249 | log_status (status.TITLE, 'Create debug symbols package') 250 | 251 | if is_buildfor_32: 252 | file_copy (os.path.join (BIN_DIRECTORY, '32', APP_NAME_SHORT + '.pdb'), os.path.join (TMP_DIRECTORY, '32', APP_NAME_SHORT + '.pdb')) 253 | 254 | if is_buildfor_64: 255 | file_copy (os.path.join (BIN_DIRECTORY, '64', APP_NAME_SHORT + '.pdb'), os.path.join (TMP_DIRECTORY, '64', APP_NAME_SHORT + '.pdb')) 256 | 257 | if is_buildfor_arm64: 258 | file_copy (os.path.join (BIN_DIRECTORY, 'ARM64', APP_NAME_SHORT + '.pdb'), os.path.join (TMP_DIRECTORY, 'ARM64', APP_NAME_SHORT + '.pdb')) 259 | 260 | file_pack_directory (PDB_PACKAGE_FILE, TMP_DIRECTORY) 261 | 262 | # Calculate sha256 checksum for files 263 | log_status (status.TITLE, 'Calculate SHA256 checksum for files') 264 | 265 | hash_string = '' 266 | 267 | if os.path.isfile (PORTABLE_FILE): 268 | hash_string += file_get_sha256 (PORTABLE_FILE) 269 | 270 | if os.path.isfile (SETUP_FILE): 271 | hash_string += file_get_sha256 (SETUP_FILE) 272 | 273 | if os.path.isfile (PDB_PACKAGE_FILE): 274 | hash_string += file_get_sha256 (PDB_PACKAGE_FILE) 275 | 276 | if binary_hash_32: 277 | hash_string += '#32-bit\n' + binary_hash_32 278 | 279 | if binary_hash_64: 280 | hash_string += '#64-bit\n' + binary_hash_64 281 | 282 | if binary_hash_arm64: 283 | hash_string += '#ARM64\n' + binary_hash_arm64 284 | 285 | if hash_string: 286 | file_create (CHECKSUM_FILE, hash_string) 287 | else: 288 | log_status (status.FAILED, 'Hash string is empty!') 289 | 290 | # Cleaning temporary files 291 | log_status (status.TITLE, 'Cleaning temporary files') 292 | 293 | dir_remove (TMP_DIRECTORY) 294 | 295 | -------------------------------------------------------------------------------- /src/setup_script.nsi: -------------------------------------------------------------------------------- 1 | ; Archive options 2 | SetCompress force 3 | SetCompressor /FINAL /SOLID lzma 4 | SetCompressorDictSize 128 5 | SetDatablockOptimize on 6 | SetDateSave off 7 | Unicode true 8 | 9 | ; Includes 10 | !include 'FileFunc.nsh' 11 | !include 'LogicLib.nsh' 12 | !include 'MUI2.nsh' 13 | !include 'x64.nsh' 14 | !include 'WinVer.nsh' 15 | 16 | !insertmacro GetParameters 17 | !insertmacro GetOptions 18 | 19 | ; Defines 20 | !define APP_AUTHOR "Henry++" 21 | !define APP_WEBSITE "https://github.com/henrypp" 22 | 23 | !define COPYRIGHT "(c) ${APP_AUTHOR}. All rights reversed." 24 | !define LICENSE_FILE "${APP_FILES_DIR}\64\License.txt" 25 | 26 | !define MUI_ABORTWARNING 27 | !define MUI_FINISHPAGE_NOAUTOCLOSE 28 | !define MUI_COMPONENTSPAGE_NODESC 29 | 30 | !define MUI_WELCOMEFINISHPAGE_BITMAP "${NSISDIR}\Contrib\Graphics\Wizard\nsis3-branding.bmp" 31 | !define MUI_HEADERIMAGE 32 | !define MUI_HEADERIMAGE_BITMAP "${NSISDIR}\Contrib\Graphics\Header\nsis3-metro.bmp" 33 | 34 | !define MUI_FINISHPAGE_RUN 35 | !define MUI_FINISHPAGE_RUN_FUNCTION RunApplication 36 | 37 | !define MUI_FINISHPAGE_SHOWREADME 38 | !define MUI_FINISHPAGE_SHOWREADME_TEXT "Show release notes" 39 | !define MUI_FINISHPAGE_SHOWREADME_FUNCTION ShowReleaseNotes 40 | !define MUI_FINISHPAGE_SHOWREADME_NOTCHECKED 41 | 42 | !define MUI_FINISHPAGE_NOREBOOTSUPPORT 43 | 44 | !define MUI_LANGDLL_REGISTRY_ROOT "HKLM" 45 | !define MUI_LANGDLL_REGISTRY_KEY "Software\Microsoft\Windows\CurrentVersion\Uninstall\${APP_NAME_SHORT}" 46 | !define MUI_LANGDLL_REGISTRY_VALUENAME "NSIS:Language" 47 | 48 | ; Pages 49 | !insertmacro MUI_PAGE_WELCOME 50 | !insertmacro MUI_PAGE_LICENSE "${LICENSE_FILE}" 51 | !define MUI_PAGE_CUSTOMFUNCTION_LEAVE IsPortable 52 | !insertmacro MUI_PAGE_DIRECTORY 53 | !insertmacro MUI_PAGE_COMPONENTS 54 | !insertmacro MUI_PAGE_INSTFILES 55 | !insertmacro MUI_PAGE_FINISH 56 | 57 | LicenseLangString MUILicense ${LANG_ENGLISH} "${LICENSE_FILE}" 58 | 59 | ; Language files 60 | !insertmacro MUI_LANGUAGE "Arabic" 61 | !insertmacro MUI_LANGUAGE "Albanian" 62 | !insertmacro MUI_LANGUAGE "Belarusian" 63 | !insertmacro MUI_LANGUAGE "Catalan" 64 | !insertmacro MUI_LANGUAGE "Croatian" 65 | !insertmacro MUI_LANGUAGE "Czech" 66 | !insertmacro MUI_LANGUAGE "Danish" 67 | !insertmacro MUI_LANGUAGE "Dutch" 68 | !insertmacro MUI_LANGUAGE "English" 69 | !insertmacro MUI_LANGUAGE "Farsi" 70 | !insertmacro MUI_LANGUAGE "French" 71 | !insertmacro MUI_LANGUAGE "German" 72 | !insertmacro MUI_LANGUAGE "Greek" 73 | !insertmacro MUI_LANGUAGE "Korean" 74 | !insertmacro MUI_LANGUAGE "Hungarian" 75 | !insertmacro MUI_LANGUAGE "Indonesian" 76 | !insertmacro MUI_LANGUAGE "Italian" 77 | !insertmacro MUI_LANGUAGE "Japanese" 78 | !insertmacro MUI_LANGUAGE "Lithuanian" 79 | !insertmacro MUI_LANGUAGE "Polish" 80 | !insertmacro MUI_LANGUAGE "Portuguese" 81 | !insertmacro MUI_LANGUAGE "PortugueseBR" 82 | !insertmacro MUI_LANGUAGE "Romanian" 83 | !insertmacro MUI_LANGUAGE "Russian" 84 | !insertmacro MUI_LANGUAGE "SimpChinese" 85 | !insertmacro MUI_LANGUAGE "Spanish" 86 | !insertmacro MUI_LANGUAGE "Swedish" 87 | !insertmacro MUI_LANGUAGE "Thai" 88 | !insertmacro MUI_LANGUAGE "TradChinese" 89 | !insertmacro MUI_LANGUAGE "Turkish" 90 | !insertmacro MUI_LANGUAGE "Ukrainian" 91 | !insertmacro MUI_RESERVEFILE_LANGDLL 92 | 93 | ; Options 94 | AllowSkipFiles off 95 | AutoCloseWindow false 96 | LicenseBkColor /windows 97 | LicenseForceSelection checkbox 98 | ManifestSupportedOS all 99 | ManifestLongPathAware true 100 | ManifestDPIAware true 101 | ShowInstDetails show 102 | SilentUnInstall silent 103 | XPStyle on 104 | 105 | Name "${APP_NAME}" 106 | BrandingText "${COPYRIGHT}" 107 | 108 | Caption "${APP_NAME} v${APP_VERSION}" 109 | UninstallCaption "${APP_NAME}" 110 | 111 | Icon "${NSISDIR}\Contrib\Graphics\Icons\orange-install-nsis.ico" 112 | UninstallIcon "${NSISDIR}\Contrib\Graphics\Icons\orange-uninstall-nsis.ico" 113 | 114 | InstallDir "$PROGRAMFILES64\${APP_NAME}" 115 | InstallDirRegKey HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\${APP_NAME_SHORT}" "InstallLocation" 116 | 117 | RequestExecutionLevel admin 118 | 119 | !define DEBUG `System::Call kernel32::OutputDebugString(ts)` 120 | 121 | !macro CloseInstances 122 | SetPluginUnload alwaysoff 123 | 124 | System::Call 'kernel32::OpenMutex(i 0x100000, i 0, t "${APP_NAME_SHORT}") p.R0' 125 | IntPtrCmp $R0 0 skip 126 | System::Call 'kernel32::CloseHandle(p $R0)' 127 | 128 | IfSilent 0 +1 129 | Sleep 2000 130 | 131 | System::Get "(p.R1, i) iss" 132 | Pop $R0 133 | System::Call "user32::EnumWindows(k R0, i) i.s" 134 | 135 | loop: 136 | Pop $0 137 | StrCmp $0 "callback1" 0 done 138 | 139 | System::Call 'user32::GetProp(p R1, t "${APP_NAME}") p.R2' 140 | 141 | IntPtrCmp $R2 0 notfound 142 | 143 | System::Call 'user32::GetWindowThreadProcessId(p R1, *i.R3)' 144 | 145 | # PROCESS_TERMINATE 0x0001 146 | # PROCESS_QUERY_LIMITED_INFORMATION 0x1000 147 | 148 | System::Call 'kernel32::OpenProcess(i 0x1001, i 0, i R3) p.R4' 149 | 150 | IntPtrCmp $R4 0 notfound 151 | 152 | System::Call 'psapi::GetModuleFileNameEx(p R4, i 0, t.R6, i ${NSIS_MAX_STRLEN}) i.R7' 153 | 154 | IntCmp $R7 0 cleanup 155 | StrCmp $R6 "$INSTDIR\${APP_NAME_SHORT}.exe" 0 cleanup 156 | 157 | # WM_QUIT 0x0012 158 | # SMTO_BLOCK = 0x0001 159 | 160 | System::Call 'user32::SendMessageTimeout(p R1, i 0x0012, i 0, i 0, i 0x0001, i 4000, i 0)' 161 | Sleep 1000 162 | 163 | System::Call 'kernel32::TerminateProcess(p R4, i 0)' 164 | Sleep 1000 165 | 166 | cleanup: 167 | 168 | System::Call 'kernel32::CloseHandle(p R4)' 169 | 170 | Push 0 171 | System::Call "$R0" 172 | 173 | Goto loop 174 | 175 | notfound: 176 | 177 | Push 1 178 | System::Call "$R0" 179 | 180 | Goto loop 181 | 182 | System::Call "$R0" 183 | 184 | Goto loop 185 | done: 186 | 187 | System::Free $R0 188 | skip: 189 | 190 | SetPluginUnload manual 191 | !macroend 192 | 193 | !define CloseInstances "${CallArtificialFunction} CloseInstances" 194 | 195 | Function .onInit 196 | ${If} ${RunningX64} 197 | SetRegView 64 198 | ${EndIf} 199 | 200 | !insertmacro MUI_LANGDLL_DISPLAY 201 | 202 | Push $R0 203 | 204 | ; Check if we are updating current configuration, then check executable existing. 205 | IfSilent 0 not_update 206 | ClearErrors 207 | 208 | ${GetOptions} $R0 '/u' $0 209 | IfErrors update 0 210 | IfFileExists "$INSTDIR\${APP_NAME_SHORT}.exe" update 0 211 | Abort 212 | 213 | update: 214 | ClearErrors 215 | 216 | not_update: 217 | Pop $R0 218 | 219 | ; Windows 7 and later only 220 | ${IfNot} ${AtLeastWin7} 221 | IfSilent skip 222 | 223 | MessageBox MB_OK|MB_ICONEXCLAMATION '${APP_NAME} requires Windows 7 and later!' 224 | 225 | skip: 226 | Abort 227 | ${EndIf} 228 | FunctionEnd 229 | 230 | Function un.onInit 231 | ${If} ${RunningX64} 232 | SetRegView 64 233 | ${EndIf} 234 | 235 | IfSilent skip 236 | 237 | MessageBox MB_YESNO|MB_ICONEXCLAMATION|MB_DEFBUTTON2 'Are you sure you want to uninstall ${APP_NAME}?' IDYES skip ; MUI_UNTEXT_CONFIRM_SUBTITLE 238 | Abort 239 | 240 | skip: 241 | FunctionEnd 242 | 243 | Function un.onUninstSuccess 244 | IfSilent skip 245 | 246 | MessageBox MB_OK|MB_ICONINFORMATION '${APP_NAME} was completely removed!' 247 | 248 | skip: 249 | FunctionEnd 250 | 251 | Section "!${APP_NAME}" 252 | SectionIn RO 253 | 254 | SetOutPath $INSTDIR 255 | 256 | DetailPrint "Close running instances..." 257 | 258 | ${CloseInstances} 259 | 260 | ${If} ${IsNativeARM64} 261 | File "${APP_FILES_DIR}\ARM64\${APP_NAME_SHORT}.exe" 262 | File /nonfatal "${APP_FILES_DIR}\ARM64\${APP_NAME_SHORT}.exe.sig" 263 | ${ElseIf} ${RunningX64} 264 | File "${APP_FILES_DIR}\64\${APP_NAME_SHORT}.exe" 265 | File /nonfatal "${APP_FILES_DIR}\64\${APP_NAME_SHORT}.exe.sig" 266 | ${Else} 267 | File "${APP_FILES_DIR}\32\${APP_NAME_SHORT}.exe" 268 | File /nonfatal "${APP_FILES_DIR}\32\${APP_NAME_SHORT}.exe.sig" 269 | ${EndIf} 270 | 271 | File "${APP_FILES_DIR}\64\History.txt" 272 | File "${APP_FILES_DIR}\64\License.txt" 273 | File "${APP_FILES_DIR}\64\Readme.txt" 274 | 275 | WriteUninstaller $INSTDIR\uninstall.exe 276 | 277 | ; Create uninstall entry 278 | Call CreateUninstallEntry 279 | 280 | Push $R0 281 | 282 | ${GetParameters} $R0 283 | ${GetOptions} $R0 '/u' $0 284 | 285 | IfErrors +2 286 | Call RunApplication 287 | 288 | Pop $R0 289 | SectionEnd 290 | 291 | Section "Localization" 292 | SetOutPath $INSTDIR 293 | 294 | File /nonfatal "${APP_FILES_DIR}\64\${APP_NAME_SHORT}.lng" 295 | SectionEnd 296 | 297 | Section "Create desktop shortcut" SecShortcut1 298 | IfSilent skip 299 | 300 | CreateShortCut "$DESKTOP\${APP_NAME}.lnk" "$INSTDIR\${APP_NAME_SHORT}.exe" 301 | 302 | skip: 303 | SectionEnd 304 | 305 | Section "Create start menu shortcuts" SecShortcut2 306 | IfSilent skip 307 | 308 | CreateDirectory "$SMPROGRAMS\${APP_NAME}" 309 | 310 | CreateShortCut "$SMPROGRAMS\${APP_NAME}\${APP_NAME}.lnk" "$INSTDIR\${APP_NAME_SHORT}.exe" 311 | CreateShortCut "$SMPROGRAMS\${APP_NAME}\License.lnk" "$INSTDIR\License.txt" 312 | CreateShortCut "$SMPROGRAMS\${APP_NAME}\History.lnk" "$INSTDIR\History.txt" 313 | CreateShortCut "$SMPROGRAMS\${APP_NAME}\Readme.lnk" "$INSTDIR\Readme.txt" 314 | CreateShortCut "$SMPROGRAMS\${APP_NAME}\Uninstall.lnk" "$INSTDIR\uninstall.exe" 315 | 316 | skip: 317 | SectionEnd 318 | 319 | Section /o "Store settings in application directory (portable mode)" SecPortable 320 | IfFileExists "$INSTDIR\portable.dat" portable 321 | IfFileExists "$INSTDIR\${APP_NAME_SHORT}.ini" portable not_portable 322 | 323 | Push $R0 324 | 325 | ; Create portable indicator file 326 | not_portable: 327 | 328 | FileOpen $R0 "$INSTDIR\portable.dat" w 329 | FileWrite $R0 "#PORTABLE#" ; for is not being empty 330 | FileClose $R0 331 | 332 | portable: 333 | 334 | Pop $R0 335 | SectionEnd 336 | 337 | Section "Uninstall" 338 | IfFileExists $INSTDIR\${APP_NAME_SHORT}.exe installed 339 | 340 | MessageBox MB_YESNO "It does not appear that ${APP_NAME} is installed in the installation directory.$\r$\nContinue anyway (not recommended)?" IDYES installed 341 | Abort 342 | 343 | installed: 344 | 345 | ${CloseInstances} 346 | 347 | ExecWait '"$INSTDIR\${APP_NAME_SHORT}.exe" -uninstall' 348 | 349 | ; Remove shortcuts 350 | RMDir /r "$SMPROGRAMS\${APP_NAME}" 351 | Delete "$DESKTOP\${APP_NAME}.lnk" 352 | 353 | ; Clean registry 354 | DeleteRegKey HKCU "Software\Microsoft\Windows\CurrentVersion\Uninstall\${APP_NAME_SHORT}" 355 | DeleteRegKey HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\${APP_NAME_SHORT}" 356 | 357 | DeleteRegValue HKCU "Software\Microsoft\Windows\CurrentVersion\Run" "${APP_NAME}" 358 | DeleteRegValue HKLM "Software\Microsoft\Windows\CurrentVersion\Run" "${APP_NAME}" 359 | 360 | ; Remove "skipuac" entry 361 | nsExec::Exec 'schtasks /delete /f /tn "${APP_NAME_SHORT}Task"' 362 | 363 | ; Remove "skipuac" entry (deprecated) 364 | nsExec::Exec 'schtasks /delete /f /tn "${APP_NAME_SHORT}SkipUac"' 365 | 366 | ; Remove configuration from %appdata% only for non-portable mode 367 | IfFileExists "$INSTDIR\portable.dat" portable 368 | IfFileExists "$INSTDIR\${APP_NAME_SHORT}.ini" portable not_portable 369 | 370 | not_portable: 371 | RMDir /r "$APPDATA\${APP_AUTHOR}\${APP_NAME}" 372 | RMDir "$APPDATA\${APP_AUTHOR}" 373 | 374 | portable: 375 | 376 | ; Remove internal directories 377 | RMDir /r "$INSTDIR\cache" 378 | RMDir /r "$INSTDIR\crashdump" 379 | RMDir /r "$INSTDIR\plugins" 380 | 381 | ; Remove install directory 382 | Delete "$INSTDIR\${APP_NAME_SHORT}.scr" 383 | Delete "$INSTDIR\${APP_NAME_SHORT}.scr.sig" 384 | Delete "$INSTDIR\${APP_NAME_SHORT}.exe" 385 | Delete "$INSTDIR\${APP_NAME_SHORT}.exe.sig" 386 | Delete "$INSTDIR\${APP_NAME_SHORT}.pdb" 387 | Delete "$INSTDIR\${APP_NAME_SHORT}.sig" 388 | Delete "$INSTDIR\${APP_NAME_SHORT}.lng" 389 | Delete "$INSTDIR\${APP_NAME_SHORT}.ini" 390 | Delete "$INSTDIR\${APP_NAME_SHORT}_debug.log" 391 | Delete "$INSTDIR\portable.dat" 392 | Delete "$INSTDIR\Readme.txt" 393 | Delete "$INSTDIR\History.txt" 394 | Delete "$INSTDIR\License.txt" 395 | Delete "$INSTDIR\FAQ.txt" 396 | 397 | ${If} ${APP_NAME_SHORT} == 'simplewall' 398 | Delete "$INSTDIR\profile.xml" 399 | Delete "$INSTDIR\profile.xml.bak" 400 | 401 | Delete "$INSTDIR\profile_internal.xml" 402 | Delete "$INSTDIR\profile_internal.sp" 403 | ${EndIf} 404 | 405 | Delete "$INSTDIR\Uninstall.exe" 406 | 407 | RMDir "$INSTDIR" 408 | SectionEnd 409 | 410 | Function CreateUninstallEntry 411 | WriteRegExpandStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\${APP_NAME_SHORT}" "InstallLocation" '"$INSTDIR"' 412 | WriteRegExpandStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\${APP_NAME_SHORT}" "UninstallString" '"$INSTDIR\uninstall.exe"' 413 | 414 | WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\${APP_NAME_SHORT}" "DisplayName" "${APP_NAME}" 415 | WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\${APP_NAME_SHORT}" "DisplayIcon" '"$INSTDIR\${APP_NAME_SHORT}.exe"' 416 | WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\${APP_NAME_SHORT}" "DisplayVersion" "${APP_VERSION}" 417 | WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\${APP_NAME_SHORT}" "Publisher" "${APP_AUTHOR}" 418 | WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\${APP_NAME_SHORT}" "URLInfoAbout" "${APP_WEBSITE}" 419 | 420 | WriteRegDWORD HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\${APP_NAME_SHORT}" "Installed" 1 421 | WriteRegDWORD HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\${APP_NAME_SHORT}" "NoModify" 1 422 | WriteRegDWORD HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\${APP_NAME_SHORT}" "NoRepair" 1 423 | FunctionEnd 424 | 425 | Function RunApplication 426 | ${If} ${FileExists} $INSTDIR\${APP_NAME_SHORT}.exe 427 | Exec '"$INSTDIR\${APP_NAME_SHORT}.exe"' 428 | ${EndIf} 429 | FunctionEnd 430 | 431 | Function ShowReleaseNotes 432 | IfSilent skip 433 | 434 | ${If} ${FileExists} $INSTDIR\History.txt 435 | ExecShell "" '"$INSTDIR\History.txt"' 436 | ${EndIf} 437 | 438 | skip: 439 | FunctionEnd 440 | 441 | Function IsPortable 442 | IfFileExists "$INSTDIR\portable.dat" portable 0 443 | IfFileExists "$INSTDIR\${APP_NAME_SHORT}.ini" portable not_portable 444 | 445 | portable: 446 | SectionSetFlags ${SecPortable} ${SF_SELECTED} 447 | 448 | SectionSetFlags ${SecShortcut1} 0 449 | SectionSetFlags ${SecShortcut2} 0 450 | 451 | Goto skip 452 | 453 | not_portable: 454 | SectionSetFlags ${SecPortable} 0 455 | 456 | SectionSetFlags ${SecShortcut1} ${SF_SELECTED} 457 | SectionSetFlags ${SecShortcut2} ${SF_SELECTED} 458 | 459 | skip: 460 | FunctionEnd 461 | 462 | ; Version info 463 | VIAddVersionKey "Comments" "${APP_WEBSITE}" 464 | VIAddVersionKey "CompanyName" "${APP_AUTHOR}" 465 | VIAddVersionKey "FileDescription" "${APP_NAME}" 466 | VIAddVersionKey "FileVersion" "${APP_VERSION}" 467 | VIAddVersionKey "InternalName" "${APP_NAME_SHORT}" 468 | VIAddVersionKey "LegalCopyright" "${COPYRIGHT}" 469 | VIAddVersionKey "OriginalFilename" "${APP_NAME_SHORT}-${APP_VERSION}-setup.exe" 470 | VIAddVersionKey "ProductName" "${APP_NAME}" 471 | VIAddVersionKey "ProductVersion" "${APP_VERSION}" 472 | VIProductVersion "${APP_VERSION}.0.0" 473 | --------------------------------------------------------------------------------