Compare commits

...

1206 Commits

Author SHA1 Message Date
Akash Mozumdar
df5b830d17 revert the dumbest change I ever made 2023-08-26 07:36:48 -04:00
Akash Mozumdar
f3fbe04409 standardize file names 2023-04-24 14:48:16 -04:00
Glaringsoul
5adeb1fab7 Update README_GER.md 2023-04-24 14:40:55 -04:00
Glaringsoul
5c7b41dd84 Create README_GER.md
Translated the README.MD by hand into German.

Will probably tackle the text.ccp next, although I haven't looked at it, so I cannot really say how long that can take.

Some parts especially words that are commonly used in German as Anglicisms are left as is, this is not an oversight by me, but intentional.
2023-04-24 14:40:55 -04:00
秋庭里香
92e2e85191 Update Chinese (Simplified) translation
and add a link to text.cpp in the README.md
2023-04-24 14:34:40 -04:00
Blu3train
0bc23ba2c7 update ShinaRio 2.50+ 2023-04-24 14:28:49 -04:00
Akash Mozumdar
fd8a090e28 remove workaround for fixed msvc bug 2023-04-21 19:43:32 -04:00
Akash Mozumdar
aa00d9e789 ignore extra parameters #947 2023-04-21 19:42:54 -04:00
Akash Mozumdar
cfa11c5646 use drive relative path 2023-04-21 19:23:14 -04:00
Akash Mozumdar
446f6d44e6 upgrade build configuration to not rely on qt creator which is no longer required by qt installer 2023-04-21 18:31:17 -04:00
Akash Mozumdar
ff1a7a358c handle toggling of cache properly, error cases consistently, and stop saving cache unnecessarily 2023-04-21 17:23:25 -04:00
Akash Mozumdar
60a5d74eda improve config loading and prevent crashes 2023-04-21 16:49:22 -04:00
Akash Mozumdar
4cebe9462f insane hack to work around false positive in SearchForText 2023-04-21 16:41:00 -04:00
Akash Mozumdar
d97ebd4fbb edge is also usable for devtools 2023-04-21 16:35:19 -04:00
Akash Mozumdar
233e75225c add link to stylesheets 2022-09-10 20:01:48 -04:00
Akash Mozumdar
72ad51f039 tighten abel detection 2022-08-09 00:02:27 -04:00
Akash Mozumdar
39fcfaf644 restore shinario hook 2022-08-08 23:13:51 -04:00
RoARene317
5eb0440f01 Fix some typos 2022-08-08 18:39:16 -06:00
RoARene317
c4ca84d39b Change from text --> Teks in Indonesian 2022-08-08 18:39:16 -06:00
RoARene317
c7b818aa75 Removing some english and fix translations
Fix some Indonesian translations that still english to make it Indonesian but still understandable for young people.
2022-08-08 18:39:16 -06:00
Blu3train
2cb45ab9e3 DeepL Translate: Added Turkish and Indonesian 2022-07-26 23:14:57 -06:00
Blu3train
72132239a5 new browser size setting for DeepL blocking in hidden mode 2022-07-26 22:29:29 -06:00
Blu3train
5eaafbe81c Devtools extensions: chrome user data cache error if there are spaces in path 2022-07-26 22:29:29 -06:00
Akash Mozumdar
97f2bc6304 change qt version to make alpha builds compatible with releases 2022-07-26 23:59:26 -04:00
Akash Mozumdar
f732f488e6
update translation tasks 2022-01-31 05:05:03 -07:00
Blu3train
3e283975c8 Fix auto detect source language selection 2022-01-31 01:18:10 -07:00
Akash Mozumdar
5b35f121d2 reorganize and update docs 2022-01-31 01:16:53 -07:00
Akash Mozumdar
b28aa3d189 clarify text 2022-01-31 00:48:50 -07:00
Akash Mozumdar
faeda02f2b allow users to type capital x 2022-01-30 01:00:00 -07:00
Akash Mozumdar
ef55ef0c61 improve chrome locating and ignore empty translation 2022-01-29 14:20:57 -07:00
Akash Mozumdar
c9240d9e68 improve process selection 2022-01-29 12:49:48 -07:00
Akash Mozumdar
6ba4f9c909 fix incorrect reading 2022-01-23 00:52:15 -07:00
Akash Mozumdar
7c9f861926 most people won't bother with styler 2022-01-23 00:30:57 -07:00
Akash Mozumdar
e158219266 small corrections 2022-01-23 00:29:48 -07:00
Akash Mozumdar
3a9fdfb9ae fix error messaging 2022-01-02 00:50:25 -07:00
Akash Mozumdar
dad036083a destroy garbage sentences 2022-01-01 22:06:10 -07:00
Blu3train
b09783d111 Moved test after filter 2022-01-01 22:06:10 -07:00
Blu3train
306b9f0525 Don't translate text consisting of spaces only 2022-01-01 22:06:10 -07:00
Akash Mozumdar
823987e79f fix crash on windows 11 2022-01-01 22:05:53 -07:00
Akash Mozumdar
fdc8a13b36 clarify instructions 2022-01-01 22:05:53 -07:00
Akash Mozumdar
90b58da867 move file parsing to load time 2022-01-01 22:05:53 -07:00
Blu3train
2316f59415 Added global modifier option 2022-01-01 22:05:53 -07:00
Blu3train
06b93e3def Added ignore case modifier option 2022-01-01 22:05:53 -07:00
Blu3train
fde588d922 Corrected text translation 2022-01-01 22:05:53 -07:00
Blu3train
8e0e17c59b Regex Replacer Extension 2022-01-01 22:05:53 -07:00
Blu3train
e6ee5ecf3d Regex Replacer Extension 2022-01-01 22:05:53 -07:00
k1mlka
49133974b3 add hook artemis3 2022-01-01 22:05:34 -07:00
Akash Mozumdar
4b67b8a5cf make logic clearer, remove second call to size 2022-01-01 22:05:21 -07:00
Blu3train
663bb97fff fix rateLimitTimespan issue #665 2022-01-01 22:05:21 -07:00
Akash Mozumdar
953a537cb8 squashing final bugs with resizing 2022-01-01 22:05:07 -07:00
Akash Mozumdar
8ac4a072a9 bug bashing 2022-01-01 22:05:07 -07:00
Akash Mozumdar
45279d26dc consolidate features and add context menu to edge 2022-01-01 22:05:07 -07:00
Blu3train
5df90e2c01 Added all the features developed by the last PR 2022-01-01 22:05:07 -07:00
Akash Mozumdar
0346f95707 fix x64 build 2022-01-01 22:05:07 -07:00
Akash Mozumdar
1f19a9e74d use system global hotkey 2022-01-01 22:05:07 -07:00
Blu3train
b236a50a8f Fixed bug in calculating height in autoresize mode 2022-01-01 22:05:07 -07:00
Blu3train
2f08ab1e9b added Auto Resize and Click Through features 2022-01-01 22:05:07 -07:00
Akash Mozumdar
ad3f09d8ef bing api update 2022-01-01 21:58:04 -07:00
Akash Mozumdar
44c8e4f94c be more careful inserting waffle 2022-01-01 21:29:13 -07:00
Akash Mozumdar
a17d8d5993 bundle unicode font 2021-11-28 04:57:16 -07:00
Akash Mozumdar
bf49f0bca6 rename 2021-11-13 14:08:16 -07:00
Akash Mozumdar
15db478e62 rearrange build for less confusion and to build CLI faster 2021-11-13 13:38:32 -07:00
Akash Mozumdar
46bc6ec84b add emails 2021-11-08 07:24:22 -07:00
Akash Mozumdar
b3b665fb65 fix debug build 2021-11-08 07:07:34 -07:00
Akash Mozumdar
17b5884149 add basic upstream upgrades 2021-11-08 06:41:28 -07:00
Akash Mozumdar
742b7cacf3 update docs 2021-11-08 06:21:17 -07:00
Akash Mozumdar
4ff150b674 update docs 2021-11-07 06:18:42 -07:00
Akash Mozumdar
e83579ed7c hopefully remove dependency on vcredist for good 2021-11-07 05:54:34 -07:00
Akash Mozumdar
febff243d3 fix bing translate 2021-11-06 17:48:45 -06:00
Akash Mozumdar
1a29f5670a fix crash 2021-11-06 04:53:31 -06:00
Akash Mozumdar
6ed3d9c951 update path 2021-10-23 00:20:44 -06:00
Akash Mozumdar
492c843e5c update qt version to one supported by appveyor 2021-10-23 00:15:05 -06:00
Akash Mozumdar
574eeec79e deepl bugfix 2021-09-06 03:51:51 -06:00
Akash Mozumdar
6c74df864a update deepl devtools 2021-09-06 02:04:25 -06:00
Akash Mozumdar
ca93120442 better search in old horkeye 2021-09-06 02:03:22 -06:00
Akash Mozumdar
eddf1bf0ee ui improvements 2021-09-06 02:02:36 -06:00
Akash Mozumdar
c9ad8880fb fix garbage filter bug 2021-08-18 00:41:34 -06:00
Akash Mozumdar
fb9fb5d54a add universal links to thread linker 2021-08-18 00:22:23 -06:00
Akash Mozumdar
085bec6d5b always open to root 2021-08-17 23:08:42 -06:00
Akash Mozumdar
695672ce9c support larger sentences by default, possibly with repetition (since builtin filter is disabled by default) 2021-08-17 23:08:22 -06:00
Akash Mozumdar
bda08d500e fix minor bugs with rate limiter, update urls 2021-08-17 22:48:16 -06:00
Akash Mozumdar
5862bf50ac small fixes 2021-07-01 23:50:47 -06:00
Akash Mozumdar
493e80e568 change file name and remove global to fix threading issue 2021-07-01 23:45:01 -06:00
Blu3train
db31e19997 cache filename extension translators: added target language name 2021-07-01 23:45:01 -06:00
Akash Mozumdar
d64a2c05b4 get rid of gettickcount 2021-07-01 13:35:33 -06:00
Akash Mozumdar
f8874bf8a0 add rpg maker mz hook 2021-06-30 20:59:07 -06:00
Akash Mozumdar
b28b68d218 upgrade character deduplication algorithm 2021-06-30 20:58:40 -06:00
Akash Mozumdar
50f5b183f1 fix sorting of names 2021-06-30 20:58:04 -06:00
Akash Mozumdar
a566818ad4 improve ui for rate limiter 2021-06-30 19:14:49 -06:00
Akash Mozumdar
ac95d873f1 improve translation extension ui, add language detection for papago and systran, add more languages to systran, improve deepl translation and bing error handling 2021-06-28 22:24:59 -06:00
Akash Mozumdar
444974ee8a improve error handling and fix build 2021-06-07 00:05:45 -06:00
Akash Mozumdar
3b948c3359 update credits 2021-06-06 22:43:49 -06:00
Akash Mozumdar
1eaa054b33 improve devtools resource management and style fixes 2021-06-06 22:43:40 -06:00
Akash Mozumdar
647058e9aa
Merge pull request #568 from Blu3train/devtools_papago_translate
DevTools Papago Translate
2021-06-06 03:25:50 -06:00
Akash Mozumdar
918c877e04
Merge branch 'master' into devtools_papago_translate 2021-06-06 03:15:33 -06:00
Akash Mozumdar
2cf146652d integrate with devtools refactor and remove unneeded code 2021-06-06 03:13:45 -06:00
Blu3train
89198fd4fe Updated CMakeLists.txt and deploy.ps1 files 2021-06-06 03:13:45 -06:00
Blu3train
9e0c95be98 Updated CMakeLists.txt and deploy.ps1 files 2021-06-06 03:13:45 -06:00
Blu3train
7425ae770f DevTools Systran Translate 2021-06-06 03:13:45 -06:00
Akash Mozumdar
e0981f89e8 add new runtime file 2021-06-05 17:16:21 -06:00
Akash Mozumdar
eb1421c143 fix style issues and use blank icons 2021-06-05 17:16:21 -06:00
tera8m4
a1d3abb080 Create a new attach dialog window
New dialog window that shows processes with icons in QListView instead of ComboBox.
2021-06-05 17:16:21 -06:00
tera8m4
dd4b8cfbb5 Create small helpers to get icon from executable 2021-06-05 17:16:21 -06:00
Akash Mozumdar
615d372eeb missing vcredist causes some weird stuff... 2021-06-05 10:11:36 -06:00
Akash Mozumdar
e4cea83ae4 integrate with devtools refactor and remove unneeded code 2021-06-05 09:43:53 -06:00
Akash Mozumdar
9e26c462e7 Merge remote-tracking branch 'origin/master' into pr/568-devtools-papago-translate 2021-06-05 09:41:33 -06:00
Akash Mozumdar
0df2526560 fix botched merge 2021-06-05 08:50:00 -06:00
Akash Mozumdar
de3cad37f8
Merge pull request #578 from Blu3train/devtools_fixed_failure_remove_devtoolscache
devtools: fixed failure to remove devtoolscache when closing the program
2021-06-05 08:31:46 -06:00
Akash Mozumdar
8c9faf9947
Merge branch 'master' into devtools_fixed_failure_remove_devtoolscache 2021-06-05 08:31:04 -06:00
Akash Mozumdar
76fea31fb3 fix bugs and add new languages to deepl 2021-06-05 08:25:46 -06:00
Akash Mozumdar
795ecce45e fix bing trranslate 2021-06-05 08:15:19 -06:00
Akash Mozumdar
675695cde1 prevent write starvation 2021-06-05 03:00:01 -06:00
Akash Mozumdar
9f8e523ce3 (hopefully) improved renpy hook 2021-06-05 02:30:34 -06:00
Akash Mozumdar
d74dcdc286 fix thread linker crash and other minor improvements 2021-06-05 02:20:04 -06:00
Akash Mozumdar
b4aa113fac turn off repetiton filter by default, use google by default, increase max hook count, replace regex with capture 2021-06-05 01:48:10 -06:00
Blu3train
1e03e69d00 devtools: fixed failure to remove devtoolscache when closing the browser 2021-05-26 00:41:19 +02:00
Blu3train
76d716956d DevTools Papago Translate 2021-05-15 11:07:22 +02:00
Akash Mozumdar
1782292662 fix wrong output for non utf text 2021-04-28 21:02:31 -06:00
Akash Mozumdar
3c33d11d80 fix function finding (restore to ithvnr) - should fix system40 and rugp2 hooks 2021-04-28 21:01:28 -06:00
Akash Mozumdar
a8ae2156d9 fix build errors 2021-04-24 17:12:40 -06:00
Akash Mozumdar
f26f30dce7 small fixes 2021-04-24 16:45:17 -06:00
MishaIac
1fcacc7bc9 Update text.cpp 2021-04-24 16:40:11 -06:00
Akash Mozumdar
71fe1410c2 fix error handling 2021-04-24 16:39:52 -06:00
Akash Mozumdar
aa0c0e0047 build system improvements 2021-03-13 00:51:36 -07:00
Akash Mozumdar
74121d7484 doc fixes 2021-03-12 08:23:41 -07:00
Akash Mozumdar
8543d49192 fix google translate and devtools deepl translate, add chrome file selector, add garbage filter 2021-03-09 21:32:56 -07:00
Akash Mozumdar
5537679442 update credits 2021-03-08 08:48:37 -07:00
Akash Mozumdar
20f35a02ed improve extension UI 2021-03-08 08:46:34 -07:00
Akash Mozumdar
1bab6956a8 improve renpy detection 2021-03-08 08:41:50 -07:00
Akash Mozumdar
acc85f3a86 minor improvements 2021-03-08 08:41:34 -07:00
Akash Mozumdar
54a285b53b improve hook search ux 2021-03-08 08:37:02 -07:00
Akash Mozumdar
bf97055155 small fixes 2021-02-21 14:15:59 -07:00
Akash Mozumdar
ad83eb290a update docs 2021-01-31 08:02:10 -07:00
Akash Mozumdar
c7ae06c121 final? cleanup 2021-01-30 23:43:25 -07:00
Akash Mozumdar
fdc53a393d Appropriate name 2021-01-30 23:17:44 -07:00
Akash Mozumdar
68b382e5fa upload alpha releases (with debug info) 2021-01-30 23:01:32 -07:00
Akash Mozumdar
9e6f638488 integrate new extensions 2021-01-30 16:42:29 -07:00
Akash Mozumdar
fcd735283d temporary remove regex replacement 2021-01-30 13:11:46 -07:00
Akash Mozumdar
a03c939043 fix small bugs 2021-01-30 12:45:37 -07:00
Akash Mozumdar
faa14f8cb9 fix crash (don't use avx instructions) 2021-01-22 09:11:35 -07:00
Akash Mozumdar
e00b831e3d fix localization bug, add opption to select language translating from, and add error message to devtools deepl 2021-01-21 07:07:49 -07:00
Akash Mozumdar
084f26a72d allow replacement in regex filter and make names consistent 2021-01-21 07:07:49 -07:00
lgztx96
4219115a40
update waffle hook (#461)
* update waffle hook
2021-01-16 22:02:46 -07:00
Akash Mozumdar
43aa1d6711 set up appveyor and improve qt version finding 2021-01-15 21:39:38 -07:00
Akash Mozumdar
662ca45321 add styler extension 2021-01-15 09:53:57 -07:00
Akash Mozumdar
dcff7e550c
Merge pull request #416 from zeheyler/devtools_deepl_extension
add devtools api and new deepl extension
2021-01-15 09:35:03 -07:00
Akash Mozumdar
c62c586a59 fix bugs and autoload lua 2021-01-15 09:32:23 -07:00
Akash Mozumdar
fc81b17a3c massive refactor, also fix newline issue and google translate throw and different number formats for process id 2021-01-15 06:07:23 -07:00
Akash Mozumdar
3a34f989e5 Merge branch 'master' into pr/416-add-devtools-api-and-new-deepl-extension 2020-12-16 12:23:52 -07:00
Akash Mozumdar
2c9ac1da3c improve JSON parser 2020-12-15 07:28:12 -07:00
Akash Mozumdar
95b145bece revert separation line 2020-12-14 19:10:47 -07:00
Akash Mozumdar
457aed96c2 fix unicode mangling 2020-12-14 10:37:54 -07:00
Akash Mozumdar
4783469a0a style 2020-12-14 07:48:02 -07:00
Akash Mozumdar
0680de413a update credits 2020-12-14 06:34:28 -07:00
k1mlka
29d23935ad versatile way 2020-12-14 06:26:51 -07:00
k1mlka
3aee03a244 update specific game hook 2020-12-14 06:26:51 -07:00
Akash Mozumdar
268f40771d improve reliability of translations 2020-12-14 06:26:01 -07:00
zeheyler
7eb45899c0 fix target language check
Fixed target language check
2020-12-13 00:56:00 +03:00
zeheyler
ea6ddc7a7c small improvements
Added check for the empty answer from the translator
Small refactors
2020-12-09 14:00:57 +03:00
zeheyler
6958b22e68 fix target selector check
Fixed target selector check in some cases
Changed line endings from CRLF to LF
2020-11-04 20:51:23 +03:00
zeheyler
9bb7fbff06 add source language selection and rename variables
Added source language selection since autodetect fails sometimes
Refactored names of variables
2020-11-04 01:23:20 +03:00
Akash Mozumdar
1b71b3ee86 update links 2020-11-02 06:58:44 -07:00
Akash Mozumdar
1634120669 give credit 2020-11-02 06:44:46 -07:00
Gratusfr
f8b4bc1ae3 ortho + partial translation of CODE_INFODUMP 2020-11-02 06:39:16 -07:00
Akash Mozumdar
01b6b6de16 fix things not being localized and settings not loading 2020-11-02 06:27:21 -07:00
zeheyler
4b46057800 add backup check method
Added backup check method in case the first method fails
2020-10-29 02:08:11 +03:00
zeheyler
93b8711bf8 refactor errorcode
Refactored error processing
2020-10-22 23:15:45 +03:00
zeheyler
8ab1e576bd add call queue check
Added queue check for multiple calls from the main program
2020-10-21 00:10:58 +03:00
zeheyler
3af4217075 add check for outdated doc and refactor input text modifications
Added the flag for checking if webdocument structure is outdated
Refactor text modifications before processing to the translator
2020-10-20 02:08:59 +03:00
zeheyler
2ab780a491 add user-agent switch method
Add method that changes user-agent when in the headless mode
2020-10-18 16:16:06 +03:00
zeheyler
73d1f21bc1 add notification alert
When timeout from the net the method searches for notifications from the translator and put them to output
2020-10-15 23:23:41 +03:00
zeheyler
a3ebaf0023 add event catch method and refactor error messages
Added method that catches specified events from the page
Refactored error messages
2020-10-15 02:32:58 +03:00
Akash Mozumdar
f0a1690c92 fix deepl 2020-10-13 20:19:53 -06:00
zeheyler
6b54ec0733 add devtools api and new deepl extension
Added the DevTools API and the wrapper with  the use of the QtWebSockets library.
Added a new DeepL extension which uses the DevTools API.
2020-10-14 03:37:03 +03:00
Akash Mozumdar
fe1cdfc947 custom filetype for extensions 2020-09-10 07:18:17 -06:00
Akash Mozumdar
f6742de3d5 parse shortened hook codes 2020-09-10 07:12:50 -06:00
Akash Mozumdar
dc1f819952 fix infinite loop 2020-09-09 15:13:19 -06:00
Akash Mozumdar
8e67827ae6 update anex86 hook 2020-09-09 14:58:19 -06:00
Akash Mozumdar
93238e13ed fix line endings 2020-08-12 09:31:48 -06:00
StarFang208
ef9d05f10e Updated text.cpp
Added the untranslated lines
2020-08-12 09:31:48 -06:00
Akash Mozumdar
b4303e4d4d refactor and add error handling 2020-08-12 03:31:46 -06:00
Jazzinghen
39b0882bbf Look for start of function with memory functions 2020-08-12 03:31:46 -06:00
Jazzinghen
79058c6811 Added Database extractor hook 2020-08-12 03:31:46 -06:00
Jazzinghen
681fc5b628 Updated search code to look for less memory 2020-08-12 03:31:46 -06:00
Jazzinghen
cc7d02d8a1 Removed useless import that I used for debugging 2020-08-12 03:31:46 -06:00
Jazzinghen
409ad121ae Removed debug message 2020-08-12 03:31:46 -06:00
Jazzinghen
ae35f82199 Completed the hook. 2020-08-12 03:31:46 -06:00
Jazzinghen
51b217169e Finally have something that runs and crashes.
This thing is pointing at a wrong memory location due to the fact that
there's another function that STARTS THE SAME.

I'll use another method to do this.
2020-08-12 03:31:46 -06:00
Jazzinghen
261da66d74 Adding TokyoNecro matching function 2020-08-12 03:31:46 -06:00
Akash Mozumdar
fe3e152553 steal updated chinese translation 2020-08-12 02:27:03 -06:00
Akash Mozumdar
341b6915c8 steal some hooks for willplus and waffle 2020-08-12 02:18:39 -06:00
Akash Mozumdar
6da63208c7 try both deepl api versions 2020-08-12 01:42:24 -06:00
Akash Mozumdar
9c006bce17 ignore default config file 2020-08-12 01:41:13 -06:00
Akash Mozumdar
c3c73d2ac2 fix french translation 2020-07-02 13:06:24 -06:00
Akash Mozumdar
ae5fa5fb9f
Fix link 2020-05-16 20:43:07 -06:00
Akash Mozumdar
d86f22877c document 2020-05-16 07:11:49 -06:00
Akash Mozumdar
287776903a add french readme 2020-05-16 03:00:50 -06:00
Akash Mozumdar
166ec1456b add dummy readme 2020-05-14 18:39:13 -06:00
Akash Mozumdar
5739af5a3f add french translation 2020-05-14 18:35:26 -06:00
Akash Mozumdar
4b0fc12794 dynamic port 2020-05-14 18:09:52 -06:00
TokcDK
3e21a6b4ba Updated Russian localization 2020-05-14 18:09:42 -06:00
Akash Mozumdar
a2d1374ad4 get rid of donate buttons (i'm doing great, please give the money to those who need it) 2020-04-25 22:01:52 -06:00
Akash Mozumdar
a129394fb5 dont need that param anymore 2020-04-25 20:39:12 -06:00
Akash Mozumdar
e20a12862c default setting tweak 2020-04-25 20:36:58 -06:00
Akash Mozumdar
9711ce94c9 thread linker doesn't append sentences anymore 2020-04-25 20:34:53 -06:00
Akash Mozumdar
535dac480a error when failed to create config file 2020-04-25 20:07:42 -06:00
Akash Mozumdar
d1887db2a5 use precompiled headers 2020-04-18 19:04:07 -06:00
Akash Mozumdar
386e09a81a fix translation extensions 2020-04-02 03:07:32 -06:00
Akash Mozumdar
4a320a3659 use bottom 16 bits of ctx (deals with aslr) 2020-03-30 13:19:35 -06:00
Akash Mozumdar
26960cf099 autofill regex 2020-03-29 20:55:25 -06:00
Akash Mozumdar
86415fca10 all official apis now supported and performance improvements 2020-03-29 20:55:12 -06:00
Akash Mozumdar
ecab473482 small fixes 2020-03-27 05:55:11 -06:00
Akash Mozumdar
accd231c64 add option to only translate selected thread 2020-03-27 04:07:05 -06:00
Akash Mozumdar
8a710659dc merge google and google cloud 2020-03-26 06:12:20 -06:00
Akash Mozumdar
87c056a5b3 add settings to translation dialog and implement deepl 2020-03-26 06:05:42 -06:00
Akash Mozumdar
19f35f743c inject correctly in CLI 2020-03-26 05:39:25 -06:00
Akash Mozumdar
bb0df71da2 suppress mono_* when configured 2020-03-26 03:13:21 -06:00
Akash Mozumdar
af2ba72f39 fix stack overflow 2020-03-23 19:37:11 -06:00
Akash Mozumdar
402b685820 sign all at once 2020-03-17 22:09:20 -06:00
Akash Mozumdar
e4d9b70040 better logging for translation extensions 2020-03-17 22:01:16 -06:00
Akash Mozumdar
bc01179626 multiple mono hooks and fix crash 2020-03-17 13:53:46 -06:00
Akash Mozumdar
d1d453f359 remove weird stuff 2020-03-16 03:04:40 -06:00
Akash Mozumdar
ac4cec9708 add gcp translator and add continous dialog for translators and update language lists and make extension dialogs uncloseable without properly removing the extension 2020-03-16 02:56:04 -06:00
Akash Mozumdar
df3d3a0355 sign all files 2020-03-16 02:51:45 -06:00
Akash Mozumdar
39e81bb904 escape unformatted 2020-03-07 04:06:45 -07:00
Akash Mozumdar
d6412ca0b0 fix lookup with << 2020-03-07 03:45:26 -07:00
Akash Mozumdar
b9081f2472 fix large file being read wrong 2020-03-07 03:45:07 -07:00
Akash Mozumdar
a0306fea8b remove all nulls 2020-03-05 02:23:53 -07:00
Akash Mozumdar
2be920c50e cleaner output for mono 2020-03-05 02:19:04 -07:00
Akash Mozumdar
3ff31e0ac6 reorganize config files 2020-03-05 01:51:36 -07:00
Akash Mozumdar
aa4b980422 fix build 2020-03-05 01:18:12 -07:00
Akash Mozumdar
5fb78b87c7 easily configure games and save preference for jp locale 2020-03-05 01:17:45 -07:00
Akash Mozumdar
99be5fd40f configurable max size 2020-03-04 23:30:59 -07:00
Akash Mozumdar
6bd0c74c83 fix build 2020-03-03 00:13:15 -07:00
Akash Mozumdar
b4b26d50ca attach to current process 2020-03-02 23:49:31 -07:00
Akash Mozumdar
b515a17767 fix exception 2020-03-02 23:49:18 -07:00
Akash Mozumdar
6a61342d19 fix issue with too large sentence and topmost window blocking dictionary 2020-03-02 23:48:29 -07:00
Akash Mozumdar
fcb525df36 yeah...that was never a class. improve performance and add current process function 2020-03-02 23:38:51 -07:00
Akash Mozumdar
49432f689d (hopefully) fix silent crashes 2020-03-01 23:41:27 -07:00
Akash Mozumdar
74b4ae9da5 build tests 2020-03-01 06:10:06 -07:00
Akash Mozumdar
47836f20f3 oops, spdefault uses address bounds 2020-03-01 06:04:22 -07:00
Akash Mozumdar
360254d5bf pcsx2 search 2020-03-01 05:42:34 -07:00
Akash Mozumdar
b7978ff512 detect precompiled unity 2020-03-01 04:56:20 -07:00
Akash Mozumdar
0d4af5c140 fix tinkerbell and add getstringtype and foldstring 2020-02-29 18:32:58 -07:00
Akash Mozumdar
ffff4f2eb9 tiny fixes 2020-02-29 17:51:38 -07:00
Akash Mozumdar
0688daf1da fiix build 2020-02-29 05:33:14 -07:00
Akash Mozumdar
a490ebc274 add italian to readmes 2020-02-29 01:42:39 -07:00
StarFang208
a40ae621af
Italian Readme and Text translation (
* Italian Readme and Text file

Added the text.cpp wit the italian lines, as well the italian README.

* update from old fork point

* fix line endings

Co-authored-by: Akash Mozumdar <akashmozumdar@gmail.com>
2020-02-28 05:13:59 -07:00
Akash Mozumdar
b79af59e3d use qregularexpression as hook search filter 2020-02-28 04:21:07 -07:00
Akash Mozumdar
c2725e8518 add BOM 2020-02-28 03:17:41 -07:00
Akash Mozumdar
22e0d38a1e small fixes 2020-02-28 02:34:07 -07:00
Akash Mozumdar
f6cf3c9c62 save regex 2020-02-28 02:33:51 -07:00
Akash Mozumdar
16540bfe69 move utility functions into common.h and module.h 2020-02-28 00:34:34 -07:00
Akash Mozumdar
200c01f4ab more reliable default font and only load window position if on screen 2020-02-28 00:12:23 -07:00
Akash Mozumdar
d6b0114f27 using newer qt 2020-02-27 05:33:22 -07:00
Akash Mozumdar
73ccb38641 why did i ever think that was a good idea? 2020-02-27 04:42:29 -07:00
Akash Mozumdar
c8853a1af8 dont remove mono if flooding, instead use config file for which hook to insert 2020-02-27 04:26:01 -07:00
Akash Mozumdar
565f99cced implement deinflection 2020-02-26 01:01:06 -07:00
Akash Mozumdar
dc48f2a3c8 performance improvements and autoselect native language for translations 2020-02-25 04:39:27 -07:00
Akash Mozumdar
2e23d4016d better position for dictionary 2020-02-23 00:44:26 -07:00
Akash Mozumdar
2d2a3dedb9 make dictionary mouseover and make extra window larger to start 2020-02-22 20:58:47 -07:00
Akash Mozumdar
b61272a5e6 implement replacer wildcard and optimize memory usage when loading files 2020-02-16 17:58:09 -07:00
Akash Mozumdar
b0eeca5b36 no more auto removing read hooks 2020-02-12 00:30:01 -07:00
Akash Mozumdar
3d88f2b9ec add language reselect message 2020-02-12 00:06:01 -07:00
Akash Mozumdar
e14c9b6a09 fix v8 hook x64 2020-02-11 23:35:23 -07:00
Akash Mozumdar
666b0a49c0 tiny fixes 2020-02-11 23:34:03 -07:00
Akash Mozumdar
6f01d46315 sign stuff 2020-02-06 02:52:27 -07:00
Akash Mozumdar
1300f3c540 more documentation 2020-02-01 19:01:37 -07:00
Akash Mozumdar
42e4973721 bing api changed 2020-01-31 23:24:49 -07:00
Akash Mozumdar
b1769b7cd1 more reliable qt5 location 2020-01-26 16:53:09 -07:00
Akash Mozumdar
0f7a648403 support hex dump codes 2020-01-19 14:23:30 -07:00
Akash Mozumdar
a8202893e3 auto search relevant modules 2020-01-19 04:15:02 -07:00
Akash Mozumdar
0ace753199 sanitize surrogate characters which cause qt to crash if unpaired 2020-01-18 23:25:57 -07:00
Akash Mozumdar
81d5b745aa dont use legacy api 2020-01-17 21:51:11 -07:00
Akash Mozumdar
622a7214f3 better renpy hook 2020-01-16 17:48:31 -07:00
Akash Mozumdar
2482c26316 fix issues with extensions having dot in name 2020-01-14 17:24:27 -07:00
Akash Mozumdar
8e719b2964 readd sum cache, it is useful 2020-01-12 02:22:39 -07:00
Akash Mozumdar
cbd5b01385 more hook search instructions 2019-12-29 09:12:34 -05:00
Akash Mozumdar
7b3136dcc7 no clue why that was commented out 2019-12-29 08:58:06 -05:00
Akash Mozumdar
34feb59e8c dont put conosle on extra window 2019-12-29 08:57:33 -05:00
Akash Mozumdar
4409fb88b0 custom opacity message 2019-12-29 08:54:55 -05:00
Akash Mozumdar
3414e14bc4 no longer looking for work ^_^ 2019-12-27 08:03:13 -05:00
Akash Mozumdar
6eda5651a4 fix non latching issue 2019-12-26 19:18:02 -05:00
TokcDK
5c8edc63d6 little corrections of typos 2019-12-26 19:14:51 -05:00
Akash Mozumdar
aeb0cafb15 new cotopha hook 2019-11-23 12:44:26 -05:00
Akash Mozumdar
b7b9a315c4 use lstrlen for atelier 2019-11-21 20:12:36 -05:00
Akash Mozumdar
3db37ac450 limit hook searches to a named module, also make 0 CCs default for x86 due to perf/stability improvements 2019-11-10 14:13:54 -05:00
Akash Mozumdar
bf2838f3fc text outlines implemented and fix low alpha values being saved wrong 2019-11-10 12:48:32 -05:00
Akash Mozumdar
06d5242842 better? ponscripter detection 2019-10-07 01:01:47 -04:00
Akash Mozumdar
6a6c208c20 repetition filters bail after 30 seconds 2019-10-07 01:00:54 -04:00
Akash Mozumdar
9e86ecb987 update translations 2019-10-03 16:02:02 -04:00
Akash Mozumdar
415ceb4694 optimize performance 2019-10-03 16:00:19 -04:00
Akash Mozumdar
822c2ae233 add korean translation 2019-10-03 15:27:41 -04:00
Akash Mozumdar
07bd54d726 finish integrating thai translation 2019-10-02 06:14:40 -04:00
Akash Mozumdar
61f67766a5 fix mistakes 2019-10-02 05:45:14 -04:00
Thanawit Gerdprasert
a5e5417791 added thai readme and text.cpp 2019-10-02 05:45:14 -04:00
Akash Mozumdar
2436571768 lazy man's mocking 2019-10-02 05:30:14 -04:00
Akash Mozumdar
1a17043924 forgot to remove that 2019-10-02 05:19:20 -04:00
Akash Mozumdar
0f2bbaace3 seems that's safe 2019-10-02 05:18:34 -04:00
Akash Mozumdar
b685bfc780 nope, can't order it that way 2019-09-30 08:51:34 -04:00
Akash Mozumdar
8b345a4377 fix mono perf issues (hopefully) 2019-09-30 08:45:01 -04:00
Akash Mozumdar
17aa20109f ponscripter hook 2019-09-30 08:34:07 -04:00
Akash Mozumdar
cc543a682c fix rcode disconnecting and hcode crashing on removal 2019-09-30 08:32:56 -04:00
Akash Mozumdar
54f60f7657 small fixes 2019-09-26 07:07:58 -04:00
Akash Mozumdar
268da6e41a palfontdrawtext hook 2019-09-26 07:06:32 -04:00
Akash Mozumdar
1aa381dff3 correct order 2019-09-16 17:19:54 -04:00
Akash Mozumdar
f9e1e61d51 refactor 2019-09-16 17:14:59 -04:00
Akash Mozumdar
46c4f163ba better tanuki detection 2019-09-15 12:58:15 -04:00
Akash Mozumdar
f7f6b18ad2 use better cache size and recover malloc fails 2019-09-15 12:57:26 -04:00
Akash Mozumdar
ab916126b9 make items larger 2019-09-12 16:12:30 -04:00
Akash Mozumdar
90bed9c2db performance improvements 2019-09-11 14:27:57 -04:00
Akash Mozumdar
770f219078 update ppsspp codes and massively improve performance of hook searching 2019-09-10 21:59:59 -04:00
Akash Mozumdar
0c9fc5c08a small fixes 2019-09-10 18:53:55 -04:00
Akash Mozumdar
5d77350e3c text encoding fixups 2019-09-09 22:08:24 -04:00
Akash Mozumdar
b7c9f0bfce add dictionary function to extra window, store colors as argb hex, decrease memory usage of replacer 2019-09-05 13:42:30 -04:00
Akash Mozumdar
9415e83511 rename 2019-09-04 12:29:48 -04:00
Akash Mozumdar
96f235732c hook search through all exported functions, hook search deeper into stack, fix bug with dlls being loaded next to each other 2019-09-04 12:23:23 -04:00
Akash Mozumdar
3794afc7c4 fix not recognizing mono-2.0-bdwgc 2019-09-03 08:41:08 -04:00
Akash Mozumdar
3d7e1be0ae annoying in development 2019-09-01 09:27:51 -04:00
Akash Mozumdar
1cf60785e3 can use right click and file dialog to add extension, also fix segfault when multiple copies of extension are added then removed 2019-08-29 13:46:21 -04:00
Akash Mozumdar
590c08e8d0 more consistent Light.vn hook 2019-08-29 13:09:31 -04:00
Akash Mozumdar
d3da9c06e3 update text 2019-08-20 00:38:28 -04:00
Akash Mozumdar
562c6e1a3a more consistent hook searching ui and fix critical error in searching for text and custom codepage for hook searches 2019-08-19 16:15:08 -04:00
Akash Mozumdar
3f004554f8 *.int files are too common 2019-08-19 16:00:38 -04:00
Akash Mozumdar
2333692448 fix out of memory error when history size too low 2019-08-19 15:58:53 -04:00
TsumiHokiro
c3274b8275 Update text.cpp
Got up-to-date with the changes, still have to backtrack.
2019-08-18 04:54:41 -04:00
Akash Mozumdar
c5b1e6988e refactor 2019-08-12 21:01:35 -04:00
Akash Mozumdar
749f8fa596 customizable max thread history size 2019-08-12 11:10:33 -04:00
Akash Mozumdar
58f58b02d8 save language and sort cache 2019-08-12 10:44:51 -04:00
Akash Mozumdar
4e3c1a93e0 scroll through extra window history 2019-08-12 10:43:34 -04:00
Akash Mozumdar
e4c74a55fe Light.vn hook 2019-08-12 06:13:45 -04:00
Akash Mozumdar
a8c239498c hide system processes 2019-08-10 04:18:30 -04:00
Akash Mozumdar
594198c921 treat unity as full string 2019-08-09 22:38:07 -04:00
Akash Mozumdar
2e99d6b3f2 don't filter useful things from google translate 2019-08-09 22:12:06 -04:00
Akash Mozumdar
60372a6b8a further optimize repetition detection 2019-08-09 22:11:34 -04:00
Akash Mozumdar
488a19134a more efficient suffix array construction 2019-08-08 06:23:15 -04:00
Akash Mozumdar
c38551905d upgrade repetition detection algorithm 2019-08-07 14:05:50 -04:00
Akash Mozumdar
f1e1257f21 refactor tests 2019-08-07 05:18:04 -04:00
Akash Mozumdar
8f6595e869 small fixes 2019-08-06 11:54:10 -04:00
Akash Mozumdar
29367928c3 fix finding js hook 2019-08-06 11:53:38 -04:00
Akash Mozumdar
4ab197c374 better ux for adding searched hook 2019-08-06 11:51:17 -04:00
Akash Mozumdar
e20f16da80 fix extension loading bug 2019-08-06 11:43:04 -04:00
Akash Mozumdar
703fc47972 add stress test, move common assets to assets folder 2019-08-06 11:41:49 -04:00
Akash Mozumdar
b4c7abd0a8 Merge branch 'master' of https://github.com/Artikash/Textractor 2019-07-29 01:50:25 -04:00
Akash Mozumdar
fe9c9435c5 more reliable shinario hook 2019-07-29 01:50:20 -04:00
Akash Mozumdar
e498442eb4 update readme 2019-07-29 01:50:04 -04:00
TokcDK
b7b263ba3b updated russian localization 2019-07-28 01:29:41 -04:00
Akash Mozumdar
9df97804ed fix fucked up install 2019-07-20 16:51:54 -04:00
Akash Mozumdar
79cd50ade7 fix for pensil 2019-07-20 16:51:00 -04:00
Akash Mozumdar
68e915192f build cli when deploying 2019-07-20 16:50:33 -04:00
Akash Mozumdar
c49c57dded streamline deployment 2019-07-20 10:03:40 -04:00
Akash Mozumdar
25d36ff301 only save if asked 2019-07-20 10:03:28 -04:00
Akash Mozumdar
ad20ca360d treat string as full line 2019-07-19 01:15:00 +03:00
Akash Mozumdar
e896cb033f dont double build host 2019-07-19 01:01:13 +03:00
Akash Mozumdar
b098a05a23 dialog for hook results and customize max result count 2019-07-16 21:55:40 +05:30
Akash Mozumdar
f85e3a3841 update translation apis 2019-07-16 14:03:46 +05:30
Akash Mozumdar
6a015f04bc use qfontdialog for extra and main window and massive perf improvement when loading large threads 2019-07-13 19:15:43 +05:30
Akash Mozumdar
f56e9ff07c seems sysallocstringbytelen is used mostly with null 2019-07-13 17:47:35 +05:30
Akash Mozumdar
5d83c9736b refactor replacer 2019-07-03 18:02:32 +05:30
Akash Mozumdar
c074bbf506 only cache translation on current thread 2019-07-03 12:23:10 +05:30
Akash Mozumdar
011a5418e9 user selected process forgetting, and other small fixes 2019-07-03 12:03:22 +05:30
Akash Mozumdar
c5c115c5da translation cache is saved every 50 translations 2019-06-29 16:29:33 +05:30
Akash Mozumdar
a3bb8dcf27 small fixes 2019-06-29 15:43:26 +05:30
Akash Mozumdar
ce33ca5732 better error message 2019-06-29 14:57:39 +05:30
Akash Mozumdar
447400980e fuck errors 2019-06-29 12:09:34 +05:30
TsumiHokiro
d0dd8fe3ae Update text.cpp
Editing of the text.
2019-06-29 10:10:21 +05:30
TsumiHokiro
7a5369365f Update README_PT.md
Editing the text
2019-06-29 10:10:21 +05:30
Akash Mozumdar
4192bfc412 organize qt includes 2019-06-29 08:46:31 +05:30
Akash Mozumdar
e7b5c1eb0c make text consistent 2019-06-29 08:35:47 +05:30
Akash Mozumdar
68c65bbea6 x64 v8/javascript/tyranobuilder hook 2019-06-27 12:41:15 +05:30
Akash Mozumdar
f87da8aedf add auto attaching and forgetting processes. make settings text consistent. other refactors 2019-06-27 12:39:44 +05:30
Akash Mozumdar
50af685d96 fix hcode generation for new length fun 2019-06-24 19:15:03 -04:00
Akash Mozumdar
fc2676e403 add installer 2019-06-21 23:19:29 -04:00
Akash Mozumdar
5f5c5d5a1a update translation api 2019-06-21 23:16:23 -04:00
Akash Mozumdar
ad846b90d4 fix crash 2019-06-21 22:55:29 -04:00
Akash Mozumdar
c4913fa897 typo 2019-06-21 02:22:09 -04:00
Akash Mozumdar
c03b4303db
Delete README_PO.md 2019-06-21 01:35:05 -04:00
TsumiHokiro
881b5ea3a7 Update README_PT.md
Proofreading my own Portuguese.
2019-06-21 01:31:47 -04:00
TsumiHokiro
91f282e75e Update README.md
Add Portuguese file.
2019-06-21 01:31:47 -04:00
TsumiHokiro
9982c0632d Update README_PT.md
Because there's no need for Portuguese in the PT.md
2019-06-21 01:31:47 -04:00
TsumiHokiro
0b0416efdb Create README_PT.md
Since I didn't finish all Portuguese translation yesterday,  here it is.
2019-06-21 01:31:47 -04:00
Akash Mozumdar
f1d46e9f73 refactor and perf improvements 2019-06-21 01:29:48 -04:00
Akash Mozumdar
6bac544c89 tie off i18n loose ends 2019-06-19 23:19:13 -04:00
TsumiHokiro
38b6558ba7 Adds Portuguese_BR (#162) 2019-06-19 22:29:31 -04:00
Akash Mozumdar
0ca9e301a0 i'm a moron 2019-06-19 06:43:05 -04:00
Akash Mozumdar
ea8e8fe3ed play nice with other (legacy) clipboard monitors 2019-06-18 20:18:42 -04:00
Akash Mozumdar
f7c2641d58 improve x64 mono 2019-06-18 04:48:48 -04:00
Akash Mozumdar
8db76f86e0 Merge branch 'master' of https://github.com/Artikash/Textractor 2019-06-18 00:42:02 -04:00
Akash Mozumdar
c915432763 add mono x64 support 2019-06-18 00:41:39 -04:00
Akash Mozumdar
d9ba1e5854 style cleanup 2019-06-18 00:39:50 -04:00
TokcDK
ddd2109399 Russian localization: couple fixes 2019-06-17 15:54:33 -04:00
Akash Mozumdar
68534619aa
Merge pull request #160 from Niakr1s/master
Feature: show/hide original text
2019-06-17 02:32:05 -04:00
Akash Mozumdar
e14ada3957
don't use machine translation 2019-06-17 02:30:59 -04:00
Niakr1s
5cc09d43ac SHOW_ORIGINAL and SHOW_ORIGINAL_INFO translation 2019-06-17 08:17:14 +03:00
Niakr1s
7b602393de
Merge branch 'master' into master 2019-06-17 07:55:36 +03:00
Akash Mozumdar
c85618cd0d allow custom fonts in extra window 2019-06-17 00:42:42 -04:00
Akash Mozumdar
566c0beb0a make thread linker asynchronous. also refactored, but in a way that makes me very concerned about the stability of host 2019-06-16 22:57:41 -04:00
Akash Mozumdar
3a103890ad cache translations in file 2019-06-16 19:43:59 -04:00
Akash Mozumdar
28d14bcc32 dont bother comparing to nullptr 2019-06-16 17:15:47 -04:00
Akash Mozumdar
a9a9c0cea5 note showing original works iff used after translation extension 2019-06-16 15:50:17 -04:00
Akash Mozumdar
88b797cd33 use a default searchparam unless user specifies they want custom settings 2019-06-16 15:28:59 -04:00
Akash Mozumdar
ef90382bbb fixed hookstrlen seeing ascii char+unicode space as null terminator 2019-06-16 15:24:28 -04:00
Niakr1s
f86a776536 Feature: Show original text 2019-06-16 21:03:27 +03:00
Akash Mozumdar
b4c5b31482 fix? old bgi 2019-06-15 03:23:46 -04:00
Akash Mozumdar
07d25ef25c i am a moron 2019-06-15 03:23:32 -04:00
Akash Mozumdar
0c61b0afcf so github doesnt allow utf8 in filenames... 2019-06-13 20:08:55 -04:00
Akash Mozumdar
94760703f9 catch errors 2019-06-13 19:29:30 -04:00
Akash Mozumdar
5d2c1339d5 forgot to remove that 2019-06-13 13:56:45 -04:00
TokcDK
50a505404b Russian localization typos fix 2019-06-13 13:27:15 -04:00
Akash Mozumdar
67a5f42c51 break up remove repetition extension 2019-06-13 13:06:34 -04:00
Akash Mozumdar
b5c319ee46 small fixes 2019-06-13 12:06:15 -04:00
Akash Mozumdar
140c9057ea account for null terminator in repetition filter 2019-06-13 04:08:40 -04:00
Akash Mozumdar
76804dd0aa reorganize translation 2019-06-13 04:01:29 -04:00
Akash Mozumdar
e7fff79f8f XX = 0x11 is officially the wildcard 2019-06-13 03:19:08 -04:00
Akash Mozumdar
ce88341cce improve bgi support 2019-06-12 02:58:47 -04:00
Akash Mozumdar
58c80c82ba
Merge pull request #149 from Artikash/remove-hooks
Remove hooks feature
2019-06-10 22:49:06 -04:00
Akash Mozumdar
fefa08c519 minor tweaks to remove hooks 2019-06-10 22:47:58 -04:00
Akash Mozumdar
516881d409 reorganize tests 2019-06-10 22:23:06 -04:00
Akash Mozumdar
64eee8f049 scrap that dumb idea 2019-06-10 15:06:43 -04:00
Akash Mozumdar
c5a6e16488 fix some cmake fuckups 2019-06-10 13:58:19 -04:00
Akash Mozumdar
c808e7594d (re)add remove hooks feature skeleton 2019-06-10 01:49:11 -04:00
Akash Mozumdar
ae0398a243
Merge pull request #146 from Artikash/add-test
Add testbed to quickly test hooking.
2019-06-09 08:30:26 -04:00
Akash Mozumdar
0f44a0c6f8 start injeccted 2019-06-09 08:10:44 -04:00
Akash Mozumdar
d56a4196b2 rename 2019-06-09 07:56:23 -04:00
Akash Mozumdar
d25a61e9e6 catch garbage pipe data 2019-06-09 07:33:26 -04:00
Akash Mozumdar
121606af14 dont remove null terminator in remove repetition 2019-06-09 07:31:45 -04:00
Akash Mozumdar
e453b571ba fix d3dx x64 hook 2019-06-09 06:51:34 -04:00
Akash Mozumdar
440474d2ee small fixes 2019-06-09 06:19:54 -04:00
Akash Mozumdar
907c43db4a added helpful info for hook searching in native and ppsspp memory 2019-06-09 00:50:26 -04:00
Akash Mozumdar
5e27de842b added string offsets to hook codes and searches, fixed stack alignment bug, fixed inaccurate documentation on x64 registers, fixed bug with search signature cache, imrpvoed hook search string detection 2019-06-09 00:48:30 -04:00
Akash Mozumdar
7964623ec8 update for vs 2019 2019-06-07 02:58:45 -04:00
Akash Mozumdar
d4d6c6c8b6 add tests 2019-06-07 02:24:28 -04:00
Akash Mozumdar
958ce10327 clean up cmake 2019-06-07 02:19:51 -04:00
Akash Mozumdar
b8bd602474 more user friendly hook searching and refactors 2019-06-06 23:53:37 -04:00
Akash Mozumdar
b18fe3ddd0 horrific memeory leak in rate limiter 2019-06-05 20:26:50 -04:00
Akash Mozumdar
e107eff849 change tutorial url 2019-06-05 16:58:45 -04:00
Akash Mozumdar
64bfb4596e better error handling for extensions 2019-06-04 23:14:46 -04:00
Akash Mozumdar
cf90539d09 optimize 2019-06-04 23:12:45 -04:00
Akash Mozumdar
d16db4d319 add repeated sentences filter (laxer than before) 2019-06-04 15:30:48 -04:00
Akash Mozumdar
e529046d7d after further testing, ThreadSafe<std::deque> seems to be the fastest and most reliable 2019-06-04 15:21:04 -04:00
Akash Mozumdar
94a8e2ce62 clarify cli 2019-06-03 19:39:22 -04:00
Akash Mozumdar
3a36dbca1d refactor regex filter, add guide, and make it a default 2019-06-03 19:29:37 -04:00
Akash Mozumdar
7cdbd93dcb cleanup 2019-06-03 18:54:20 -04:00
Akash Mozumdar
494fcd24c3 performance improvement. or maybe not idk my benchmarks make no sense but i think it's better 2019-06-02 23:05:01 -04:00
Akash Mozumdar
02f127e80c git is confusing 2019-06-02 21:44:22 -04:00
Akash Mozumdar
9668b01192 add find hooks feture + some cleanup 2019-06-02 21:35:47 -04:00
Akash Mozumdar
125d160ef6 add find hooks feture + some cleanup 2019-06-02 02:09:17 -04:00
Akash Mozumdar
8d763e4b3d Merge branch 'master' of https://github.com/Artikash/Textractor 2019-06-01 14:50:46 -04:00
Akash Mozumdar
15dad81627 Merge branch 'master' of https://github.com/Artikash/Textractor 2019-06-01 14:50:39 -04:00
Akash Mozumdar
9a1f123611 Merge branch 'master' of https://github.com/Artikash/Textractor 2019-06-01 13:59:56 -04:00
Akash Mozumdar
44558a6059 change to format strings 2019-06-01 13:59:37 -04:00
Akash Mozumdar
0c05b73b7c
Update README.md 2019-05-31 19:29:17 -04:00
Akash Mozumdar
e4431e2131
Merge pull request #141 from Hawxone/master
Added indonesian translation
2019-05-31 19:14:14 -04:00
Akash Mozumdar
078778bebd
Update text.cpp 2019-05-31 19:13:26 -04:00
Akash Mozumdar
2517393688
add indonesian to build script 2019-05-31 19:11:42 -04:00
Dimas Ariono Utomo
1af629b2d2
fixed missing instruction and CURRENT_FILTER 2019-06-01 05:03:46 +07:00
Akash Mozumdar
096a1b49a7 small fixes 2019-05-31 14:48:07 -04:00
Akash Mozumdar
6975145db9 OBOE fix 2019-05-31 14:29:56 -04:00
Akash Mozumdar
b7fe41f537
remove tab from string literal 2019-05-31 09:39:15 -04:00
Dimas Ariono Utomo
93e3e2f82f
Update README.md 2019-05-31 17:37:51 +07:00
Dimas Ariono Utomo
4d15ecf7c2
Update README_RU.md 2019-05-31 17:37:11 +07:00
Dimas Ariono Utomo
6264cd3577
Update README.md 2019-05-31 17:35:14 +07:00
Dimas Ariono Utomo
e3c70a721f
Update README_ID.md 2019-05-31 17:33:38 +07:00
Dimas Ariono Utomo
54e8cbcf82
wait 2019-05-31 17:07:37 +07:00
Dimas Ariono Utomo
bb7a36bcad
added indonesian language 2019-05-31 16:40:13 +07:00
Akash Mozumdar
23a6b116cd clearer readme 2019-05-27 14:16:29 -04:00
Akash Mozumdar
a38cbe2769 upgrade build scripts for x64 deployment 2019-05-27 14:13:49 -04:00
Akash Mozumdar
c7edfef9ad upgrade build pipeline for x64 deployment 2019-05-24 13:30:12 -04:00
Akash Mozumdar
aa3b9ee14f fix nulls not being reset 2019-05-11 08:25:04 -04:00
Akash Mozumdar
0a50a9b70d more reliable shino hook 2019-05-07 05:42:35 -04:00
Akash Mozumdar
fbe190f39c alternate v8/javascript hook 2019-05-02 19:24:16 -04:00
Akash Mozumdar
49e915c3b6 cap memory usage 2019-04-26 20:55:07 -04:00
Akash Mozumdar
2f7365acbd small fixes 2019-04-23 02:49:06 -04:00
Akash Mozumdar
a8100b4d8c prepare for 64 bit release 2019-04-22 10:02:59 -04:00
Akash Mozumdar
e8607e576d specify null length 2019-03-27 23:35:22 -04:00
Akash Mozumdar
c6b7c6ba22
Merge pull request #115 from TokcDK/master
Russian localization for GUI and Readme
2019-03-26 15:30:16 -04:00
TokcDK
1d4e1aabd5 . 2019-03-25 16:27:45 +05:00
TokcDK
184df2f781 Russian localization for Textractor (readme + GUI) 2019-03-25 16:18:24 +05:00
TokcDK
ce2b413f5b Added russian localization 2019-03-25 15:29:47 +05:00
Akash Mozumdar
be08c65799 update version 2019-03-24 23:01:09 -04:00
Akash Mozumdar
a40f814843 Compress-Archive uses wrong directory separator 2019-03-20 18:56:37 -04:00
Akash Mozumdar
e73dbed498 more accurate h code 2019-03-20 18:55:55 -04:00
Akash Mozumdar
f53950581a fix artemis hook 2019-03-19 04:42:04 -04:00
Akash Mozumdar
21fd3e1d59 rename 2019-03-13 11:54:19 -04:00
Akash Mozumdar
09552fe7c7 group common stuff 2019-03-13 11:44:21 -04:00
Akash Mozumdar
7e8dfc0332 fix race condition 2019-02-28 01:40:40 -05:00
Akash Mozumdar
0345181484 split text declarations 2019-02-27 11:33:17 -05:00
Akash Mozumdar
cf423fa525 small fixes 2019-02-26 20:29:13 -05:00
Akash Mozumdar
0cf2af47a6 Merge branch 'master' of https://github.com/Artikash/Textractor 2019-02-26 06:03:01 -05:00
Akash Mozumdar
259ab2cb16 update version 2019-02-26 06:01:26 -05:00
Akash Mozumdar
d26a384734
Update README.md 2019-02-26 04:04:40 -05:00
Akash Mozumdar
4c1cac7dbb small fixes 2019-02-25 01:06:35 -05:00
Akash Mozumdar
f62f90a068 store trie match and use if another isnt found 2019-02-25 01:06:16 -05:00
Akash Mozumdar
ec0b9c077c remove some false positives from repetition detection 2019-02-25 00:47:18 -05:00
Akash Mozumdar
9900c09483 use LTO to improve load time 2019-02-25 00:08:20 -05:00
Akash Mozumdar
7b82aa62aa add size locking option 2019-02-25 00:00:20 -05:00
Akash Mozumdar
08cd3959fa detect bleeding edge mono 2019-02-24 23:52:25 -05:00
Akash Mozumdar
121eddc290 command line help text 2019-02-21 13:18:37 -05:00
Akash Mozumdar
107544df70 minor fixes 2019-02-21 13:09:44 -05:00
Akash Mozumdar
f6a82984c2 allow empty replacements 2019-02-20 22:14:32 -05:00
Akash Mozumdar
40e62a13d6 upgrade repetition remover algorithm 2019-02-20 22:12:26 -05:00
Akash Mozumdar
241d5906e4 minor fixes 2019-02-20 21:54:25 -05:00
Akash Mozumdar
a64fa9b3dd reorganize includes 2019-02-18 23:12:12 -05:00
Akash Mozumdar
1f5edecbf6 attach to command line args 2019-02-18 22:39:04 -05:00
Akash Mozumdar
25d2811c0f better exception logging 2019-02-18 22:17:56 -05:00
Akash Mozumdar
29f616ae9d more efficient extension abi 2019-02-18 20:57:10 -05:00
Akash Mozumdar
8016ac178a resize 2019-02-18 01:27:41 -05:00
Akash Mozumdar
3c4a8920fe better crop 2019-02-18 01:25:29 -05:00
Akash Mozumdar
bcc384ff53 improve screenshot 2019-02-18 01:16:53 -05:00
Akash Mozumdar
aef011a713 new screenshot 2019-02-18 00:58:00 -05:00
Akash Mozumdar
146afdc926 using new api 2019-02-17 19:14:49 -05:00
Akash Mozumdar
e826801c87 more lua features 2019-02-17 18:52:09 -05:00
Akash Mozumdar
48ea8f531b static link lua 2019-02-16 22:53:19 -05:00
Akash Mozumdar
c9a7a606cb fix bugs with admin rights and pipe connection, plus some refactors 2019-02-16 22:51:10 -05:00
Akash Mozumdar
8e40c71563 refactor 2019-02-16 22:43:31 -05:00
Akash Mozumdar
ef8783d82d move minhook into libs folder 2019-02-16 03:17:16 -05:00
Akash Mozumdar
3c7b3d728c remove some other race conditions 2019-02-16 00:33:38 -05:00
Akash Mozumdar
c7ff5f637a use library rw lock 2019-02-16 00:25:19 -05:00
Akash Mozumdar
9ed3900512 dont remove all hooks when 0 in hcode 2019-02-16 00:24:36 -05:00
Akash Mozumdar
95a988f19b actually lock mutexe 2019-02-16 00:23:13 -05:00
Akash Mozumdar
3504f6cc4a
add donate button 2019-02-15 13:08:22 -05:00
Akash Mozumdar
913074e6af update readme 2019-02-13 21:33:57 -05:00
Akash Mozumdar
855f8ebfe5 fix unique_lock starvation and extension loading 2019-02-13 18:23:42 -05:00
Akash Mozumdar
4eefafeae9 fix clipboard monitor 2019-02-13 18:22:07 -05:00
Akash Mozumdar
a5bbf0bb62 update version 2019-02-13 17:47:41 -05:00
Akash Mozumdar
510acdeca3 The One Mono Hook To Rule Them All has arrived 2019-02-13 17:01:13 -05:00
Akash Mozumdar
f1ab7cd208 wow, it's like every time i have a chance to allow a buffer overrun, i do so 2019-02-13 17:00:44 -05:00
Akash Mozumdar
a4133ce243 fix game ordering 2019-02-13 16:22:49 -05:00
Akash Mozumdar
99d7d69b75 more mono hooks 2019-02-12 23:54:46 -05:00
Akash Mozumdar
551d5ba49d small refactor 2019-02-12 23:54:33 -05:00
Akash Mozumdar
e494ebc077 add lua extension 2019-02-12 19:54:15 -05:00
Akash Mozumdar
f21db0925c fix thread saving on x64 2019-02-12 04:14:08 -05:00
Akash Mozumdar
72b9a1f8ed huge refactor 2019-02-10 21:46:39 -05:00
Akash Mozumdar
6c47a27351 update text 2019-02-10 14:08:08 -05:00
Akash Mozumdar
4a7b29de72 refactor 2019-02-09 18:24:54 -05:00
Akash Mozumdar
ceeeced02f system43 pattern update 2019-02-09 17:47:42 -05:00
Akash Mozumdar
cf34160bbe update docs 2019-02-09 00:34:14 -05:00
Akash Mozumdar
a07e10344f performance optimizations 2019-02-09 00:30:38 -05:00
tinyAdapter
d6b39eb2c3 CLI: add code information when outputing
this can be necessary
2019-02-07 21:11:41 +08:00
Akash Mozumdar
5e53e63e07 perf optimization 2019-02-06 19:48:42 -05:00
Akash Mozumdar
ac86323010 whoops didnt mean to commit that 2019-02-04 15:54:41 -05:00
Akash Mozumdar
4b7452bef2 toggleable repetition filter 2019-02-04 15:53:13 -05:00
Akash Mozumdar
e6805a2be3 refactor, perf improvement 2019-02-04 15:18:47 -05:00
Akash Mozumdar
c78747c228 refactor 2019-02-04 15:02:06 -05:00
Akash Mozumdar
b9a4a94d61 dont message removing empty hook 2019-02-04 14:31:32 -05:00
Akash Mozumdar
1759b74fd3 fix siglus hook 2019-02-03 10:08:36 -05:00
Akash Mozumdar
a7a4a80913 remember saved thread and refactor 2019-02-03 08:49:58 -05:00
Akash Mozumdar
dcfbeafd44 more flexible deploy script 2019-02-02 16:54:29 -05:00
Akash Mozumdar
f74cd553c0 refactor 2019-02-02 16:54:13 -05:00
Akash Mozumdar
b7ec42ee4e process list in order of last used 2019-02-02 16:50:28 -05:00
Akash Mozumdar
67d88c48a7 lstrcpyn hook 2019-02-02 16:48:19 -05:00
Akash Mozumdar
4e8a8cce25 build speed improvement 2019-02-01 16:56:10 -05:00
Akash Mozumdar
d9e33b9be2 Merge branch 'master' of https://github.com/Artikash/Textractor 2019-02-01 11:01:51 -05:00
Akash Mozumdar
b7e8b68838 update readmes 2019-02-01 11:01:45 -05:00
tinyAdapter
3f1b2fcccb repair stdout flush issue (#85) 2019-02-01 09:18:07 -05:00
Akash Mozumdar
84bf8c659f fix code generation 2019-01-31 12:47:56 -05:00
Akash Mozumdar
c864425c36 update build 2019-01-31 12:26:11 -05:00
tinyAdapter
29a3bb0a6f Simplified Chinese translation & README (#83) 2019-01-31 11:59:21 -05:00
Akash Mozumdar
c89b05cf42 add cli, move exception handlers and code parsers into host 2019-01-31 11:41:43 -05:00
Akash Mozumdar
22d3e3f3a5 split lib folders 2019-01-30 23:54:57 -05:00
Akash Mozumdar
a05a4d41b1 update willplus hook 2019-01-30 15:31:35 -05:00
Akash Mozumdar
b61cef5386 more d3dx hooks 2019-01-30 15:14:14 -05:00
Akash Mozumdar
67c3e572d7 hook x64 directx (untested) 2019-01-30 15:02:23 -05:00
Akash Mozumdar
5aa4b982fd skip empty sentence 2019-01-30 14:51:08 -05:00
Akash Mozumdar
0d9976b0c4 fix single byte hooks 2019-01-28 07:25:58 -05:00
Akash Mozumdar
4e9df97991 update image 2019-01-28 07:05:29 -05:00
Akash Mozumdar
7469e9f115 note that adding extensions doesnt work as admin 2019-01-28 06:26:45 -05:00
Akash Mozumdar
4d68c45f24 more winapi hooks 2019-01-28 06:21:37 -05:00
Akash Mozumdar
e398fdf94e allow single char utf8 hooks 2019-01-27 09:14:45 -05:00
Akash Mozumdar
69f5522492 dont allow root modifiy 2019-01-27 08:49:08 -05:00
Akash Mozumdar
c596116a03 add oleaut hooks as misc 2019-01-27 03:39:33 -05:00
Akash Mozumdar
b873ecbc96 add d3dx hooks 2019-01-27 03:04:16 -05:00
Akash Mozumdar
7e4cc13bcf Add extra window as default extension, and reorganize build 2019-01-25 22:49:50 -05:00
Akash Mozumdar
f52104009e fix gdi+ hooks 2019-01-25 22:48:27 -05:00
Akash Mozumdar
a4e7359d83 update version 2019-01-24 09:07:24 -05:00
Akash Mozumdar
413c5b17cf more stable and user friendly replacer 2019-01-24 08:32:21 -05:00
Akash Mozumdar
c57e97bd83 add replacer extension 2019-01-23 16:11:14 -05:00
Akash Mozumdar
b9590a1c36 fix encoding 2019-01-23 14:48:09 -05:00
Akash Mozumdar
7fcf994a00 more reliable op phandle 2019-01-23 14:47:42 -05:00
Akash Mozumdar
632139dce2 perf improvement 2019-01-23 13:59:34 -05:00
Akash Mozumdar
ba83760157 fix newlines 2019-01-23 13:49:41 -05:00
Akash Mozumdar
ac6294f30e translation thread safety 2019-01-23 13:41:50 -05:00
Akash Mozumdar
af8a069243 update version 2019-01-22 15:40:03 -05:00
Akash Mozumdar
38d011271e fix buffer overrun 2019-01-22 15:18:28 -05:00
Akash Mozumdar
4e65d29978 copy on select 2019-01-22 14:57:13 -05:00
Akash Mozumdar
df3ba5dec8 readd thread linker extension 2019-01-22 14:35:34 -05:00
Akash Mozumdar
126b48c51e support for partial translation 2019-01-22 13:31:47 -05:00
Akash Mozumdar
cba353438d emulate path 2019-01-22 11:23:35 -05:00
Akash Mozumdar
c0e8a638ba update version 2019-01-21 18:44:00 -05:00
scese250
4e85b8b479 Readme Spanish (#77)
Translate readme to spanish and link it from homepage
2019-01-21 15:46:57 -05:00
Akash Mozumdar
cb2415c460
Merge pull request #76 from scese250/patch-1
Spanish translation
2019-01-21 15:40:23 -05:00
Akash Mozumdar
027d5962f8 note for future mono hook 2019-01-21 14:23:49 -05:00
Akash Mozumdar
7e080b1b44 compare using wchar null terminator 2019-01-21 14:23:26 -05:00
Akash Mozumdar
d1917ed9a6 replace \ properly 2019-01-21 14:22:47 -05:00
scese250
3101147b6d
Spanish translation
Thanks for this tool! Since I don't have Visual Studio on my computer, I can't see how translation looks, if there are something wrong tell me.
2019-01-21 13:23:39 -04:00
Akash Mozumdar
c7df716df8 update version 2019-01-20 15:38:13 -05:00
Akash Mozumdar
3cb8016b2c show when h code has no agth equivalent 2019-01-20 15:09:38 -05:00
Akash Mozumdar
cea9edab55 improved mono hook 2019-01-20 15:09:14 -05:00
Akash Mozumdar
332e5aecaf refactor 2019-01-20 09:52:35 -05:00
Akash Mozumdar
7c3ca92db3 update deploy script 2019-01-20 09:47:58 -05:00
Akash Mozumdar
dad7f23532 add dies irae hook 2019-01-20 09:47:49 -05:00
Akash Mozumdar
a7a19ef017 add srpg hook 2019-01-19 00:18:07 -05:00
Akash Mozumdar
2e5ee9e547 remove useless code 2019-01-18 23:54:53 -05:00
Akash Mozumdar
25eb1f5956 Merge branch 'master' of https://github.com/Artikash/Textractor 2019-01-18 12:56:17 -05:00
Akash Mozumdar
01421fa88b fix? artemis hook 2019-01-18 12:52:00 -05:00
Akash Mozumdar
50472a98ea fix buffer overrun 2019-01-18 12:51:45 -05:00
Akash Mozumdar
64e3d901c8
Update README.md 2019-01-15 04:43:26 -05:00
Akash Mozumdar
650e2077ca final tiny adjustments for official release 2019-01-15 04:32:00 -05:00
Akash Mozumdar
69435f8a9f ignore resource editor files 2019-01-15 04:29:17 -05:00
Akash Mozumdar
a84ea357ec give up on emulating non jp locale and launching in correct directory 2019-01-13 03:40:00 -05:00
Akash Mozumdar
6a357b5e32 update dev/contributor list 2019-01-11 17:15:10 -05:00
Akash Mozumdar
75454b3fa5 save attached games and allow launching from textractor using locale emulator 2019-01-11 17:14:49 -05:00
Akash Mozumdar
8e80543e2e reorganize folders/build system 2019-01-11 16:20:57 -05:00
Akash Mozumdar
d8429851cb better hash fn 2019-01-11 16:15:42 -05:00
Akash Mozumdar
0f7fe46980 dont copy infinitely when clipboard is selected with clipboard copy extension 2019-01-10 21:55:11 -05:00
Akash Mozumdar
54afbb24a5 using raw string literals 2019-01-10 21:51:20 -05:00
Akash Mozumdar
5903bbe2e4 fix freeze when connecting process, crash when process name cant be found, and other refactors 2019-01-10 21:47:16 -05:00
Akash Mozumdar
cdfbd77d21 simplify (and maybe break) wolf2 hook and fix display error 2019-01-10 02:00:39 -05:00
Akash Mozumdar
24d9244ad4 DRY build script 2019-01-10 01:59:27 -05:00
Akash Mozumdar
0afdafb3d1 bunch of refactoring and cleanup 2019-01-09 22:35:01 -05:00
Akash Mozumdar
84e9beea63 compile/link on x64 2019-01-08 21:52:28 -05:00
Akash Mozumdar
f1e7b4dc70 fix race condition (i think) 2019-01-07 22:17:46 -05:00
Akash Mozumdar
a9249111c0 fix some geometry issues 2019-01-06 00:07:20 -05:00
Akash Mozumdar
6247a4ed35 let extra window split on new lines 2019-01-05 03:48:25 -05:00
Akash Mozumdar
8bb7a1ba5d clarify h/r/s codes 2019-01-05 03:48:08 -05:00
Akash Mozumdar
103c805cfe always use codepage when searching 2019-01-05 03:47:32 -05:00
Akash Mozumdar
49d9d7a0c4 display error if text couldnt be found 2019-01-05 03:46:13 -05:00
Akash Mozumdar
f5d8d43149 implement searching for read codes 2019-01-03 17:52:16 -05:00
Akash Mozumdar
b06dc8ff82 fix scenario player hook 2019-01-02 03:43:14 -05:00
Akash Mozumdar
24491e1bc3 update artemis2 hook 2019-01-01 23:52:26 -05:00
Akash Mozumdar
a53af3d0fd lots of refactors and fixes on extra window 2019-01-01 22:40:29 -05:00
Akash Mozumdar
479c73d029 move more text into text.h, refactor and fix bug in texthook 2019-01-01 18:44:27 -05:00
Akash Mozumdar
3dd7be65ff optimize dispatchsentence 2019-01-01 17:50:22 -05:00
Akash Mozumdar
f080656e60 improvements in thread safety and repetition detection in textthread 2019-01-01 15:15:09 -05:00
Akash Mozumdar
8880d27dc5 more consistent use of qsettings 2018-12-31 15:06:47 -05:00
Akash Mozumdar
9216548848 small cleanup 2018-12-30 22:11:23 -05:00
Akash Mozumdar
b87b4c0b41 initial implementation of new extra window feeatures 2018-12-30 22:10:56 -05:00
Akash Mozumdar
e8b378da75 fix whitespace 2018-12-28 12:14:56 -05:00
Akash Mozumdar
54c1b508d5 upgrade repetition remover and add tests 2018-12-28 11:57:06 -05:00
Akash Mozumdar
bdc083a62a more sane way to capture clipboard, also move extension dispatch off main thread 2018-12-28 11:13:02 -05:00
Akash Mozumdar
96e9b66e9f remove error dialog on win7 2018-12-27 18:52:59 -05:00
Akash Mozumdar
8a88ad3c95 more reliable pal hook 2018-12-27 15:07:20 -05:00
Akash Mozumdar
bf4eb4df8b tiny fixes 2018-12-27 00:18:05 -05:00
Akash Mozumdar
6c0f4be45b easier testing 2018-12-26 23:56:42 -05:00
Akash Mozumdar
a5057d8671 more reliable horkeye hook 2018-12-26 23:55:05 -05:00
Akash Mozumdar
807e081bee little cleanup 2018-12-26 13:08:12 -05:00
Akash Mozumdar
4ce7425034 fix error spam 2018-12-26 13:07:59 -05:00
Akash Mozumdar
978bf2a6b8 more reliable airnovel hook 2018-12-25 18:27:12 -05:00
Akash Mozumdar
b191e511eb update systemc hook 2018-12-23 09:39:46 -05:00
Akash Mozumdar
1817a3ac53 small refactor 2018-12-22 16:17:08 -05:00
Akash Mozumdar
19e7df5631 flatten build directories 2018-12-22 16:11:41 -05:00
Akash Mozumdar
1c391e3a4b small refactor. rename and extract exception handling 2018-12-22 13:05:01 -05:00
Akash Mozumdar
507e0bc1e6 add extra window to view text 2018-12-21 15:11:12 -05:00
Akash Mozumdar
7015ef0c53 move more text into include/text.h and make a better ux for extenwindow and misc refactors 2018-12-21 14:18:43 -05:00
Akash Mozumdar
afea96e9d5 more efficient thread safe wrapper 2018-12-21 14:11:40 -05:00
Akash Mozumdar
4ea5c9cb32 add x64 engine 2018-12-21 10:10:51 -05:00
Akash Mozumdar
e0507b85d3 small refactors 2018-12-21 09:34:01 -05:00
Akash Mozumdar
7cd464661b finally, hooks work in x64! 2018-12-20 11:46:11 -05:00
Akash Mozumdar
7ec7253ec3 no need for wchar there 2018-12-20 11:02:22 -05:00
Akash Mozumdar
4e4cf00884 fix error log 2018-12-20 11:02:03 -05:00
Akash Mozumdar
6bef925920 refactor insertion_address (and i think fix a bug with deref offset in r codes) 2018-12-20 02:48:21 -05:00
Akash Mozumdar
c22a30400a translation request 2018-12-19 16:07:46 -05:00
Akash Mozumdar
e0b0fa10ad add message function 2018-12-18 16:56:04 -05:00
Akash Mozumdar
f184654883 fix file ends 2018-12-18 16:55:07 -05:00
Akash Mozumdar
240f5f235b extract remaining text 2018-12-18 16:32:28 -05:00
Akash Mozumdar
5523644ef9 google translate optimized 2018-12-18 14:21:27 -05:00
Akash Mozumdar
3c67f5a15d capturing this by ref is dumb 2018-12-18 12:55:11 -05:00
Akash Mozumdar
b82d8d5523 forgot how lambdas work 2018-12-18 12:15:18 -05:00
Akash Mozumdar
464c001773 eh who needs moc anyway 2018-12-18 12:14:54 -05:00
Akash Mozumdar
29822c89c8 more stuff in japanese readme 2018-12-18 00:33:47 -05:00
Akash Mozumdar
4079720307 credit turkish translation 2018-12-18 00:13:40 -05:00
Akash Mozumdar
465dde33d5 add turkish translation 2018-12-17 23:30:17 -05:00
Akash Mozumdar
c747e68344 fewer heap allocations 2018-12-17 21:03:42 -05:00
Akash Mozumdar
e7e81f09bf reorganize extensions and remove thread linker (doesnt work very well) 2018-12-17 20:48:14 -05:00
Akash Mozumdar
86b2014a12 fix crash when saving hooks. refactor other things to compile time 2018-12-14 23:14:30 -05:00
Akash Mozumdar
606a36968b update adobe air to v32 2018-12-14 22:27:59 -05:00
Akash Mozumdar
97f3f51758 more consistent/organized text 2018-12-14 22:26:59 -05:00
Akash Mozumdar
dfb45a3699 refactor 2018-12-13 22:44:55 -05:00
Akash Mozumdar
a3ac850bf4 refactor. remove unneeded abstraction, alias verbose conversions 2018-12-13 08:37:37 -05:00
Akash Mozumdar
c92a67dbe3 automatically check for new updates 2018-12-13 07:54:25 -05:00
Akash Mozumdar
651cd486eb tiny refactors 2018-12-13 03:17:28 -05:00
Akash Mozumdar
36ad3aa05d update airnovel 2018-12-08 23:23:23 -05:00
Akash Mozumdar
5cf12dfc27 better translation error msg 2018-12-06 04:13:57 -05:00
Akash Mozumdar
be185233d1 fix google translate (new api) 2018-12-06 04:10:21 -05:00
Akash Mozumdar
0a3c397b4e fix google translate (new api) 2018-12-06 03:56:49 -05:00
Akash Mozumdar
a0b8b78727 make TextHook consistent with code in host 2018-12-06 01:52:41 -05:00
Akash Mozumdar
5f87dae972 no need to avoid copy 2018-12-06 01:51:27 -05:00
Akash Mozumdar
4efaa9f34f nvm it is safe to delete nullptr 2018-12-03 18:44:08 -05:00
Akash Mozumdar
aae330ebef Close process cleanly 2018-12-03 18:31:00 -05:00
Akash Mozumdar
9068228427 improve regex filter UX 2018-12-03 18:29:54 -05:00
Akash Mozumdar
74383ee0d8 refactor HANDLE out param 2018-12-03 18:29:30 -05:00
Akash Mozumdar
f409ee78ce small fixes 2018-12-02 15:55:02 -05:00
Akash Mozumdar
dca006b28c use thread pooling and remove text thread cap 2018-12-02 15:30:35 -05:00
Akash Mozumdar
de109d0840 tiny fixes 2018-12-02 02:57:03 -05:00
Akash Mozumdar
cc1d2b0414 make headers consistent 2018-12-01 19:12:45 -05:00
Akash Mozumdar
5ef1ff6352 using raii for processrecords and qfileinfo for files and other refactors 2018-12-01 16:00:15 -05:00
Akash Mozumdar
0f903940bc fix logging 2018-12-01 15:54:30 -05:00
Akash Mozumdar
385af259a9 dont use terminatethread and other refactors 2018-12-01 15:54:07 -05:00
Akash Mozumdar
2f544afaa5 using raii in vnrhook 2018-12-01 15:52:52 -05:00
Akash Mozumdar
bc644bbe4e better threadsafeptr 2018-12-01 15:52:12 -05:00
Akash Mozumdar
2f43249aa8 regex filter doesnt apply to console 2018-12-01 15:23:48 -05:00
Akash Mozumdar
90d32c011d fix scenarioplayer 2018-11-29 14:12:59 -05:00
Akash Mozumdar
7693f1373d more consistent text 2018-11-28 01:09:11 -05:00
Akash Mozumdar
0ddd4a3461 remove unreliable error log 2018-11-27 21:29:09 -05:00
Akash Mozumdar
7310f9349b remove unhook button 2018-11-27 16:57:47 -05:00
Akash Mozumdar
026912ca9c refactor repetition remover 2018-11-27 16:54:19 -05:00
Akash Mozumdar
bb86bde8a5 fix buffer overrun 2018-11-27 15:56:28 -05:00
Akash Mozumdar
943e53e9a2 oops wrong order. anyway add some helper types 2018-11-27 15:55:19 -05:00
Akash Mozumdar
966620429e structured bindings are awesome! 2018-11-27 15:54:18 -05:00
Akash Mozumdar
efa8d26ada holy shit you can overload operator-> to do WHAT?? 2018-11-27 15:54:04 -05:00
Akash Mozumdar
24e31247af fix current directory being wrong if launched in weird ways 2018-11-25 21:40:19 -05:00
Akash Mozumdar
f48355c096 more reliable error messaging 2018-11-25 19:06:38 -05:00
Akash Mozumdar
7a2ceec29e Revert "more reliable error logging"
This reverts commit 49c4af8c2c5b6f23a85dc99328044d1686b96ea2.
2018-11-25 16:45:43 -05:00
Akash Mozumdar
c50e2992bf make StringToWideString pure 2018-11-25 16:23:41 -05:00
Akash Mozumdar
49c4af8c2c more reliable error logging 2018-11-25 15:05:41 -05:00
Akash Mozumdar
c105f6700f extract repetition filter 2018-11-22 16:02:45 -05:00
Akash Mozumdar
9f85edf704 rename host files 2018-11-22 15:54:11 -05:00
Akash Mozumdar
b80f795143 extract utility functions. specify deleted constructors. other misc fixes 2018-11-22 15:53:32 -05:00
Akash Mozumdar
902ded684d small fixes 2018-11-19 08:17:00 -05:00
Akash Mozumdar
95ca747912 readd wolfrpg2 2018-11-19 08:15:38 -05:00
Akash Mozumdar
523008d7e0 ok, finally have a good repetition filter 2018-11-16 08:34:15 -05:00
Akash Mozumdar
59869dc45a optimize repetition removal at cost of some correctness (can be dealt with by extensions) 2018-11-15 00:29:21 -05:00
Akash Mozumdar
0492cb9549 refactor 2018-11-15 00:27:48 -05:00
Akash Mozumdar
d9a3d5cefd improve repetition remover perf 2018-11-15 00:06:49 -05:00
Akash Mozumdar
1d17902c64 add error handling in clipboard copier 2018-11-15 00:06:10 -05:00
Akash Mozumdar
c39356473d in case unicode is needed someday 2018-11-11 00:40:58 -05:00
Akash Mozumdar
6866e6bfe9 extract more strings 2018-11-11 00:38:52 -05:00
Akash Mozumdar
d0f48a67a4 refactor. less global variables 2018-11-10 23:29:12 -05:00
Akash Mozumdar
f351148b3d add settings dialog box 2018-11-10 07:16:08 -05:00
Akash Mozumdar
97d0528550 DRY 2018-11-10 01:17:02 -05:00
Akash Mozumdar
303140dfbf update cmvs hook 2018-11-09 05:15:45 -05:00
Akash Mozumdar
e489c38990 fix several perf issues in textthread 2018-11-09 04:24:33 -05:00
Akash Mozumdar
22bb1420c1 update tkk regularly 2018-11-08 05:51:20 -05:00
Akash Mozumdar
ddc8313d37 clean up repetition remover. dont need to remove repeated sentences since that detection was added in textthread 2018-11-07 10:43:54 -05:00
Akash Mozumdar
219804cecc update version 2018-11-05 17:08:44 -05:00
Akash Mozumdar
1edc6ab1a7 update deployment 2018-11-05 16:50:17 -05:00
Akash Mozumdar
3b5ad6edfe organize includes 2018-11-04 21:19:00 -05:00
Akash Mozumdar
0c7103964d rename 2018-11-04 20:48:46 -05:00
Akash Mozumdar
3db253c790 refactor hooking code and add some more logging 2018-11-04 20:48:27 -05:00
Akash Mozumdar
46ffaf017c refactor error handling 2018-11-04 17:12:25 -05:00
Akash Mozumdar
a48815e9f4 add thread linker extension 2018-11-04 16:37:23 -05:00
Akash Mozumdar
3cb65dba63 dont need those names for 30 line program 2018-11-04 16:31:39 -05:00
Akash Mozumdar
6b2dcaa957 check and avoid crashes 2018-11-04 04:51:14 -05:00
Akash Mozumdar
43a0bac59f remove whats this button on dialogs 2018-11-04 04:31:49 -05:00
Akash Mozumdar
ff44cc5f0c
Merge pull request #54 from Artikash/extract_strings
Extract strings
2018-11-04 04:16:04 -05:00
Akash Mozumdar
b218e241da extract more strings 2018-11-04 04:00:14 -05:00
Akash Mozumdar
62541968aa extract display text 2018-11-04 02:13:51 -05:00
Akash Mozumdar
5468e44929 small refactors 2018-11-04 01:34:49 -05:00
Akash Mozumdar
e598ae731e
Merge pull request #53 from Artikash/reorganize_extensions
Reorganize extensions
2018-11-04 01:43:00 -04:00
Akash Mozumdar
49dc067492 update readme for new project architecture 2018-11-04 01:41:39 -04:00
Akash Mozumdar
d88900aa46 make dialog smaller 2018-11-04 01:20:38 -04:00
Akash Mozumdar
1d90787564 casting does not work like i thought it did 2018-11-04 01:15:34 -04:00
Akash Mozumdar
66d02a7bf0 add regex filter 2018-11-03 23:58:52 -04:00
Akash Mozumdar
1ca9b3c8e6 readd google translate 2018-11-03 23:29:26 -04:00
Akash Mozumdar
83e8c2ecde reorganize 2018-11-03 23:26:27 -04:00
Akash Mozumdar
0160578c2d rename 2018-11-03 21:41:38 -04:00
Akash Mozumdar
68cb554102 Merge branch 'master' of https://github.com/Artikash/Textractor 2018-11-03 04:59:27 -04:00
Akash Mozumdar
d42dcc1705 fix buffer overrun 2018-11-03 04:59:18 -04:00
Akash Mozumdar
b74ca7e20b fix buffer overrun 2018-11-03 04:53:51 -04:00
Akash Mozumdar
a99131fa23 add default codepage option 2018-11-01 21:59:13 -04:00
Akash Mozumdar
71f7e9bf29 update version 2018-11-01 21:32:18 -04:00
Akash Mozumdar
5167ec3a6b fix bing translate for wine (how did that ever work?) 2018-11-01 21:32:13 -04:00
Akash Mozumdar
534ca4ec35 add clipboard text thread 2018-11-01 19:51:23 -04:00
Akash Mozumdar
c1198aeefd
Merge pull request #48 from Artikash/extension-dialog
Extension dialog
2018-11-01 19:04:28 -04:00
Akash Mozumdar
7696d45475 ui touchup 2018-11-01 18:54:28 -04:00
Akash Mozumdar
5caf42a6b1 kill everything when mainwindow is closed 2018-11-01 18:47:46 -04:00
Akash Mozumdar
ee0a8c4887 add drag'n'drop for extensions 2018-11-01 18:47:19 -04:00
Akash Mozumdar
4b82d545bf update ui to match new extension window design 2018-11-01 17:02:52 -04:00
Akash Mozumdar
942cdd94ba refactor. less reliance on edge cases 2018-11-01 15:03:30 -04:00
Akash Mozumdar
6ec8e7c19e make extensions thread safe 2018-11-01 14:46:37 -04:00
Akash Mozumdar
ce225fd900 big refactor. move extension internals into unnamed namespace. use mainwindow instead of dialog for extension window. and other renames/bugfixes 2018-11-01 14:07:42 -04:00
Akash Mozumdar
e48adc14bb add extension dialog 2018-11-01 10:38:14 -04:00
Akash Mozumdar
aa2d71a078 clean up some definitions 2018-10-31 20:09:29 -04:00
Akash Mozumdar
c877d9cd31 use raii for process records and mutexes. remove a lot of manual resource management 2018-10-31 12:04:32 -04:00
Akash Mozumdar
1915008d00 using shared_ptr to improve thread safety 2018-10-31 01:20:44 -04:00
Akash Mozumdar
8e45b35ebe refactor 2018-10-30 21:21:21 -04:00
Akash Mozumdar
69e01dab7c allow different codepages 2018-10-30 20:50:50 -04:00
Akash Mozumdar
dddbc00694 small refactors 2018-10-28 02:35:19 -04:00
Akash Mozumdar
6bc8143980 fix bing translate not working if added later 2018-10-28 02:28:08 -04:00
Akash Mozumdar
3a3bdfd48e refactor extensions some and let user rearrange them 2018-10-28 02:27:24 -04:00
Akash Mozumdar
1e2f6e14ec update readme. removing 64 bit from downloads 2018-10-22 14:30:32 -04:00
Akash Mozumdar
580ab0a75c update readmes 2018-10-21 16:31:19 -04:00
Akash Mozumdar
26dbab020d update version 2018-10-20 15:22:12 -04:00
Akash Mozumdar
e921062684 fix marine heart hook 2018-10-20 15:01:43 -04:00
Akash Mozumdar
0ea99ed732 readd mono hook 2018-10-20 15:01:15 -04:00
Akash Mozumdar
60be482634 ignore qt creator 2018-10-20 14:52:34 -04:00
Akash Mozumdar
74f2bc1c4d build system improvements 2018-10-19 15:55:48 -04:00
Akash Mozumdar
16666ac699 add some tests 2018-10-18 23:42:53 -04:00
Akash Mozumdar
8f60060e37 extract http request and refactor == L""/.size() == 0 2018-10-18 22:52:27 -04:00
Akash Mozumdar
d71fe0f147 auto detect language from bing 2018-10-18 10:13:33 -04:00
Akash Mozumdar
3fc5cb17bd update version 2018-10-17 17:38:05 -04:00
Akash Mozumdar
5e6d67691c google seems to be locking down their ap. imove to bing translate 2018-10-17 17:36:56 -04:00
Akash Mozumdar
104f9a7b8c fix extension thread safety 2018-10-17 01:08:31 -04:00
Akash Mozumdar
3ad311293f add cyclic repetition detection in textthread 2018-10-17 00:42:51 -04:00
Akash Mozumdar
abba2d77e3 improve error msg reliability 2018-10-15 06:46:02 -04:00
Akash Mozumdar
4adf820d58 horkeye pattern update 2018-10-14 16:07:09 -04:00
Akash Mozumdar
d307b7af2e searchmemory returns all matches now 2018-10-14 10:29:23 -04:00
Akash Mozumdar
273411d22e whoops 2018-10-14 10:28:40 -04:00
Akash Mozumdar
e79bbe4c03 move extension instead of deleting 2018-10-14 10:28:22 -04:00
Akash Mozumdar
af34731880 update deploy script 2018-10-11 15:31:42 -04:00
Akash Mozumdar
6d35e7ea67 update version 2018-10-11 15:06:44 -04:00
Akash Mozumdar
199844987b move onnewsentence into own file 2018-10-11 14:29:11 -04:00
Akash Mozumdar
f7e3bbeb02 add function based offset. rewrite hookparam processing while we're at it 2018-10-11 12:58:30 -04:00
Akash Mozumdar
3b9ca65e39 refactor 2018-10-10 08:16:14 -04:00
Akash Mozumdar
effb005376 add extension class and refactor extensions using it 2018-10-10 07:03:15 -04:00
Akash Mozumdar
ec1bd5622e no need for filter 2018-10-10 06:17:23 -04:00
Akash Mozumdar
7304727c17 better comment 2018-10-10 06:16:47 -04:00
Akash Mozumdar
59effb32dd refactor 2018-10-09 17:44:14 -04:00
Akash Mozumdar
1325d41e35 leave room for more extensions 2018-10-09 17:43:52 -04:00
Akash Mozumdar
39a0161989 improve extension abi (dont rely on exceptions across dlls) 2018-10-09 17:43:33 -04:00
Akash Mozumdar
586e22e4a5 read filter from ini 2018-10-09 05:45:49 -04:00
Akash Mozumdar
69511ddeda fix error message on empty throws 2018-10-09 04:31:54 -04:00
Akash Mozumdar
16e17526c7 improve exception handler 2018-10-09 04:02:33 -04:00
Akash Mozumdar
59be83eee8 that was embarassing 2018-10-09 02:22:59 -04:00
Akash Mozumdar
967f75a5ec rename 2018-10-09 02:09:52 -04:00
Akash Mozumdar
0166df7209 regex filter added 2018-10-09 01:46:11 -04:00
Akash Mozumdar
d07dd5c641 output debug info 2018-10-09 00:35:47 -04:00
Akash Mozumdar
5a464fc083 refactor 2018-10-08 00:26:43 -04:00
Akash Mozumdar
9929c3fac1 use unique lock for replacing extensions and fix extensions with _ in them 2018-10-07 23:32:31 -04:00
Akash Mozumdar
a21ee5bba4 use win32 to allocate memory (more stable) 2018-10-07 11:02:00 -04:00
Akash Mozumdar
e126e4c4f3 use size of hashtable instead of total threads created for thread cap 2018-10-04 23:31:26 -04:00
Akash Mozumdar
0b7db9e5ca add prefilter 2018-10-04 23:23:57 -04:00
Akash Mozumdar
5f74684b46 revert that idea, laggy as hell and unstable-ish. hard cap thread count. still leave conversion in addtext 2018-10-04 22:10:27 -04:00
Akash Mozumdar
68bafcdda7 whoops 2018-10-04 20:00:35 -04:00
Akash Mozumdar
effe03fd96 rearrange textthread logic. dont have threads where not needed. 2018-10-04 19:52:16 -04:00
Akash Mozumdar
2947c5a497 better translation 2018-10-02 16:15:13 -04:00
Akash Mozumdar
98e27bec60 Merge branch 'master' of https://github.com/Artikash/Textractor 2018-10-02 14:15:11 -04:00
Akash Mozumdar
257e617eb6 refactor to use shared_lock 2018-10-02 14:10:25 -04:00
Akash Mozumdar
750638e52d
Add japanese readme (#35)
* Create README_JP.md

* add readme jp link
2018-10-02 13:35:44 -04:00
Akash Mozumdar
f4c068fa29 update version 2018-09-29 20:24:57 -04:00
Akash Mozumdar
e244799a72 recolor icon 2018-09-29 17:44:57 -04:00
Akash Mozumdar
4f38287bea update readme 2018-09-29 17:42:44 -04:00
Akash Mozumdar
d506b3edd9 new icon 2018-09-29 16:31:17 -04:00
Akash Mozumdar
8a3a494a14 update readme 2018-09-29 16:07:08 -04:00
Akash Mozumdar
23736478c0 rename 2018-09-29 16:05:08 -04:00
Akash Mozumdar
baa7923be2 fix oboe 2018-09-29 13:10:07 -04:00
Akash Mozumdar
8cd4ea4a29 upgrade google translate reliability 2018-09-29 05:17:35 -04:00
Akash Mozumdar
923bb15f3d updated willplus hook 2018-09-29 03:03:32 -04:00
Akash Mozumdar
d2c9e5a6f5 let user change max buffer size 2018-09-23 22:29:33 -04:00
Akash Mozumdar
e31c9563ef refactor error handler 2018-09-23 19:36:00 -04:00
Akash Mozumdar
6b1bb33b66 update readme 2018-09-23 18:34:53 -04:00
Akash Mozumdar
1dfaa759b9 fix crash 2018-09-23 01:36:54 -04:00
Akash Mozumdar
3df68a6c2c small bugfixes 2018-09-23 01:08:33 -04:00
Akash Mozumdar
524a0f362d update version 2018-09-22 17:13:53 -04:00
Akash Mozumdar
cd3cb280d5 move to hex for processId display and display text handles 2018-09-22 17:13:06 -04:00
Akash Mozumdar
310d12ea14 get rid of some warnings 2018-09-22 15:45:54 -04:00
Akash Mozumdar
22c4f10c82 refactor extensions 2018-09-22 15:08:31 -04:00
Akash Mozumdar
665c5b3eea bugfixes 2018-09-22 03:17:52 -04:00
Akash Mozumdar
38ea5dba36 refactor and use .ini 2018-09-21 22:25:37 -04:00
Akash Mozumdar
e5dcd33680 bugfix 2018-09-21 22:20:53 -04:00
Akash Mozumdar
4dc7b236c9 refactor 2018-09-21 21:27:59 -04:00
Akash Mozumdar
7a059cc2e0 upgrade v8 hook 2018-09-21 15:01:38 -04:00
Akash Mozumdar
a2cc3bab28 bugfix 2018-09-20 23:04:11 -04:00
Akash Mozumdar
c5d5976b47 refactor 2018-09-20 22:42:15 -04:00
Akash Mozumdar
4bc0c834d7 refactor and give extensions 64 bits 2018-09-20 22:32:47 -04:00
Akash Mozumdar
e2f83d47b9 god, "unsigned __int64" was a mouthful 2018-09-20 21:59:07 -04:00
Akash Mozumdar
28172e2cea improve v8 hook 2018-09-20 21:36:15 -04:00
Akash Mozumdar
aa4e289cc8 bugfix google translate 2018-09-20 21:27:13 -04:00
Akash Mozumdar
a36c9b4f52 best hook for v8 i think exists 2018-09-20 21:17:51 -04:00
Akash Mozumdar
bb48b1e64b reorganize and add v8 skeleton 2018-09-17 21:40:54 -04:00
Akash Mozumdar
6ae86a5988 refactor misc pchooks 2018-09-17 19:44:29 -04:00
Akash Mozumdar
50c488f109 remove unneeded code 2018-09-17 19:26:44 -04:00
Akash Mozumdar
878e4b443f update version 2018-09-13 15:57:52 -04:00
Akash Mozumdar
6d59ad763c process id as hex 2018-09-13 11:59:15 -04:00
Akash Mozumdar
329d56c969 improve repetition detection 2018-09-13 02:50:03 -04:00
Akash Mozumdar
20956f6412 update hook for v8 runtime/javascript/tyranobuilder 2018-09-12 23:50:02 -04:00
Akash Mozumdar
6100134646 reorganize 2018-09-11 14:03:06 -04:00
Akash Mozumdar
23b4cffbb3 remove unneeded logging 2018-09-11 13:41:24 -04:00
Akash Mozumdar
0fe18a43ac bugfix searchmemory 2018-09-11 13:40:49 -04:00
Akash Mozumdar
2ad278255d bugfixes, and remove ntdll for good 2018-09-11 13:23:10 -04:00
Akash Mozumdar
a455869837 attach all processes in given name 2018-09-09 22:37:48 -04:00
Akash Mozumdar
7d49166dc4 update read code description 2018-09-05 10:52:50 -04:00
Akash Mozumdar
64cbe4e5d6 Merge branch 'master' of https://github.com/Artikash/NextHooker 2018-09-05 10:34:48 -04:00
Akash Mozumdar
8e0f5697f8 extract shinario: might be useful for other font caching 2018-09-05 10:23:58 -04:00
Akash Mozumdar
bd63cf3994
Update README.md 2018-09-04 16:17:08 -04:00
Akash Mozumdar
29586a4f41
Update README.md 2018-09-04 16:15:04 -04:00
Akash Mozumdar
baebb238ad insert conversion hooks by default 2018-09-03 18:43:43 -04:00
Akash Mozumdar
79b2782dab bugfix getmemoryrange 2018-09-03 18:42:47 -04:00
Akash Mozumdar
34082f31ed new shinario hook 2018-09-03 18:42:28 -04:00
Akash Mozumdar
72c37c9f9e generate derefed read code 2018-09-03 18:37:53 -04:00
Akash Mozumdar
a59942fde2 fix oboe in repeat remover 2018-09-03 18:37:32 -04:00
Akash Mozumdar
c2c96746be Merge branch 'master' of https://github.com/Artikash/NextHooker 2018-09-02 22:34:43 -04:00
Akash Mozumdar
5b31e9fed9 update version 2018-09-02 22:34:34 -04:00
Akash Mozumdar
abfa150513
Update README.md 2018-09-02 19:45:18 -04:00
Akash Mozumdar
0c4bb85f50 bugfix read code/rio hook 2018-09-02 18:28:37 -04:00
Akash Mozumdar
6ca7eb3f54 update version 2018-09-02 14:19:58 -04:00
Akash Mozumdar
a6151f4b24 add deployment script 2018-09-02 13:30:29 -04:00
Akash Mozumdar
0e9d9210a1 move minhook 2018-09-02 12:48:43 -04:00
Akash Mozumdar
c0b294de0f bugfix 2018-09-02 12:47:48 -04:00
Akash Mozumdar
ee14e73541 update version 2018-09-01 16:17:55 -04:00
Akash Mozumdar
030cdd6a03 bugfix 2018-09-01 15:49:16 -04:00
Akash Mozumdar
7c2bddcc98 improve error msg 2018-09-01 15:31:18 -04:00
Akash Mozumdar
072840e307 add exception handler 2018-09-01 14:57:25 -04:00
Akash Mozumdar
88d67c5378 refactor 2018-09-01 14:11:48 -04:00
Akash Mozumdar
50fc7e24c6 refactor 2018-09-01 13:56:45 -04:00
Akash Mozumdar
ee0a953848 typo 2018-09-01 13:25:05 -04:00
Akash Mozumdar
d31748c105 refactor all extensions. move repetition detection to extension 2018-09-01 04:24:22 -04:00
Akash Mozumdar
ca749691f8 refactor extension code and add error handling 2018-09-01 04:23:29 -04:00
Akash Mozumdar
a978df2655 dont split mono strings 2018-08-31 12:17:48 -04:00
Akash Mozumdar
4854cd35f1 super embarassing bugfix 2018-08-31 12:17:04 -04:00
Akash Mozumdar
4f1bc5a9b2 legacy r code support 2018-08-30 16:48:14 -04:00
Akash Mozumdar
a40222285a update version 2018-08-30 16:09:20 -04:00
Akash Mozumdar
72c37dee88 add error if memsearch fails 2018-08-30 16:09:09 -04:00
Akash Mozumdar
8342850bb7 bugfix deadlock 2018-08-30 02:29:06 -04:00
Akash Mozumdar
6a25a41d79 fix google tanslation 2018-08-29 22:47:00 -04:00
Akash Mozumdar
02c163f930 bugfix hook modifying 2018-08-29 17:15:59 -04:00
Akash Mozumdar
166c9681e4 bugfix pal hook 2018-08-29 17:15:35 -04:00
Akash Mozumdar
53cf099bc6 added new mono hook (probably just works on bsz though) 2018-08-28 22:06:20 -04:00
Akash Mozumdar
728c7a798b add method for searching all process memory 2018-08-28 22:05:56 -04:00
Akash Mozumdar
0b8e2a217b better error reporting 2018-08-28 17:47:11 -04:00
Akash Mozumdar
383fe554c9 bugfix 2018-08-28 17:46:53 -04:00
Akash Mozumdar
fcb3fd63d3 bugfix (?) 2018-08-28 17:28:26 -04:00
Akash Mozumdar
189691a562 refactor 2018-08-28 17:21:20 -04:00
Akash Mozumdar
d1007097ad actual performance improvement 2018-08-28 16:26:19 -04:00
Akash Mozumdar
a36ba41eb4 Revert "performance improvement"
This reverts commit 41f80622cbcbc0f7c17d609a77d92e9e5ed1639b.
2018-08-28 16:25:08 -04:00
Akash Mozumdar
79bcc7d6e1 remove overly defensive programming (that doesnt even work) 2018-08-28 15:18:05 -04:00
Akash Mozumdar
41f80622cb performance improvement 2018-08-28 15:17:45 -04:00
Akash Mozumdar
88b2d85a75 print error code 2018-08-27 20:50:29 -04:00
Akash Mozumdar
4979349a56 enable bsz 2018-08-27 20:50:09 -04:00
Akash Mozumdar
4660be2efb add repetition filter and refactor host 2018-08-27 20:49:33 -04:00
Akash Mozumdar
e92d260b31 reenable wolf hook 2018-08-27 20:48:02 -04:00
Akash Mozumdar
39ef47d85a add more mono hooks 2018-08-27 20:47:26 -04:00
Akash Mozumdar
41e4b9fd9a improve read code 2018-08-26 22:21:15 -04:00
Akash Mozumdar
0509bc13a2 fix typo. how did that go unnoticed for years? 2018-08-26 22:20:58 -04:00
Akash Mozumdar
0e0d44961c prepare for x64 someday 2018-08-26 15:14:45 -04:00
Akash Mozumdar
6d670324f5 prepare for 64 bit hcode support 2018-08-25 20:11:02 -04:00
Akash Mozumdar
33210a95ac enable WPO 2018-08-25 17:09:36 -04:00
Akash Mozumdar
72221106ac change name convention 2018-08-25 17:08:45 -04:00
Akash Mozumdar
3f35c5c230 refactor 2018-08-25 16:02:16 -04:00
Akash Mozumdar
2be762b708 reorganize 2018-08-25 15:51:18 -04:00
Akash Mozumdar
cb55ef9a86
Merge pull request #17 from Artikash/hookrewrite
Rewrite hook logic
2018-08-25 15:47:10 -04:00
Akash Mozumdar
f8ba6102e3 update readme 2018-08-25 15:45:35 -04:00
Akash Mozumdar
a93a7dc444 rewrite hooking logic 2018-08-25 15:45:25 -04:00
Akash Mozumdar
70ae03279c move minhook 2018-08-24 15:09:37 -04:00
Akash Mozumdar
a8cebd1b86 std::optional is beautiful 2018-08-24 14:24:46 -04:00
Akash Mozumdar
61fb3248fe refactor more 2018-08-24 14:04:23 -04:00
Akash Mozumdar
411e4b17dc Merge remote-tracking branch 'origin/master' into hookrewrite 2018-08-24 12:52:17 -04:00
Akash Mozumdar
13c2bfed2f another refactor 2018-08-24 12:50:20 -04:00
Akash Mozumdar
4b3edeef07 add minhook 2018-08-24 11:33:01 -04:00
Akash Mozumdar
db6b09a50e refactor 2018-08-24 11:32:10 -04:00
Akash Mozumdar
65bb4b35cf error handling 2018-08-23 15:28:40 -04:00
Akash Mozumdar
4310e97236 minor fixes 2018-08-23 15:25:33 -04:00
Akash Mozumdar
679317b5f5 bugfixes 2018-08-23 13:15:24 -04:00
Akash Mozumdar
389db72dd2 bugfixes 2018-08-23 12:18:03 -04:00
Akash Mozumdar
446ff71464 reorganize, everything 2018-08-23 11:53:23 -04:00
Akash Mozumdar
98649ffbcc rename 2018-08-22 21:31:15 -04:00
Akash Mozumdar
fc00a3c1ed oops 2018-08-22 19:13:52 -04:00
Akash Mozumdar
6a3c07ee63 update version 2018-08-22 18:40:05 -04:00
Akash Mozumdar
17b057adc0 minor fixes 2018-08-22 18:25:23 -04:00
Akash Mozumdar
e621155e25 refactors 2018-08-22 18:05:45 -04:00
Akash Mozumdar
588b013392 error on architecture mismatch 2018-08-22 15:11:58 -04:00
Akash Mozumdar
cda2cefc28 refactor 2018-08-22 15:11:40 -04:00
Akash Mozumdar
29eb1dcfe6 fix memory leaks 2018-08-22 13:22:37 -04:00
Akash Mozumdar
5984ae514b oops 2018-08-22 12:44:53 -04:00
Akash Mozumdar
4c5814d1a7 no one saw that 2018-08-22 12:44:16 -04:00
Akash Mozumdar
2a222c4446 start cleaning up headers 2018-08-22 12:24:55 -04:00
Akash Mozumdar
af1512b7bf clean up on close 2018-08-22 11:51:28 -04:00
Akash Mozumdar
e35e26c2eb refactor 2018-08-22 01:22:34 -04:00
Akash Mozumdar
51dc229edc delete unused code 2018-08-22 00:57:32 -04:00
Akash Mozumdar
a89e33c68a bugfix 2018-08-22 00:23:41 -04:00
Akash Mozumdar
911fb991be performance improvement 2018-08-21 23:56:16 -04:00
Akash Mozumdar
67e5ff31d1 refactor 2018-08-21 23:41:44 -04:00
Akash Mozumdar
fffb63edf4 google translate error handling 2018-08-21 23:41:30 -04:00
Akash Mozumdar
1312a80ddd bugfix read code 2018-08-21 23:41:19 -04:00
Akash Mozumdar
2c37a50f7b massive refactor 2018-08-21 22:43:30 -04:00
Akash Mozumdar
d03bd93bec bugfix 2018-08-21 12:59:28 -04:00
Akash Mozumdar
52a4fb348b refactor 2018-08-21 12:46:44 -04:00
Akash Mozumdar
47ea350eaa translate everything 2018-08-21 12:41:49 -04:00
Akash Mozumdar
19d7cdb180 TIL timers go to the main thread 2018-08-21 12:41:19 -04:00
Akash Mozumdar
0b3599cb84 better ux for attaching games 2018-08-20 14:30:50 -04:00
Akash Mozumdar
4e10a597c9 bugfix read code 2018-08-20 11:15:48 -04:00
Akash Mozumdar
2f09b534d2 save window dimensions between sessions 2018-08-20 11:00:34 -04:00
Akash Mozumdar
dc2643ffa7 change text size (was uncomfortably big) 2018-08-20 01:19:49 -04:00
Akash Mozumdar
91ac0825f8 misc refactors 2018-08-19 17:55:50 -04:00
Akash Mozumdar
3f7a0fdd52 add google translate 2018-08-19 15:26:50 -04:00
Akash Mozumdar
8711332f77 update build stuff and add the starter extensions 2018-08-19 00:13:19 -04:00
Akash Mozumdar
9733cca628 typo fix 2018-08-18 21:01:05 -04:00
Akash Mozumdar
8a1c946abd Merge branch 'master' of https://github.com/Artikash/NextHooker 2018-08-18 19:15:39 -04:00
Akash Mozumdar
c1005a9736 comment 2018-08-18 19:07:20 -04:00
Akash Mozumdar
715bf9feaa update scenarioplayer hook 2018-08-18 19:06:45 -04:00
Akash Mozumdar
73eedc4177
Update README.md 2018-08-18 15:11:51 -04:00
Akash Mozumdar
f1a2beef4f minor ui fixes 2018-08-18 15:06:14 -04:00
Akash Mozumdar
44a0367883 engine bugfixes 2018-08-18 14:47:30 -04:00
Akash Mozumdar
34acf4f300 improve UX 2018-08-17 14:45:14 -04:00
Akash Mozumdar
b290392034 abstract extension loading code 2018-08-17 14:37:37 -04:00
Akash Mozumdar
16cf2eb9b2 remove qt project, using only cmake 2018-08-17 13:31:46 -04:00
Akash Mozumdar
0f3195a81c update readme 2018-08-17 13:31:15 -04:00
Akash Mozumdar
2be7f72813 clean up host pipe code 2018-08-17 13:20:45 -04:00
Akash Mozumdar
c431cda7d7 bugfix for wine support 2018-08-16 23:16:20 -04:00
Akash Mozumdar
42546e5215 update hook 2018-08-16 21:05:32 -04:00
Akash Mozumdar
12c3262f95 update readme 2018-08-16 19:56:33 -04:00
Akash Mozumdar
751533bdc8 Merge branch 'master' of https://github.com/Artikash/NextHooker 2018-08-16 19:55:13 -04:00
Akash Mozumdar
cf29840c1a update engine matching code 2018-08-16 19:54:46 -04:00
Akash Mozumdar
0634f3dab5
Update README.md 2018-08-11 17:46:57 -04:00
Akash Mozumdar
0db5944c81
Update README.md 2018-08-11 17:46:13 -04:00
Akash Mozumdar
5e630f76f5 update version 2018-08-11 15:15:00 -04:00
Akash Mozumdar
cb7649177f fix renpy hook 2018-08-11 14:52:37 -04:00
Akash Mozumdar
707bc2cdf4 refactor 2018-08-11 14:51:51 -04:00
Akash Mozumdar
d535ece5a3 remove unneeded code from host 2018-08-11 12:48:43 -04:00
Akash Mozumdar
2fcdf141c5 bugfix engine some more 2018-08-11 03:20:31 -04:00
Akash Mozumdar
7353a95a7e fixes for 64 bit 2018-08-11 03:05:31 -04:00
Akash Mozumdar
e06b8f27c8 bugfix engine and add hook for renpy 2018-08-09 15:27:44 -04:00
Akash Mozumdar
dd78e555f8 bugfix concurrency 2018-08-08 23:56:36 -04:00
Akash Mozumdar
b8a76d79b7 more sane way to support 64 bit 2018-08-07 15:44:13 -04:00
Akash Mozumdar
1150d83474 update version 2018-08-07 13:24:54 -04:00
Akash Mozumdar
143a2f091f move display logic to gui 2018-08-07 12:50:31 -04:00
Akash Mozumdar
4b70c62d64 finish removeing ntdll dependence in core hijacking code 2018-08-07 12:49:20 -04:00
Akash Mozumdar
c3707de5b5 extension bugfix 2018-08-07 02:19:20 -04:00
Akash Mozumdar
cda2914a27 engine bugfix 2018-08-07 01:03:41 -04:00
Akash Mozumdar
0d3597a91d gui tweak 2018-08-06 16:27:59 -04:00
Akash Mozumdar
a55f04d628 bugfix 2018-08-06 11:28:57 -04:00
Akash Mozumdar
f09bac71d2 Merge branch 'master' of https://github.com/Artikash/NextHooker 2018-08-06 10:46:27 -04:00
Akash Mozumdar
7a336af56c change gui style 2018-08-06 10:46:19 -04:00
Akash Mozumdar
8e38b3a970
Update README.md 2018-08-06 05:25:07 -04:00
Akash Mozumdar
e86817fb15 Merge branch 'master' of https://github.com/Artikash/NextHooker 2018-08-06 00:23:23 -04:00
Akash Mozumdar
584817744b
Update README.md 2018-08-05 19:10:55 -04:00
Akash Mozumdar
d85ba3911b
Update README.md 2018-08-05 01:06:25 -04:00
Akash Mozumdar
aba5dadc6a add basic support for x64 2018-08-04 22:27:10 -04:00
Akash Mozumdar
959d7c2dd5 bugfixes 2018-08-04 18:42:06 -04:00
Akash Mozumdar
7739c45458 tiny fixes 2018-08-04 18:29:43 -04:00
Akash Mozumdar
ed4879ba5c implement read code 2018-08-04 18:01:59 -04:00
Akash Mozumdar
9a299f357f prepare for /r code 2018-08-04 04:33:56 -04:00
Akash Mozumdar
c32066e43c make host play nice with larger addresses, and more refactoring 2018-08-04 03:16:14 -04:00
Akash Mozumdar
bc6c1325ed remove inline asm 2018-08-04 02:03:39 -04:00
Akash Mozumdar
f22ccbd909 clean up engine more, prepare for 64-bit support 2018-08-04 01:27:28 -04:00
Akash Mozumdar
3a0b0bb77e clean up engine 2018-08-03 19:53:31 -04:00
Akash Mozumdar
df9fb07395 clean up engine 2018-08-03 15:04:48 -04:00
Akash Mozumdar
1b9febb4a5 bugfix/clean up engine 2018-08-03 14:07:11 -04:00
Akash Mozumdar
5edfb4de0d bug fix and version update 2018-08-03 10:41:30 -04:00
Akash Mozumdar
60fb17155e change build system, prepare for x64 someday 2018-08-03 10:00:42 -04:00
Akash Mozumdar
1e58842ca9 remove unneeded var 2018-08-03 02:32:23 -04:00
Akash Mozumdar
c7f119e0a2 add /h code explanation 2018-08-03 02:32:05 -04:00
Akash Mozumdar
6835c339d7 implement utf8 2018-08-03 01:48:57 -04:00
Akash Mozumdar
afe0464b56 bugfix/reorganize 2018-08-03 00:07:25 -04:00
Akash Mozumdar
31dafc4416 bugfix 2018-08-02 20:06:01 -04:00
Akash Mozumdar
42585984c0 add icon 2018-08-02 17:18:08 -04:00
Akash Mozumdar
24407f8979 receive hook removal via pipe 2018-08-02 17:17:54 -04:00
Akash Mozumdar
11d75c2987 notify hook removal via pipe - fix hook rewrite bug 2018-08-02 17:16:49 -04:00
Akash Mozumdar
971e35dc10 bugfix and update version 2018-08-02 02:17:20 -04:00
Akash Mozumdar
b1b4625a06 bugfix 2018-08-02 01:06:01 -04:00
Akash Mozumdar
634bb04f48 new and updated hooks for BGI, WAFFLE, and QLIE 2018-08-02 01:05:53 -04:00
Akash Mozumdar
bc8ba90838 final commits, ready for next release! 2018-07-30 20:25:08 -07:00
Akash Mozumdar
f63bd97f71 change to free 2018-07-30 11:29:36 -07:00
Akash Mozumdar
e4d74fe6ab reformat dev list 2018-07-30 00:56:24 -07:00
Akash Mozumdar
732cbce106 update developer list 2018-07-30 00:51:27 -07:00
Akash Mozumdar
b5eef8b3fe free memory myself 2018-07-30 00:47:09 -07:00
Akash Mozumdar
7a39c40936 add const 2018-07-30 00:19:44 -07:00
Akash Mozumdar
cf2a658ac7 bugfix 2018-07-29 18:24:46 -07:00
Akash Mozumdar
ff2fc74c5a change abi to be C compatible 2018-07-29 14:36:45 -07:00
Akash Mozumdar
5d297404f6
Merge pull request #9 from DoumanAsh/full_cmake
Add cmake support for GUI part
2018-07-28 22:39:21 -07:00
Akash Mozumdar
dbe7bed0d0 stop unneeded static linking, have to dynamic link to qt anyway 2018-07-28 22:37:54 -07:00
Akash Mozumdar
4acd030e54 (almost) finish implementing gui 2018-07-28 12:41:21 -07:00
Douman
7808fd508c Add cmake support for GUI part 2018-07-28 12:05:13 +03:00
Akash Mozumdar
5fbf92bc07 bugfix for extensions 2018-07-26 23:16:15 -07:00
Akash Mozumdar
41566e68c1 start implementing extensions 2018-07-26 22:42:21 -07:00
Akash Mozumdar
f3c4884660 continue implementing new gui 2018-07-25 21:48:18 -07:00
Akash Mozumdar
d0b1efd033 tabs to spaces and bugfixes 2018-07-25 10:46:59 -07:00
Akash Mozumdar
9c5be4be80 bugfix 2018-07-25 01:16:23 -07:00
Akash Mozumdar
11016d146a minor fixes 2018-07-25 01:11:23 -07:00
Akash Mozumdar
11d6a156dd set up GUI skeleton, and small bugfixes 2018-07-24 19:00:10 -07:00
Akash Mozumdar
a85c398f47
Merge pull request #7 from Artikash/rewritegui
Rewrite gui
2018-07-24 10:40:38 -07:00
Akash Mozumdar
541e3cc4e7 implement callbacks properly and update gui some more 2018-07-24 10:39:02 -07:00
Akash Mozumdar
97fe9800a6 implement basic gui 2018-07-23 22:57:54 -07:00
Akash Mozumdar
fe30b77a44 start implementing new GUI 2018-07-23 12:25:02 -07:00
Akash Mozumdar
ffeb4e2dad final fix for build pipeline 2018-07-22 23:27:12 -07:00
Akash Mozumdar
e12a35a609 update config 2018-07-22 18:33:45 -07:00
Akash Mozumdar
e500c9ceed refactor 2018-07-22 18:15:43 -07:00
Akash Mozumdar
3d4ee3d698 refactoring and bugfixes 2018-07-22 18:10:00 -07:00
Akash Mozumdar
89f8cb9d9b
Delete GUI.pro.user 2018-07-22 16:56:33 -07:00
Akash Mozumdar
ff6dd7c7d8 link qt with vnrhost 2018-07-22 16:53:51 -07:00
Akash Mozumdar
a01e7c70a8 add qt gui 2018-07-21 15:40:16 -07:00
Akash Mozumdar
fd82471303 remove unneeded stuff 2018-07-21 15:30:42 -07:00
Akash Mozumdar
4543a86e48 give up on c# interop (callbacks too difficult). trying qt instead 2018-07-21 15:11:28 -07:00
Akash Mozumdar
2905ed4f9c update config 2018-07-21 14:23:59 -07:00
Akash Mozumdar
3f6348e7d0 remove old gui 2018-07-21 14:19:25 -07:00
Akash Mozumdar
a4eede1b4e remove unneeded logic from host 2018-07-21 14:16:26 -07:00
Akash Mozumdar
4aef775a6a prepare for use with c++/cli 2018-07-21 14:01:38 -07:00
Akash Mozumdar
a2924f4a2c continue implementing interop 2018-07-21 11:09:07 -07:00
Akash Mozumdar
a5232b605e update config/readme 2018-07-21 08:22:32 -07:00
Akash Mozumdar
af19b4e481 start implementing hostinterop 2018-07-21 01:08:55 -07:00
Akash Mozumdar
224af91806 add host interoperability project, and add everything to organized build system 2018-07-21 00:51:27 -04:00
Akash Mozumdar
dd6230c92d Revert "add project for c#/c++ interoperability"
This reverts commit a8cdee67e8d06ef1265bbac1629a101aa63b344e.
2018-07-20 23:44:03 -04:00
Akash Mozumdar
a055db4bf8 Revert "start implementing hostinterop"
This reverts commit 62b5f5fe8f788629c95cbd2375a0d9845decb4c1.
2018-07-20 23:43:55 -04:00
Akash Mozumdar
a8f2ca79a5 Revert "fix? hostinterop"
This reverts commit 314c17b4351c7676b92ec627f729fa0bc4157748.
2018-07-20 23:43:25 -04:00
Akash Mozumdar
314c17b435 fix? hostinterop 2018-07-20 22:10:03 -04:00
Akash Mozumdar
62b5f5fe8f start implementing hostinterop 2018-07-20 17:18:27 -04:00
Akash Mozumdar
a8cdee67e8 add project for c#/c++ interoperability 2018-07-20 16:54:51 -04:00
Akash Mozumdar
1380f64cec minor fix 2018-07-20 16:26:27 -04:00
Akash Mozumdar
ebc1303e9a add new gui 2018-07-20 16:08:30 -04:00
Akash Mozumdar
933a225a12 rename folder 2018-07-20 15:50:26 -04:00
Akash Mozumdar
9407c96be3
Merge pull request #5 from Artikash/cleanup
Cleanup
2018-07-20 15:15:03 -04:00
Akash Mozumdar
b7bf5b91f0 move folders into root 2018-07-20 15:10:34 -04:00
Akash Mozumdar
61ca07d29b move profile stuff into gui 2018-07-20 14:55:58 -04:00
Akash Mozumdar
f132e01b7a move stuff only used by vnrhook into vnrhook 2018-07-20 17:50:19 -04:00
Akash Mozumdar
9dfebf7eac minor fixes 2018-07-20 17:36:58 -04:00
Akash Mozumdar
a290c36b52 remove extension code from host 2018-07-20 17:21:35 -04:00
Akash Mozumdar
f4980d6eb6 minor fixes 2018-07-19 23:22:55 -04:00
Akash Mozumdar
379024afc0
Merge pull request #4 from Artikash/encapsulate
Encapsulate
2018-07-19 21:47:51 -04:00
Akash Mozumdar
5ea77e23a5 bugfix 2018-07-19 21:44:03 -04:00
Akash Mozumdar
d6a90e5426 remove direct access to process records 2018-07-19 20:51:49 -04:00
Akash Mozumdar
5ae1e811d7 remove msvc redistributable dependency 2018-07-19 15:47:48 -04:00
Akash Mozumdar
7df8e40fc9 more error checking 2018-07-19 14:34:34 -04:00
Akash Mozumdar
a7a3379050 minor fixes 2018-07-19 13:34:26 -04:00
Akash Mozumdar
55764bf8e2 more bugfixing 2018-07-19 02:02:42 -04:00
Akash Mozumdar
097e274347 bugfix 2018-07-19 01:44:44 -04:00
Akash Mozumdar
e4f5f3cfd5 refactor 2018-07-19 01:43:31 -04:00
Akash Mozumdar
05a14d201e update readme 2018-07-19 01:16:21 -04:00
Akash Mozumdar
775af2da54 update readme 2018-07-19 01:06:38 -04:00
Akash Mozumdar
7a6fe61edc remove unneeded return values 2018-07-19 01:05:15 -04:00
Akash Mozumdar
6598b979b3 more refactoring 2018-07-19 00:46:52 -04:00
Akash Mozumdar
48450f9717 reorganize files 2018-07-19 00:09:58 -04:00
Akash Mozumdar
2065359a4e shit ton of refactoring and bugfixes 2018-07-18 23:40:44 -04:00
Akash Mozumdar
904804de28 refactor textthread 2018-07-18 16:18:43 -04:00
Akash Mozumdar
4368826f2a update detection for AIRNovel 2018-07-18 12:44:17 -04:00
Akash Mozumdar
a52bfd36f8 config fix 2018-07-18 00:24:22 -04:00
Akash Mozumdar
ffbb081bcc refactor and start using pipe instead of section to get hook info 2018-07-17 19:18:36 -04:00
Akash Mozumdar
ae6656441b clean up vnrhost 2018-07-17 17:01:56 -04:00
Akash Mozumdar
803a1cd4bf metadata update 2018-07-17 11:09:28 -04:00
Akash Mozumdar
8a11cc9949 add tyranobuilder hook. performance optimization. improve airnovel hook 2018-07-16 14:04:21 -04:00
Akash Mozumdar
8e615704ca final touches 2018-07-15 15:56:32 -04:00
Akash Mozumdar
af2708217c add adobe air/airnovel hook, fix small bug 2018-07-15 15:37:26 -04:00
Akash Mozumdar
c87729814a minor fixes 2018-07-14 23:18:08 -04:00
Akash Mozumdar
7802195193 update metadata 2018-07-14 10:20:28 -04:00
Akash Mozumdar
18b9c7bd38 Merge branch 'master' of https://github.com/Artikash/NextVNR 2018-07-13 21:01:02 -04:00
Akash Mozumdar
b903d77730 hotfix for win7 2018-07-13 20:51:09 -04:00
Akash Mozumdar
5ec35dcbae
Update README.md 2018-07-13 13:10:04 -04:00
Akash Mozumdar
ef36cb9274 really final fixes for beta release 2018-07-13 11:47:16 -04:00
Akash Mozumdar
a05714fdae update metadata 2018-07-13 07:38:37 -04:00
Akash Mozumdar
5e3230373e final fixes for beta release 2018-07-12 17:47:22 -04:00
Akash Mozumdar
758e729f30 clean up a lot of code 2018-07-12 13:59:05 -04:00
Akash Mozumdar
423ee9efac clean a lot of stuff up and improve extension support. not sure that i still have thread safety though... 2018-07-11 20:18:04 -04:00
Akash Mozumdar
9da6875444 continue implementing the removal of section object 2018-07-04 23:17:40 -04:00
Akash Mozumdar
3d342503f0 start updating hook name code 2018-07-03 07:38:46 -04:00
Akash Mozumdar
1a2672604a remove fillRange 2018-07-01 02:08:51 -04:00
Akash Mozumdar
9bc95191ff removed reliance on undocumented windows internals in vnrhook 2018-06-30 05:55:10 -04:00
Akash Mozumdar
3a001e66b2 rename stuff 2018-06-21 02:59:40 -04:00
Akash Mozumdar
faa4eb55e0 remove misleading console output 2018-06-21 01:39:07 -04:00
Akash Mozumdar
37d29586d6 misc QoL changes 2018-06-20 19:17:35 -04:00
Akash Mozumdar
1e83b50fdf fix weird bug 2018-06-20 19:04:18 -04:00
Akash Mozumdar
545b6f9492 use std unordered map instead of custom avl tree 2018-06-19 16:19:29 -04:00
Akash Mozumdar
29e6229326 delete unused code 2018-06-19 01:37:42 -04:00
Akash Mozumdar
393341a443 finish rename 2018-06-18 14:45:16 -04:00
Akash Mozumdar
534838342a rename files for consistency 2018-06-18 14:30:33 -04:00
Akash Mozumdar
c6ca4696f1 remove hook filter 2018-06-18 14:19:13 -04:00
Akash Mozumdar
3a0a24878f update config stuff 2018-06-18 01:16:34 -04:00
Akash Mozumdar
acf639dd45 stop filtering space 2018-06-17 21:04:04 -04:00
Akash Mozumdar
6a199a8246 update cmakelists-ithsys no longer a library 2018-06-17 20:49:25 -04:00
Akash Mozumdar
4456e1c35c remove include 2018-06-16 04:17:04 -04:00
Akash Mozumdar
e3f64131f6 remove ntinspect 2018-06-16 03:49:56 -04:00
Akash Mozumdar
8cfb3def97 update cmake settings 2018-06-15 19:30:32 -04:00
Akash Mozumdar
39e439e4b1 remove unused code 2018-06-15 07:43:32 -04:00
Akash Mozumdar
a422fca50d fix regex 2018-06-15 06:50:25 -04:00
Akash Mozumdar
73246f7eea bugfixes 2018-06-15 05:51:11 -04:00
Akash Mozumdar
5890f0022c migrate off legacy api calls 2018-06-15 04:32:35 -04:00
Akash Mozumdar
b7764ad860 remove ntdll from util.h 2018-06-14 11:51:09 -04:00
Akash Mozumdar
d89cc56d6f remove more stuff from ntdll 2018-06-13 21:52:45 -04:00
Akash Mozumdar
c393d29115 start migrating off ntdll 2018-06-13 16:24:52 -04:00
Akash Mozumdar
c5d847f310 remove random dependencies 2018-06-13 00:52:44 -04:00
Akash Mozumdar
47dccbf218 remove silly macros 2018-06-12 20:02:41 -04:00
Akash Mozumdar
162b2cb79f remove unused custom filter code 2018-06-12 19:37:45 -04:00
Akash Mozumdar
1dbaff780c remove more unused code 2018-06-12 19:31:52 -04:00
Akash Mozumdar
6d7a800a56 remove more unused code 2018-06-12 17:41:38 -04:00
Akash Mozumdar
32bcee8f50 simplify thread safety code 2018-06-12 17:31:24 -04:00
Akash Mozumdar
30f95423f1 remove dependency of ithsys on host 2018-06-11 19:49:28 -04:00
Akash Mozumdar
3ac79fb9c5 remove unused code 2018-06-11 19:17:46 -04:00
Akash Mozumdar
b6a43e7283 move definition of flushthread to more sensible place 2018-06-10 20:35:24 -04:00
Akash Mozumdar
27fa8c7db7 Merge branch 'master' of https://github.com/Artikash/NextVNR 2018-06-10 19:48:35 -04:00
Akash Mozumdar
efd162ae40 remove outdated korean gui 2018-06-10 19:48:17 -04:00
Akash Mozumdar
2ea47a56cb
Merge pull request #3 from Artikash/rewritetextthread
Rewrite textthread
2018-06-10 19:43:10 -04:00
Akash Mozumdar
5f24f1efd7 clean up textthread some more 2018-06-10 19:15:05 -04:00
Akash Mozumdar
c80f36efff remove unused code from textthread and related files 2018-06-10 04:53:14 -04:00
Akash Mozumdar
bb0133bc58 remove unused code from textthread 2018-06-06 23:33:17 -04:00
Akash Mozumdar
2cf987bef5 refactor textthread a little 2018-06-06 23:11:35 -04:00
Akash Mozumdar
79d39ce135 add comment 2018-06-03 14:51:40 -04:00
Akash Mozumdar
4e373df13b reenable gdi hooks 2018-06-03 14:50:55 -04:00
Akash Mozumdar
a1962865f8 quick bugfix 2018-06-03 12:47:16 -04:00
Akash Mozumdar
b59a962f4a
Merge pull request #2 from Artikash/rewritehookman
Rewrite hook manager
2018-06-02 14:30:50 -04:00
Akash Mozumdar
337038d684 really finish rewrite. everything works (afaict). still messy but thats not new lol 2018-06-02 14:29:32 -04:00
Akash Mozumdar
3a6b98b48b
Update README.md 2018-06-01 09:56:40 -04:00
Akash Mozumdar
fb56457cdb finish rewriting hookman, still some cleanup to do 2018-06-01 02:36:51 -04:00
Akash Mozumdar
aaf33094d1 remove linking (useless and a pain to update) and continue rewriting hookman 2018-05-31 06:17:14 -04:00
Akash Mozumdar
97bca014e4 start rewriting hookman with c++ 14 features 2018-05-31 05:40:00 -04:00
Akash Mozumdar
1a2156f502 remove unneeded file 2018-05-31 04:45:17 -04:00
Akash Mozumdar
ca6ec15ac8 clean up hook manager a little (remove unused stuff) 2018-05-31 04:44:33 -04:00
Akash Mozumdar
f8cb81ec59 update readme 2018-05-26 06:48:14 -04:00
Akash Mozumdar
0fb2c3a9c1 update readme 2018-05-26 06:38:24 -04:00
Akash Mozumdar
9715612b83 add basic extension support 2018-05-25 04:34:40 -04:00
Akash Mozumdar
0b79811cc3 Revert "remove korean gui (for now) since its hard to maintain"
This reverts commit 60866c158a11a2d666998aba7cf70b0201c7a764.
2018-05-24 05:36:05 -04:00
Akash Mozumdar
553059ee0f Revert "start cleaning up hookman"
This reverts commit dff4ae34630fe46123bced537b78ad793fadef4c.
2018-05-24 05:36:00 -04:00
Akash Mozumdar
2591a8c124 Revert "clean up hookman some more"
This reverts commit 1fb66ddc04cec7b2d31bdab4b8992e61d0696a43.
2018-05-24 05:35:54 -04:00
Akash Mozumdar
fe552f6e19 Revert "remove unused code"
This reverts commit b3496222ec1b24df40365d77e8ea6c902873cbc1.
2018-05-24 05:35:50 -04:00
Akash Mozumdar
b3496222ec remove unused code 2018-05-24 03:49:05 -04:00
Akash Mozumdar
1fb66ddc04 clean up hookman some more 2018-05-23 04:31:21 -04:00
Akash Mozumdar
dff4ae3463 start cleaning up hookman 2018-05-23 03:23:18 -04:00
Akash Mozumdar
60866c158a remove korean gui (for now) since its hard to maintain 2018-05-21 19:27:12 -04:00
Akash Mozumdar
ef1884701f move constant definitions around some 2018-05-21 04:05:09 -04:00
Akash Mozumdar
7aa4e51803 update readme 2018-05-21 03:32:29 -04:00
Akash Mozumdar
2ba72fcbfe add license and update readme 2018-05-20 21:21:37 -04:00
Akash Mozumdar
50d7cdf8fb remove useless code 2018-05-20 19:04:10 -04:00
Akash Mozumdar
1ab516a1b8 remove unused code 2018-05-20 17:12:17 -04:00
Akash Mozumdar
6dab2871eb undo overzealous changes 2018-05-20 17:07:42 -04:00
Akash Mozumdar
65ec2d5be0 remove unneeded abstraction 2018-05-20 16:26:26 -04:00
Akash Mozumdar
05fa52f589 refactor 2018-05-20 16:10:07 -04:00
Akash Mozumdar
290ef57490 cleaning things up 2018-05-20 13:11:55 -04:00
Akash Mozumdar
c7a79e6064 clean up more code/rename vars 2018-05-13 15:33:36 -04:00
Akash Mozumdar
38a57eb480 delete unused files and start cleaning up code 2018-05-11 16:46:05 -04:00
257 changed files with 12661 additions and 37140 deletions

39
.appveyor.yml Normal file
View File

@ -0,0 +1,39 @@
version: '{branch}-{build}'
configuration: RelWithDebInfo
clone_folder: C:\Textractor
skip_branch_with_pr: false
environment:
matrix:
- APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2017
arch: x64
platform: x64
qtbin: msvc2017_64
- APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2017
arch: x86
platform: Win32
qtbin: msvc2017
before_build:
- git submodule update --init
- cd C:\
- mkdir %arch%
- cd %arch%
- cmake -G "Visual Studio 15 2017" -A %platform% -DQt5_DIR="C:\Qt\5.13.2\%qtbin%\lib\cmake\Qt5" -DCMAKE_BUILD_TYPE="RelWithDebInfo" -DVERSION="" ../Textractor
build:
project: C:\%arch%\Textractor.sln
parallel: false
verbosity: normal
after_build:
- 7z a Textractor-Alpha%APPVEYOR_BUILD_NUMBER%-%arch%.zip C:\Textractor\builds\RelWithDebInfo_%arch%\*
- appveyor PushArtifact Textractor-Alpha%APPVEYOR_BUILD_NUMBER%-%arch%.zip
notifications:
- provider: Email
to:
- akashmozumdar@gmail.com
on_build_success: false
on_build_failure: true
on_build_status_changed: true

10
.gitignore vendored
View File

@ -1,2 +1,8 @@
gui/command.cpp
.vs/
/Textractor*
/build/
/builds/
/out/
*.vs/
*.vscode/
*.user
*.aps

3
.gitmodules vendored Normal file
View File

@ -0,0 +1,3 @@
[submodule "texthook/minhook"]
path = texthook/minhook
url = https://github.com/TsudaKageyu/minhook.git

View File

@ -1,117 +1,50 @@
cmake_minimum_required(VERSION 2.8)
cmake_minimum_required(VERSION 3.16)
set(CMAKE_CONFIGURATION_TYPES Debug Release)
project(Textractor)
project(ITHVNR)
if (NOT MSVC)
message(FATAL_ERROR "Textractor can only be built with Visual Studio")
endif()
set(WDK_HOME "C:\\WinDDK\\7600.16385.1" CACHE FILEPATH "Windows Driver Kit path")
set(CMAKE_INSTALL_PREFIX "" CACHE FILEPATH "installation path")
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY_DEBUG "${CMAKE_BINARY_DIR}/Debug")
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY_RELEASE "${CMAKE_BINARY_DIR}/Release")
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY_DEBUG "${CMAKE_BINARY_DIR}/Debug")
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY_RELEASE "${CMAKE_BINARY_DIR}/Release")
set(CMAKE_WARN_ON_ABSOLUTE_INSTALL_DESTINATION ON)
set(CPACK_GENERATOR "ZIP")
set(CPACK_PACKAGE_VERSION_MAJOR 3)
set(CPACK_PACKAGE_VERSION_MINOR 5640)
set(CPACK_PACKAGE_VERSION_PATCH 1)
set(CPACK_SOURCE_GENERATOR "ZIP")
set(CPACK_SOURCE_IGNORE_FILES "/CVS/;/\\\\.svn/;/\\\\.bzr/;/\\\\.hg/;/\\\\.git/;\\\\.swp$;\\\\.#;/#" ".*\\\\.user$" "\\\\.gitignore$" "\\\\.gitmodules$" "\\\\.git$")
include(CPack)
set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${CMAKE_CURRENT_SOURCE_DIR}/cmake)
add_compile_options(
#/Zc:auto # config.pri
/wd4819 # config.pri
/std:c++17
/MP
/GS-
# $<$<CONFIG:Release>:/MT>
# $<$<CONFIG:Debug>:/MTd>
)
add_definitions(
/D_SECURE_SCL=0 # config.pri
/D_SCL_SECURE_NO_WARNINGS # config.pri
/D_CRT_SECURE_NO_WARNINGS # config.pri
/wd4018 # signed/unsigned mismatch
/DVERSION="${VERSION}"
/DUNICODE # config.pri
/D_UNICODE
/D_CRT_NON_CONFORMING_SWPRINTFS # common.pri
/DITH_HAS_CRT
)
include_directories(
.
vnr
vnr/texthook
${CMAKE_BINARY_DIR}/gui
)
if(${CMAKE_SIZEOF_VOID_P} EQUAL 8)
set(CMAKE_FINAL_OUTPUT_DIRECTORY ${CMAKE_SOURCE_DIR}/builds/${CMAKE_BUILD_TYPE}_x64)
link_directories(x64libs)
else()
set(CMAKE_FINAL_OUTPUT_DIRECTORY ${CMAKE_SOURCE_DIR}/builds/${CMAKE_BUILD_TYPE}_x86)
link_directories(x86libs)
endif()
set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY $<1:${CMAKE_FINAL_OUTPUT_DIRECTORY}>)
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY $<1:${CMAKE_FINAL_OUTPUT_DIRECTORY}>)
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY $<1:${CMAKE_FINAL_OUTPUT_DIRECTORY}>)
set(resource_src
gui/ITHVNR.rc
gui/icon1.ico
)
include_directories(include)
add_library(pch text.cpp)
target_precompile_headers(pch PUBLIC include/common.h)
set(ithvnr_src
gui/command.cpp
gui/CustomFilter.cpp
gui/CustomFilter.h
gui/ITH.h
gui/language.cpp
gui/language.h
gui/main.cpp
gui/ProcessWindow.cpp
gui/ProcessWindow.h
gui/ProfileManager.cpp
gui/ProfileManager.h
gui/resource.h
gui/utility.cpp
gui/utility.h
${CMAKE_BINARY_DIR}/gui/version.h
gui/version.h.in
gui/window.cpp
gui/window.h
gui/TextBuffer.cpp
gui/TextBuffer.h
${resource_src}
)
file(GLOB ASSETS assets/*)
file(COPY ${ASSETS} DESTINATION ${CMAKE_FINAL_OUTPUT_DIRECTORY})
source_group("Resource Files" FILES ${resource_src})
include(QtUtils)
msvc_registry_search()
find_qt5(Core Widgets WinExtras WebSockets)
add_executable(${PROJECT_NAME} ${ithvnr_src})
add_library(text text.cpp)
target_compile_definitions(text PRIVATE ${TEXT_LANGUAGE})
link_libraries(text)
add_subdirectory(vnr)
# add_subdirectory(profile)
set_target_properties(${PROJECT_NAME} PROPERTIES
LINK_FLAGS "/SUBSYSTEM:WINDOWS /MANIFESTDEPENDENCY:\"type='win32' name='Microsoft.Windows.Common-Controls' version='6.0.0.0' processorArchitecture='*' publicKeyToken='6595b64144ccf1df' language='*'\""
)
target_compile_definitions(${PROJECT_NAME}
PRIVATE
PSAPI_VERSION=1
DEFAULT_MM
)
target_link_libraries(${PROJECT_NAME}
profile
vnrhost
ithsys
${WDK_HOME}/lib/wxp/i386/ntdll.lib
comctl32.lib
psapi.lib
)
target_compile_options(${PROJECT_NAME}
PRIVATE
/EHsc
)
install(TARGETS ${PROJECT_NAME}
DESTINATION .
CONFIGURATIONS Release
)
configure_file(gui/version.h.in gui/version.h)
add_subdirectory(host)
add_subdirectory(texthook)
add_subdirectory(GUI)
add_subdirectory(extensions)
add_subdirectory(test)

32
CMakeSettings.json Normal file
View File

@ -0,0 +1,32 @@
{
"configurations": [
{
"name": "x86-Debug",
"generator": "Ninja",
"configurationType": "Debug",
"cmakeCommandArgs": "-DQT_ROOT=/Qt",
"inheritEnvironments": [ "msvc_x86" ]
},
{
"name": "x86-RelWithDebInfo",
"generator": "Ninja",
"configurationType": "RelWithDebInfo",
"cmakeCommandArgs": "-DQT_ROOT=/Qt",
"inheritEnvironments": [ "msvc_x86" ]
},
{
"name": "x64-Debug",
"generator": "Ninja",
"configurationType": "Debug",
"cmakeCommandArgs": "-DQT_ROOT=/Qt",
"inheritEnvironments": [ "msvc_x64" ]
},
{
"name": "x64-RelWithDebInfo",
"generator": "Ninja",
"configurationType": "RelWithDebInfo",
"cmakeCommandArgs": "-DQT_ROOT=/Qt",
"inheritEnvironments": [ "msvc_x64" ]
}
]
}

20
GUI/CMakeLists.txt Normal file
View File

@ -0,0 +1,20 @@
add_executable(Textractor WIN32
main.cpp
exception.cpp
mainwindow.cpp
extenwindow.cpp
attachprocessdialog.cpp
Textractor.rc
Textractor.ico
)
target_precompile_headers(Textractor REUSE_FROM pch)
target_link_libraries(Textractor host Qt5::Widgets Qt5::WinExtras shell32 winhttp)
if (NOT EXISTS ${CMAKE_FINAL_OUTPUT_DIRECTORY}/Qt5Core.dll AND NOT EXISTS ${CMAKE_FINAL_OUTPUT_DIRECTORY}/Qt5Cored.dll)
add_custom_command(TARGET Textractor
POST_BUILD
COMMAND ${CMAKE_COMMAND} -E remove_directory "${CMAKE_CURRENT_BINARY_DIR}/windeployqt"
COMMAND set PATH=%PATH%$<SEMICOLON>${qt5_install_prefix}/bin
COMMAND Qt5::windeployqt --dir ${CMAKE_FINAL_OUTPUT_DIRECTORY} "${CMAKE_FINAL_OUTPUT_DIRECTORY}/Textractor.exe"
)
endif()

BIN
GUI/Textractor.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

1
GUI/Textractor.rc Normal file
View File

@ -0,0 +1 @@
IDI_ICON1 ICON DISCARDABLE "Textractor.ico"

View File

@ -0,0 +1,39 @@
#include "attachprocessdialog.h"
#include <QtWinExtras/QtWin>
extern const char* SELECT_PROCESS;
extern const char* ATTACH_INFO;
AttachProcessDialog::AttachProcessDialog(QWidget* parent, std::vector<std::pair<QString, HICON>> processIcons) :
QDialog(parent, Qt::WindowCloseButtonHint),
model(this)
{
ui.setupUi(this);
setWindowTitle(SELECT_PROCESS);
ui.label->setText(ATTACH_INFO);
ui.processList->setModel(&model);
QPixmap transparent(100, 100);
transparent.fill(QColor::fromRgba(0));
for (const auto& [process, icon] : processIcons)
{
auto item = new QStandardItem(icon ? QIcon(QtWin::fromHICON(icon)) : transparent, process);
item->setEditable(false);
model.appendRow(item);
}
connect(ui.buttonBox, &QDialogButtonBox::accepted, this, &QDialog::accept);
connect(ui.buttonBox, &QDialogButtonBox::rejected, this, &QDialog::reject);
connect(ui.processList, &QListView::clicked, [this](QModelIndex index) { ui.processEdit->setText(model.item(index.row())->text()); });
connect(ui.processList, &QListView::doubleClicked, this, &QDialog::accept);
connect(ui.processEdit, &QLineEdit::textEdited, [this](QString process)
{
for (int i = 0; i < model.rowCount(); ++i) ui.processList->setRowHidden(i, !model.item(i)->text().contains(process, Qt::CaseInsensitive));
});
connect(ui.processEdit, &QLineEdit::returnPressed, this, &QDialog::accept);
}
QString AttachProcessDialog::SelectedProcess()
{
return ui.processEdit->text();
}

17
GUI/attachprocessdialog.h Normal file
View File

@ -0,0 +1,17 @@
#pragma once
#include "qtcommon.h"
#include "ui_attachprocessdialog.h"
#include <QStandardItemModel>
class AttachProcessDialog : public QDialog
{
public:
explicit AttachProcessDialog(QWidget* parent, std::vector<std::pair<QString, HICON>> processIcons);
QString SelectedProcess();
private:
Ui::AttachProcessDialog ui;
QStandardItemModel model;
QString selectedProcess;
};

View File

@ -0,0 +1,37 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>AttachProcessDialog</class>
<widget class="QWidget" name="AttachProcessDialog">
<property name="windowModality">
<enum>Qt::WindowModal</enum>
</property>
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>800</width>
<height>400</height>
</rect>
</property>
<layout class="QVBoxLayout">
<item>
<widget class="QLabel" name="label"/>
</item>
<item>
<widget class="QLineEdit" name="processEdit"/>
</item>
<item>
<widget class="QListView" name="processList"/>
</item>
<item>
<widget class="QDialogButtonBox" name="buttonBox">
<property name="standardButtons">
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
</property>
</widget>
</item>
</layout>
</widget>
<resources/>
<connections/>
</ui>

59
GUI/exception.cpp Normal file
View File

@ -0,0 +1,59 @@
#include "module.h"
#include <sstream>
namespace
{
char* GetCppExceptionInfo(EXCEPTION_POINTERS* exception)
{
// https://blogs.msdn.microsoft.com/oldnewthing/20100730-00/?p=13273
// Not very reliable so use __try
__try { return ((char****)exception->ExceptionRecord->ExceptionInformation[2])[3][1][1] + 8; }
__except (EXCEPTION_EXECUTE_HANDLER) { return "Could not find"; }
}
const char* GetCppExceptionMessage(EXCEPTION_POINTERS* exception)
{
__try { return ((std::exception*)exception->ExceptionRecord->ExceptionInformation[1])->what(); }
__except (EXCEPTION_EXECUTE_HANDLER) { return "Could not find"; }
}
thread_local std::wstring lastError = L"Unknown error";
__declspec(noreturn) void Terminate()
{
WaitForSingleObject(CreateThread(nullptr, 0, [](void* lastError) -> DWORD
{
MessageBoxW(NULL, (wchar_t*)lastError, L"Textractor ERROR", MB_ICONERROR); // might fail to display if called in main thread and exception was in main event loop
abort();
}, lastError.data(), 0, nullptr), INFINITE);
}
LONG WINAPI ExceptionLogger(EXCEPTION_POINTERS* exception)
{
thread_local static auto _ = set_terminate(Terminate);
MEMORY_BASIC_INFORMATION info = {};
VirtualQuery(exception->ExceptionRecord->ExceptionAddress, &info, sizeof(info));
std::wstringstream errorMsg;
errorMsg << std::uppercase << std::hex <<
L"Error code: " << exception->ExceptionRecord->ExceptionCode << std::endl <<
L"Error address: " << exception->ExceptionRecord->ExceptionAddress << std::endl <<
L"Error in module: " << GetModuleFilename((HMODULE)info.AllocationBase).value_or(L"Could not find") << std::endl <<
L"Additional info: " << info.AllocationBase << std::endl;
if (exception->ExceptionRecord->ExceptionCode == 0xE06D7363)
{
if (char* info = GetCppExceptionInfo(exception)) errorMsg << L"Additional info: " << info << std::endl;
if (const char* info = GetCppExceptionMessage(exception)) errorMsg << L"Additional info: " << info << std::endl;
}
for (int i = 0; i < exception->ExceptionRecord->NumberParameters; ++i)
errorMsg << L"Additional info: " << exception->ExceptionRecord->ExceptionInformation[i] << std::endl;
lastError = errorMsg.str();
return EXCEPTION_CONTINUE_SEARCH;
}
auto _ = (AddVectoredExceptionHandler(FALSE, ExceptionLogger), SetUnhandledExceptionFilter([](auto) -> LONG { Terminate(); }));
}

176
GUI/extenwindow.cpp Normal file
View File

@ -0,0 +1,176 @@
#include "extenwindow.h"
#include "ui_extenwindow.h"
#include <QMenu>
#include <QFileDialog>
#include <QDragEnterEvent>
#include <QDropEvent>
#include <QMimeData>
#include <QUrl>
extern const char* EXTENSIONS;
extern const char* ADD_EXTENSION;
extern const char* REMOVE_EXTENSION;
extern const char* INVALID_EXTENSION;
extern const char* CONFIRM_EXTENSION_OVERWRITE;
extern const char* EXTENSION_WRITE_ERROR;
extern const char* EXTEN_WINDOW_INSTRUCTIONS;
namespace
{
constexpr auto EXTEN_SAVE_FILE = u8"SavedExtensions.txt";
constexpr auto DEFAULT_EXTENSIONS = u8"Remove Repeated Characters>Regex Filter>Copy to Clipboard>Google Translate>Extra Window>Extra Newlines";
struct Extension
{
std::wstring name;
wchar_t* (*callback)(wchar_t*, const InfoForExtension*);
};
Ui::ExtenWindow ui;
concurrency::reader_writer_lock extenMutex;
std::vector<Extension> extensions;
ExtenWindow* This = nullptr;
bool Load(QString extenName)
{
if (extenName.endsWith(".dll")) extenName.chop(4);
if (extenName.endsWith(".xdll")) extenName.chop(5);
if (!QFile::exists(extenName + ".xdll")) QFile::copy(extenName + ".dll", extenName + ".xdll");
// Extension must export "OnNewSentence"
if (QTextFile(extenName + ".xdll", QIODevice::ReadOnly).readAll().contains("OnNewSentence"))
{
if (HMODULE module = LoadLibraryW(S(extenName + ".xdll").c_str()))
{
if (auto callback = (decltype(Extension::callback))GetProcAddress(module, "OnNewSentence"))
{
std::scoped_lock lock(extenMutex);
extensions.push_back({ S(extenName), callback });
return true;
}
FreeLibrary(module);
}
}
return false;
}
void Unload(int index)
{
std::scoped_lock lock(extenMutex);
FreeLibrary(GetModuleHandleW((extensions.at(index).name + L".xdll").c_str()));
extensions.erase(extensions.begin() + index);
}
void Reorder(QStringList extenNames)
{
std::scoped_lock lock(extenMutex);
std::vector<Extension> extensions;
for (auto extenName : extenNames)
extensions.push_back(*std::find_if(::extensions.begin(), ::extensions.end(), [&](Extension extension) { return extension.name == S(extenName); }));
::extensions = extensions;
}
void Sync()
{
ui.extenList->clear();
QTextFile extenSaveFile(EXTEN_SAVE_FILE, QIODevice::WriteOnly | QIODevice::Truncate);
concurrency::reader_writer_lock::scoped_lock_read readLock(extenMutex);
for (auto extension : extensions)
{
ui.extenList->addItem(S(extension.name));
extenSaveFile.write((S(extension.name) + ">").toUtf8());
}
}
void Add(QFileInfo extenFile)
{
if (extenFile.suffix() == "dll" || extenFile.suffix() == "xdll")
{
if (extenFile.absolutePath() != QDir::currentPath())
{
if (QFile::exists(extenFile.fileName()) && QMessageBox::question(This, EXTENSIONS, CONFIRM_EXTENSION_OVERWRITE) == QMessageBox::Yes) QFile::remove(extenFile.fileName());
if (!QFile::copy(extenFile.absoluteFilePath(), extenFile.fileName())) QMessageBox::warning(This, EXTENSIONS, EXTENSION_WRITE_ERROR);
}
if (Load(extenFile.fileName())) return Sync();
}
QMessageBox::information(This, EXTENSIONS, QString(INVALID_EXTENSION).arg(extenFile.fileName()));
}
void Delete()
{
if (ui.extenList->currentItem())
{
Unload(ui.extenList->currentIndex().row());
Sync();
}
}
void ContextMenu(QPoint point)
{
QAction addExtension(ADD_EXTENSION), removeExtension(REMOVE_EXTENSION);
if (auto action = QMenu::exec({ &addExtension, &removeExtension }, ui.extenList->mapToGlobal(point), nullptr, This))
if (action == &removeExtension) Delete();
else if (QString extenFile = QFileDialog::getOpenFileName(This, ADD_EXTENSION, ".", EXTENSIONS + QString(" (*.xdll);;Libraries (*.dll)")); !extenFile.isEmpty()) Add(extenFile);
}
}
bool DispatchSentenceToExtensions(std::wstring& sentence, const InfoForExtension* sentenceInfo)
{
wchar_t* sentenceBuffer = (wchar_t*)HeapAlloc(GetProcessHeap(), HEAP_GENERATE_EXCEPTIONS, (sentence.size() + 1) * sizeof(wchar_t));
wcscpy_s(sentenceBuffer, sentence.size() + 1, sentence.c_str());
concurrency::reader_writer_lock::scoped_lock_read readLock(extenMutex);
for (const auto& extension : extensions)
if (!*(sentenceBuffer = extension.callback(sentenceBuffer, sentenceInfo))) break;
sentence = sentenceBuffer;
HeapFree(GetProcessHeap(), 0, sentenceBuffer);
return !sentence.empty();
}
void CleanupExtensions()
{
std::scoped_lock lock(extenMutex);
for (auto extension : extensions) FreeLibrary(GetModuleHandleW((extension.name + L".xdll").c_str()));
extensions.clear();
}
ExtenWindow::ExtenWindow(QWidget* parent) : QMainWindow(parent, Qt::WindowCloseButtonHint)
{
This = this;
ui.setupUi(this);
ui.vboxLayout->addWidget(new QLabel(EXTEN_WINDOW_INSTRUCTIONS, this));
setWindowTitle(EXTENSIONS);
connect(ui.extenList, &QListWidget::customContextMenuRequested, ContextMenu);
ui.extenList->installEventFilter(this);
if (!QFile::exists(EXTEN_SAVE_FILE)) QTextFile(EXTEN_SAVE_FILE, QIODevice::WriteOnly).write(DEFAULT_EXTENSIONS);
for (auto extenName : QString(QTextFile(EXTEN_SAVE_FILE, QIODevice::ReadOnly).readAll()).split(">")) Load(extenName);
Sync();
}
bool ExtenWindow::eventFilter(QObject* target, QEvent* event)
{
// https://stackoverflow.com/questions/1224432/how-do-i-respond-to-an-internal-drag-and-drop-operation-using-a-qlistwidget/1528215
if (event->type() == QEvent::ChildRemoved)
{
QStringList extenNames;
for (int i = 0; i < ui.extenList->count(); ++i) extenNames.push_back(ui.extenList->item(i)->text());
Reorder(extenNames);
Sync();
}
return false;
}
void ExtenWindow::keyPressEvent(QKeyEvent* event)
{
if (event->key() == Qt::Key_Delete) Delete();
}
void ExtenWindow::dragEnterEvent(QDragEnterEvent* event)
{
event->acceptProposedAction();
}
void ExtenWindow::dropEvent(QDropEvent* event)
{
for (auto file : event->mimeData()->urls()) Add(file.toLocalFile());
}

24
GUI/extenwindow.h Normal file
View File

@ -0,0 +1,24 @@
#pragma once
#include "qtcommon.h"
struct InfoForExtension
{
const char* name;
int64_t value;
};
bool DispatchSentenceToExtensions(std::wstring& sentence, const InfoForExtension* sentenceInfo);
void CleanupExtensions(); // must call this before exiting the program, only way to uphold guarantee that DllMain and OnNewSentence won't be called concurrently
class ExtenWindow : public QMainWindow
{
public:
explicit ExtenWindow(QWidget* parent = nullptr);
private:
bool eventFilter(QObject* target, QEvent* event) override;
void keyPressEvent(QKeyEvent* event) override;
void dragEnterEvent(QDragEnterEvent* event) override;
void dropEvent(QDropEvent* event) override;
};

36
GUI/extenwindow.ui Normal file
View File

@ -0,0 +1,36 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>ExtenWindow</class>
<widget class="QMainWindow" name="ExtenWindow">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>400</width>
<height>300</height>
</rect>
</property>
<property name="acceptDrops">
<bool>true</bool>
</property>
<widget class="QWidget">
<layout class="QVBoxLayout">
<item>
<widget class="QListWidget" name="extenList">
<property name="contextMenuPolicy">
<enum>Qt::CustomContextMenu</enum>
</property>
<property name="dragDropMode">
<enum>QAbstractItemView::InternalMove</enum>
</property>
<property name="defaultDropAction">
<enum>Qt::MoveAction</enum>
</property>
</widget>
</item>
</layout>
</widget>
</widget>
<resources/>
<connections/>
</ui>

31
GUI/main.cpp Normal file
View File

@ -0,0 +1,31 @@
#include "mainwindow.h"
#include "module.h"
#include <winhttp.h>
extern const wchar_t* UPDATE_AVAILABLE;
int main(int argc, char *argv[])
{
std::thread([]
{
if (!*VERSION) return;
using InternetHandle = AutoHandle<Functor<WinHttpCloseHandle>>;
// Queries GitHub releases API https://developer.github.com/v3/repos/releases/ and checks the last release tag to check if it's the same
if (InternetHandle internet = WinHttpOpen(L"Mozilla/5.0 Textractor", WINHTTP_ACCESS_TYPE_DEFAULT_PROXY, NULL, NULL, 0))
if (InternetHandle connection = WinHttpConnect(internet, L"api.github.com", INTERNET_DEFAULT_HTTPS_PORT, 0))
if (InternetHandle request = WinHttpOpenRequest(connection, L"GET", L"/repos/Artikash/Textractor/releases", NULL, NULL, NULL, WINHTTP_FLAG_SECURE))
if (WinHttpSendRequest(request, NULL, 0, NULL, 0, 0, NULL))
{
char buffer[1000] = {};
WinHttpReceiveResponse(request, NULL);
WinHttpReadData(request, buffer, 1000, DUMMY);
if (abs(strstr(buffer, "/tag/") - strstr(buffer, VERSION)) > 10) TEXTRACTOR_MESSAGE(UPDATE_AVAILABLE);
}
}).detach();
QDir::setCurrent(QFileInfo(S(GetModuleFilename().value())).absolutePath());
QApplication app(argc, argv);
app.setFont(QFont("MS Shell Dlg 2", 10));
return MainWindow().show(), app.exec();
}

704
GUI/mainwindow.cpp Normal file
View File

@ -0,0 +1,704 @@
#include "mainwindow.h"
#include "ui_mainwindow.h"
#include "defs.h"
#include "module.h"
#include "extenwindow.h"
#include "../host/host.h"
#include "../host/hookcode.h"
#include "attachprocessdialog.h"
#include <shellapi.h>
#include <process.h>
#include <QRegularExpression>
#include <QStringListModel>
#include <QScrollBar>
#include <QMenu>
#include <QDialogButtonBox>
#include <QFileDialog>
#include <QFontDialog>
#include <QHash>
extern const char* ATTACH;
extern const char* LAUNCH;
extern const char* CONFIG;
extern const char* DETACH;
extern const char* FORGET;
extern const char* ADD_HOOK;
extern const char* REMOVE_HOOKS;
extern const char* SAVE_HOOKS;
extern const char* SEARCH_FOR_HOOKS;
extern const char* SETTINGS;
extern const char* EXTENSIONS;
extern const char* FONT;
extern const char* SELECT_PROCESS;
extern const char* SELECT_PROCESS_INFO;
extern const char* FROM_COMPUTER;
extern const char* PROCESSES;
extern const char* CODE_INFODUMP;
extern const char* FAILED_TO_CREATE_CONFIG_FILE;
extern const char* HOOK_SEARCH_UNSTABLE_WARNING;
extern const char* HOOK_SEARCH_STARTING_VIEW_CONSOLE;
extern const char* SEARCH_CJK;
extern const char* SEARCH_PATTERN;
extern const char* SEARCH_DURATION;
extern const char* SEARCH_MODULE;
extern const char* PATTERN_OFFSET;
extern const char* MIN_ADDRESS;
extern const char* MAX_ADDRESS;
extern const char* STRING_OFFSET;
extern const char* MAX_HOOK_SEARCH_RECORDS;
extern const char* HOOK_SEARCH_FILTER;
extern const char* SEARCH_FOR_TEXT;
extern const char* TEXT;
extern const char* CODEPAGE;
extern const char* START_HOOK_SEARCH;
extern const char* SAVE_SEARCH_RESULTS;
extern const char* TEXT_FILES;
extern const char* DOUBLE_CLICK_TO_REMOVE_HOOK;
extern const char* SAVE_SETTINGS;
extern const char* USE_JP_LOCALE;
extern const char* FILTER_REPETITION;
extern const char* AUTO_ATTACH;
extern const char* ATTACH_SAVED_ONLY;
extern const char* SHOW_SYSTEM_PROCESSES;
extern const char* DEFAULT_CODEPAGE;
extern const char* FLUSH_DELAY;
extern const char* MAX_BUFFER_SIZE;
extern const char* MAX_HISTORY_SIZE;
extern const char* CONFIG_JP_LOCALE;
extern const wchar_t* ABOUT;
extern const wchar_t* CL_OPTIONS;
extern const wchar_t* LAUNCH_FAILED;
extern const wchar_t* INVALID_CODE;
namespace
{
constexpr auto HOOK_SAVE_FILE = u8"SavedHooks.txt";
constexpr auto GAME_SAVE_FILE = u8"SavedGames.txt";
enum LaunchWithJapaneseLocale { PROMPT, ALWAYS, NEVER };
Ui::MainWindow ui;
std::atomic<DWORD> selectedProcessId = 0;
ExtenWindow* extenWindow = nullptr;
std::unordered_set<DWORD> alreadyAttached;
bool autoAttach = false, autoAttachSavedOnly = true;
bool showSystemProcesses = false;
uint64_t savedThreadCtx = 0, savedThreadCtx2 = 0;
wchar_t savedThreadCode[1000] = {};
TextThread* current = nullptr;
MainWindow* This = nullptr;
void FindHooks();
QString TextThreadString(TextThread& thread)
{
return QString("%1:%2:%3:%4:%5: %6").arg(
QString::number(thread.handle, 16),
QString::number(thread.tp.processId, 16),
QString::number(thread.tp.addr, 16),
QString::number(thread.tp.ctx, 16),
QString::number(thread.tp.ctx2, 16)
).toUpper().arg(S(thread.name));
}
ThreadParam ParseTextThreadString(QString ttString)
{
auto threadParam = ttString.splitRef(":");
return { threadParam[1].toUInt(nullptr, 16), threadParam[2].toULongLong(nullptr, 16), threadParam[3].toULongLong(nullptr, 16), threadParam[4].toULongLong(nullptr, 16) };
}
std::array<InfoForExtension, 20> GetSentenceInfo(TextThread& thread)
{
void (*AddText)(int64_t, const wchar_t*) = [](int64_t number, const wchar_t* text)
{
QMetaObject::invokeMethod(This, [number, text = std::wstring(text)] { if (TextThread* thread = Host::GetThread(number)) thread->Push(text.c_str()); });
};
void (*AddSentence)(int64_t, const wchar_t*) = [](int64_t number, const wchar_t* sentence)
{
// pointer from Host::GetThread may not stay valid unless on main thread
QMetaObject::invokeMethod(This, [number, sentence = std::wstring(sentence)] { if (TextThread* thread = Host::GetThread(number)) thread->AddSentence(sentence); });
};
DWORD (*GetSelectedProcessId)() = [] { return selectedProcessId.load(); };
return
{ {
{ "current select", &thread == current },
{ "text number", thread.handle },
{ "process id", thread.tp.processId },
{ "hook address", (int64_t)thread.tp.addr },
{ "text handle", thread.handle },
{ "text name", (int64_t)thread.name.c_str() },
{ "add sentence", (int64_t)AddSentence },
{ "add text", (int64_t)AddText },
{ "get selected process id", (int64_t)GetSelectedProcessId },
{ "void (*AddSentence)(int64_t number, const wchar_t* sentence)", (int64_t)AddSentence },
{ "void (*AddText)(int64_t number, const wchar_t* text)", (int64_t)AddText },
{ "DWORD (*GetSelectedProcessId)()", (int64_t)GetSelectedProcessId },
{ nullptr, 0 } // nullptr marks end of info array
} };
}
void AttachSavedProcesses()
{
std::unordered_set<std::wstring> attachTargets;
if (autoAttach)
for (auto process : QString(QTextFile(GAME_SAVE_FILE, QIODevice::ReadOnly).readAll()).split("\n", QString::SkipEmptyParts))
attachTargets.insert(S(process));
if (autoAttachSavedOnly)
for (auto process : QString(QTextFile(HOOK_SAVE_FILE, QIODevice::ReadOnly).readAll()).split("\n", QString::SkipEmptyParts))
attachTargets.insert(S(process.split(" , ")[0]));
if (!attachTargets.empty())
for (auto [processId, processName] : GetAllProcesses())
if (processName && attachTargets.count(processName.value()) > 0 && alreadyAttached.count(processId) == 0) Host::InjectProcess(processId);
}
std::optional<std::wstring> UserSelectedProcess()
{
QStringList savedProcesses = QString::fromUtf8(QTextFile(GAME_SAVE_FILE, QIODevice::ReadOnly).readAll()).split("\n", QString::SkipEmptyParts);
std::reverse(savedProcesses.begin(), savedProcesses.end());
savedProcesses.removeDuplicates();
savedProcesses.insert(1, FROM_COMPUTER);
QString process = QInputDialog::getItem(This, SELECT_PROCESS, SELECT_PROCESS_INFO, savedProcesses, 0, true, &ok, Qt::WindowCloseButtonHint);
if (process == FROM_COMPUTER) process = QDir::toNativeSeparators(QFileDialog::getOpenFileName(This, SELECT_PROCESS, "/", PROCESSES));
if (ok && process.contains('\\')) return S(process);
return {};
}
void ViewThread(int index)
{
ui.ttCombo->setCurrentIndex(index);
ui.textOutput->setPlainText(sanitize(S((current = &Host::GetThread(ParseTextThreadString(ui.ttCombo->itemText(index))))->storage->c_str())));
ui.textOutput->moveCursor(QTextCursor::End);
}
void AttachProcess()
{
QMultiHash<QString, DWORD> processesMap;
std::vector<std::pair<QString, HICON>> processIcons;
for (auto [processId, processName] : GetAllProcesses())
{
if (processName && (showSystemProcesses || processName->find(L":\\Windows\\") == std::string::npos))
{
QString fileName = QFileInfo(S(processName.value())).fileName();
if (!processesMap.contains(fileName))
{
HICON bigIcon, smallIcon;
ExtractIconExW(processName->c_str(), 0, &bigIcon, &smallIcon, 1);
processIcons.push_back({ fileName, bigIcon ? bigIcon : smallIcon });
}
processesMap.insert(fileName, processId);
}
}
std::sort(processIcons.begin(), processIcons.end(), [](auto one, auto two) { return QString::compare(one.first, two.first, Qt::CaseInsensitive) < 0; });
AttachProcessDialog attachProcessDialog(This, processIcons);
if (attachProcessDialog.exec())
{
QString process = attachProcessDialog.SelectedProcess();
if (int processId = process.toInt(nullptr, 0)) Host::InjectProcess(processId);
else for (int processId : processesMap.values(process)) Host::InjectProcess(processId);
}
}
void LaunchProcess()
{
std::wstring process;
if (auto selected = UserSelectedProcess()) process = selected.value();
else return;
std::wstring path = std::wstring(process).erase(process.rfind(L'\\'));
PROCESS_INFORMATION info = {};
auto useLocale = Settings().value(CONFIG_JP_LOCALE, PROMPT).toInt();
if (!x64 && (useLocale == ALWAYS || (useLocale == PROMPT && QMessageBox::question(This, SELECT_PROCESS, USE_JP_LOCALE) == QMessageBox::Yes)))
{
if (HMODULE localeEmulator = LoadLibraryW(L"LoaderDll"))
{
// https://github.com/xupefei/Locale-Emulator/blob/aa99dec3b25708e676c90acf5fed9beaac319160/LEProc/LoaderWrapper.cs#L252
struct
{
ULONG AnsiCodePage = SHIFT_JIS;
ULONG OemCodePage = SHIFT_JIS;
ULONG LocaleID = LANG_JAPANESE;
ULONG DefaultCharset = SHIFTJIS_CHARSET;
ULONG HookUiLanguageApi = FALSE;
WCHAR DefaultFaceName[LF_FACESIZE] = {};
TIME_ZONE_INFORMATION Timezone;
ULONG64 Unused = 0;
} LEB;
GetTimeZoneInformation(&LEB.Timezone);
((LONG(__stdcall*)(decltype(&LEB), LPCWSTR appName, LPWSTR commandLine, LPCWSTR currentDir, void*, void*, PROCESS_INFORMATION*, void*, void*, void*, void*))
GetProcAddress(localeEmulator, "LeCreateProcess"))(&LEB, process.c_str(), NULL, path.c_str(), NULL, NULL, &info, NULL, NULL, NULL, NULL);
}
}
if (info.hProcess == NULL)
{
STARTUPINFOW DUMMY = { sizeof(DUMMY) };
CreateProcessW(process.c_str(), NULL, nullptr, nullptr, FALSE, 0, nullptr, path.c_str(), &DUMMY, &info);
}
if (info.hProcess == NULL) return Host::AddConsoleOutput(LAUNCH_FAILED);
Host::InjectProcess(info.dwProcessId);
CloseHandle(info.hProcess);
CloseHandle(info.hThread);
}
void ConfigureProcess()
{
if (auto processName = GetModuleFilename(selectedProcessId)) if (int last = processName->rfind(L'\\') + 1)
{
std::wstring configFile = std::wstring(processName.value()).replace(last, std::string::npos, GAME_CONFIG_FILE);
if (!std::filesystem::exists(configFile)) QTextFile(S(configFile), QFile::WriteOnly).write("see https://github.com/Artikash/Textractor/wiki/Game-configuration-file");
if (std::filesystem::exists(configFile)) _wspawnlp(_P_DETACH, L"notepad", L"notepad", configFile.c_str(), NULL);
else QMessageBox::critical(This, CONFIG, QString(FAILED_TO_CREATE_CONFIG_FILE).arg(S(configFile)));
}
}
void DetachProcess()
{
try { Host::DetachProcess(selectedProcessId); }
catch (std::out_of_range) {}
}
void ForgetProcess()
{
auto processName = GetModuleFilename(selectedProcessId);
if (!processName) processName = UserSelectedProcess();
DetachProcess();
if (!processName) return;
for (auto file : { GAME_SAVE_FILE, HOOK_SAVE_FILE })
{
QStringList lines = QString::fromUtf8(QTextFile(file, QIODevice::ReadOnly).readAll()).split("\n", QString::SkipEmptyParts);
lines.erase(std::remove_if(lines.begin(), lines.end(), [&](const QString& line) { return line.contains(S(processName.value())); }), lines.end());
QTextFile(file, QIODevice::WriteOnly | QIODevice::Truncate).write(lines.join("\n").append("\n").toUtf8());
}
}
void AddHook(QString hook)
{
if (QString hookCode = QInputDialog::getText(This, ADD_HOOK, CODE_INFODUMP, QLineEdit::Normal, hook, &ok, Qt::WindowCloseButtonHint); ok)
if (hookCode.startsWith("S") || hookCode.startsWith("/S")) FindHooks(); // backwards compatibility for old hook search UX
else if (auto hp = HookCode::Parse(S(hookCode))) try { Host::InsertHook(selectedProcessId, hp.value()); } catch (std::out_of_range) {}
else Host::AddConsoleOutput(INVALID_CODE);
}
void AddHook()
{
AddHook("");
}
void RemoveHooks()
{
DWORD processId = selectedProcessId;
std::unordered_map<uint64_t, HookParam> hooks;
for (int i = 0; i < ui.ttCombo->count(); ++i)
{
ThreadParam tp = ParseTextThreadString(ui.ttCombo->itemText(i));
if (tp.processId == selectedProcessId) hooks[tp.addr] = Host::GetThread(tp).hp;
}
auto hookList = new QListWidget(This);
hookList->setWindowFlags(Qt::Window | Qt::WindowCloseButtonHint);
hookList->setAttribute(Qt::WA_DeleteOnClose);
hookList->setMinimumSize({ 300, 50 });
hookList->setWindowTitle(DOUBLE_CLICK_TO_REMOVE_HOOK);
for (auto [address, hp] : hooks) new QListWidgetItem(QString(hp.name) + "@" + QString::number(address, 16), hookList);
QObject::connect(hookList, &QListWidget::itemDoubleClicked, [processId, hookList](QListWidgetItem* item)
{
try
{
Host::RemoveHook(processId, item->text().split("@")[1].toULongLong(nullptr, 16));
delete item;
}
catch (std::out_of_range) { hookList->close(); }
});
hookList->show();
}
void SaveHooks()
{
auto processName = GetModuleFilename(selectedProcessId);
if (!processName) return;
QHash<uint64_t, QString> hookCodes;
for (int i = 0; i < ui.ttCombo->count(); ++i)
{
ThreadParam tp = ParseTextThreadString(ui.ttCombo->itemText(i));
if (tp.processId == selectedProcessId)
{
HookParam hp = Host::GetThread(tp).hp;
if (!(hp.type & HOOK_ENGINE)) hookCodes[tp.addr] = S(HookCode::Generate(hp, tp.processId));
}
}
auto hookInfo = QStringList() << S(processName.value()) << hookCodes.values();
ThreadParam tp = current->tp;
if (tp.processId == selectedProcessId) hookInfo << QString("|%1:%2:%3").arg(tp.ctx).arg(tp.ctx2).arg(S(HookCode::Generate(current->hp, tp.processId)));
QTextFile(HOOK_SAVE_FILE, QIODevice::WriteOnly | QIODevice::Append).write((hookInfo.join(" , ") + "\n").toUtf8());
}
void FindHooks()
{
QMessageBox::information(This, SEARCH_FOR_HOOKS, HOOK_SEARCH_UNSTABLE_WARNING);
DWORD processId = selectedProcessId;
SearchParam sp = {};
sp.codepage = Host::defaultCodepage;
bool searchForText = false, customSettings = false;
QRegularExpression filter(".", QRegularExpression::UseUnicodePropertiesOption | QRegularExpression::DotMatchesEverythingOption);
QDialog dialog(This, Qt::WindowCloseButtonHint);
QFormLayout layout(&dialog);
QCheckBox asianCheck(&dialog);
layout.addRow(SEARCH_CJK, &asianCheck);
QDialogButtonBox confirm(QDialogButtonBox::Ok | QDialogButtonBox::Help | QDialogButtonBox::Retry, &dialog);
layout.addRow(&confirm);
confirm.button(QDialogButtonBox::Ok)->setText(START_HOOK_SEARCH);
confirm.button(QDialogButtonBox::Retry)->setText(SEARCH_FOR_TEXT);
confirm.button(QDialogButtonBox::Help)->setText(SETTINGS);
QObject::connect(&confirm, &QDialogButtonBox::clicked, [&](QAbstractButton* button)
{
if (button == confirm.button(QDialogButtonBox::Retry)) searchForText = true;
if (button == confirm.button(QDialogButtonBox::Help)) customSettings = true;
dialog.accept();
});
dialog.setWindowTitle(SEARCH_FOR_HOOKS);
if (!dialog.exec()) return;
if (searchForText)
{
QDialog dialog(This, Qt::WindowCloseButtonHint);
QFormLayout layout(&dialog);
QLineEdit textEdit(&dialog);
layout.addRow(TEXT, &textEdit);
QSpinBox codepageSpin(&dialog);
codepageSpin.setMaximum(INT_MAX);
codepageSpin.setValue(sp.codepage);
layout.addRow(CODEPAGE, &codepageSpin);
QDialogButtonBox confirm(QDialogButtonBox::Ok);
QObject::connect(&confirm, &QDialogButtonBox::accepted, &dialog, &QDialog::accept);
layout.addRow(&confirm);
if (!dialog.exec()) return;
wcsncpy_s(sp.text, S(textEdit.text()).c_str(), PATTERN_SIZE - 1);
try
{
Host::FindHooks(selectedProcessId, sp);
ViewThread(0);
} catch (std::out_of_range) {}
return;
}
filter.setPattern(asianCheck.isChecked() ? "[\\x{3000}-\\x{a000}]{4,}" : "[\\x{0020}-\\x{1000}]{4,}");
if (customSettings)
{
QDialog dialog(This, Qt::WindowCloseButtonHint);
QFormLayout layout(&dialog);
QLineEdit patternEdit(x64 ? "CC CC 48 89" : "55 8B EC", &dialog);
assert(QByteArray::fromHex(patternEdit.text().toUtf8()) == QByteArray((const char*)sp.pattern, sp.length));
layout.addRow(SEARCH_PATTERN, &patternEdit);
for (auto [value, label] : Array<int&, const char*>{
{ sp.searchTime, SEARCH_DURATION },
{ sp.offset, PATTERN_OFFSET },
{ sp.maxRecords, MAX_HOOK_SEARCH_RECORDS },
{ sp.codepage, CODEPAGE },
})
{
auto spinBox = new QSpinBox(&dialog);
spinBox->setMaximum(INT_MAX);
spinBox->setValue(value);
layout.addRow(label, spinBox);
QObject::connect(spinBox, qOverload<int>(&QSpinBox::valueChanged), [&value](int newValue) { value = newValue; });
}
QLineEdit boundEdit(QFileInfo(S(GetModuleFilename(selectedProcessId).value_or(L""))).fileName(), &dialog);
layout.addRow(SEARCH_MODULE, &boundEdit);
for (auto [value, label] : Array<uintptr_t&, const char*>{
{ sp.minAddress, MIN_ADDRESS },
{ sp.maxAddress, MAX_ADDRESS },
{ sp.padding, STRING_OFFSET },
})
{
auto edit = new QLineEdit(QString::number(value, 16), &dialog);
layout.addRow(label, edit);
QObject::connect(edit, &QLineEdit::textEdited, [&value](QString text) { if (uintptr_t newValue = text.toULongLong(&ok, 16); ok) value = newValue; });
}
QLineEdit filterEdit(filter.pattern(), &dialog);
layout.addRow(HOOK_SEARCH_FILTER, &filterEdit);
QPushButton startButton(START_HOOK_SEARCH, &dialog);
layout.addWidget(&startButton);
QObject::connect(&startButton, &QPushButton::clicked, &dialog, &QDialog::accept);
if (!dialog.exec()) return;
if (patternEdit.text().contains('.'))
{
wcsncpy_s(sp.exportModule, S(patternEdit.text()).c_str(), MAX_MODULE_SIZE - 1);
sp.length = 1;
}
else
{
QByteArray pattern = QByteArray::fromHex(patternEdit.text().replace("??", QString::number(XX, 16)).toUtf8());
memcpy(sp.pattern, pattern.data(), sp.length = min(pattern.size(), PATTERN_SIZE));
}
wcsncpy_s(sp.boundaryModule, S(boundEdit.text()).c_str(), MAX_MODULE_SIZE - 1);
filter.setPattern(filterEdit.text());
if (!filter.isValid()) filter.setPattern(".");
}
else
{
sp.length = 0; // use default
}
filter.optimize();
auto hooks = std::make_shared<QStringList>();
try
{
Host::FindHooks(processId, sp,
[hooks, filter](HookParam hp, std::wstring text) { if (filter.match(S(text)).hasMatch()) *hooks << sanitize(S(HookCode::Generate(hp) + L" => " + text)); });
}
catch (std::out_of_range) { return; }
ViewThread(0);
std::thread([hooks]
{
for (int lastSize = 0; hooks->size() == 0 || hooks->size() != lastSize; Sleep(2000)) lastSize = hooks->size();
QString saveFileName;
QMetaObject::invokeMethod(This, [&]
{
auto hookList = new QListView(This);
hookList->setWindowFlags(Qt::Window | Qt::WindowCloseButtonHint);
hookList->setAttribute(Qt::WA_DeleteOnClose);
hookList->resize({ 750, 300 });
hookList->setWindowTitle(SEARCH_FOR_HOOKS);
if (hooks->size() > 5'000)
{
hookList->setUniformItemSizes(true); // they aren't actually uniform, but this improves performance
hooks->push_back(QString(2000, '-')); // dumb hack: with uniform item sizes, the last item is assumed to be the largest
}
hookList->setModel(new QStringListModel(*hooks, hookList));
QObject::connect(hookList, &QListView::clicked, [](QModelIndex i) { AddHook(i.data().toString().split(" => ")[0]); });
hookList->show();
saveFileName = QFileDialog::getSaveFileName(This, SAVE_SEARCH_RESULTS, "./results.txt", TEXT_FILES);
}, Qt::BlockingQueuedConnection);
if (!saveFileName.isEmpty())
{
QTextFile saveFile(saveFileName, QIODevice::WriteOnly | QIODevice::Truncate);
for (auto hook = hooks->cbegin(); hook != hooks->cend(); ++hook) saveFile.write(hook->toUtf8().append('\n')); // QStringList::begin() makes a copy
}
hooks->clear();
}).detach();
QMessageBox::information(This, SEARCH_FOR_HOOKS, HOOK_SEARCH_STARTING_VIEW_CONSOLE);
}
void OpenSettings()
{
QDialog dialog(This, Qt::WindowCloseButtonHint);
Settings settings(&dialog);
QFormLayout layout(&dialog);
QPushButton saveButton(SAVE_SETTINGS, &dialog);
for (auto [value, label] : Array<bool&, const char*>{
{ TextThread::filterRepetition, FILTER_REPETITION },
{ autoAttach, AUTO_ATTACH },
{ autoAttachSavedOnly, ATTACH_SAVED_ONLY },
{ showSystemProcesses, SHOW_SYSTEM_PROCESSES },
})
{
auto checkBox = new QCheckBox(&dialog);
checkBox->setChecked(value);
layout.addRow(label, checkBox);
QObject::connect(&saveButton, &QPushButton::clicked, [checkBox, label, &settings, &value] { settings.setValue(label, value = checkBox->isChecked()); });
}
for (auto [value, label] : Array<int&, const char*>{
{ TextThread::maxBufferSize, MAX_BUFFER_SIZE },
{ TextThread::flushDelay, FLUSH_DELAY },
{ TextThread::maxHistorySize, MAX_HISTORY_SIZE },
{ Host::defaultCodepage, DEFAULT_CODEPAGE },
})
{
auto spinBox = new QSpinBox(&dialog);
spinBox->setMaximum(INT_MAX);
spinBox->setValue(value);
layout.addRow(label, spinBox);
QObject::connect(&saveButton, &QPushButton::clicked, [spinBox, label, &settings, &value] { settings.setValue(label, value = spinBox->value()); });
}
QComboBox localeCombo(&dialog);
assert(PROMPT == 0 && ALWAYS == 1 && NEVER == 2);
localeCombo.addItems({ { "Prompt", "Always", "Never" } });
localeCombo.setCurrentIndex(settings.value(CONFIG_JP_LOCALE, PROMPT).toInt());
layout.addRow(CONFIG_JP_LOCALE, &localeCombo);
QObject::connect(&localeCombo, qOverload<int>(&QComboBox::activated), [&settings](int i) { settings.setValue(CONFIG_JP_LOCALE, i); });
layout.addWidget(&saveButton);
QObject::connect(&saveButton, &QPushButton::clicked, &dialog, &QDialog::accept);
dialog.setWindowTitle(SETTINGS);
dialog.exec();
}
void Extensions()
{
extenWindow->activateWindow();
extenWindow->showNormal();
}
void SetOutputFont(QString fontString)
{
QFont font = ui.textOutput->font();
font.fromString(fontString);
font.setStyleStrategy(QFont::NoFontMerging);
ui.textOutput->setFont(font);
Settings().setValue(FONT, font.toString());
}
void ProcessConnected(DWORD processId)
{
alreadyAttached.insert(processId);
QString process = S(GetModuleFilename(processId).value_or(L"???"));
QMetaObject::invokeMethod(This, [process, processId]
{
ui.processCombo->addItem(QString::number(processId, 16).toUpper() + ": " + QFileInfo(process).fileName());
});
if (process == "???") return;
// This does add (potentially tons of) duplicates to the file, but as long as I don't perform Ω(N^2) operations it shouldn't be an issue
QTextFile(GAME_SAVE_FILE, QIODevice::WriteOnly | QIODevice::Append).write((process + "\n").toUtf8());
QStringList allProcesses = QString(QTextFile(HOOK_SAVE_FILE, QIODevice::ReadOnly).readAll()).split("\n", QString::SkipEmptyParts);
auto hookList = std::find_if(allProcesses.rbegin(), allProcesses.rend(), [&](QString hookList) { return hookList.contains(process); });
if (hookList != allProcesses.rend())
for (auto hookInfo : hookList->split(" , "))
if (auto hp = HookCode::Parse(S(hookInfo))) Host::InsertHook(processId, hp.value());
else swscanf_s(S(hookInfo).c_str(), L"|%I64d:%I64d:%[^\n]", &savedThreadCtx, &savedThreadCtx2, savedThreadCode, (unsigned)std::size(savedThreadCode));
}
void ProcessDisconnected(DWORD processId)
{
QMetaObject::invokeMethod(This, [processId]
{
ui.processCombo->removeItem(ui.processCombo->findText(QString::number(processId, 16).toUpper() + ":", Qt::MatchStartsWith));
}, Qt::BlockingQueuedConnection);
}
void ThreadAdded(TextThread& thread)
{
std::wstring threadCode = HookCode::Generate(thread.hp, thread.tp.processId);
bool savedMatch = (savedThreadCtx & 0xFFFF) == (thread.tp.ctx & 0xFFFF) && savedThreadCtx2 == thread.tp.ctx2 && savedThreadCode == threadCode;
if (savedMatch)
{
savedThreadCtx = savedThreadCtx2 = savedThreadCode[0] = 0;
current = &thread;
}
QMetaObject::invokeMethod(This, [savedMatch, ttString = TextThreadString(thread) + S(FormatString(L" (%s)", threadCode))]
{
ui.ttCombo->addItem(ttString);
if (savedMatch) ViewThread(ui.ttCombo->count() - 1);
});
}
void ThreadRemoved(TextThread& thread)
{
QMetaObject::invokeMethod(This, [ttString = TextThreadString(thread)]
{
int threadIndex = ui.ttCombo->findText(ttString, Qt::MatchStartsWith);
if (threadIndex == ui.ttCombo->currentIndex()) ViewThread(0);
ui.ttCombo->removeItem(threadIndex);
}, Qt::BlockingQueuedConnection);
}
bool SentenceReceived(TextThread& thread, std::wstring& sentence)
{
for (int i = 0; i < sentence.size(); ++i) if (sentence[i] == '\r' && sentence[i + 1] == '\n') sentence[i] = 0x200b; // for some reason \r appears as newline - no need to double
if (!DispatchSentenceToExtensions(sentence, GetSentenceInfo(thread).data())) return false;
sentence += L'\n';
if (&thread == current) QMetaObject::invokeMethod(This, [sentence = S(sentence)]() mutable
{
sanitize(sentence);
auto scrollbar = ui.textOutput->verticalScrollBar();
bool atBottom = scrollbar->value() + 3 > scrollbar->maximum() || (double)scrollbar->value() / scrollbar->maximum() > 0.975; // arbitrary
QTextCursor cursor(ui.textOutput->document());
cursor.movePosition(QTextCursor::End);
cursor.insertText(sentence);
if (atBottom) scrollbar->setValue(scrollbar->maximum());
});
return true;
}
void OutputContextMenu(QPoint point)
{
std::unique_ptr<QMenu> menu(ui.textOutput->createStandardContextMenu());
menu->addAction(FONT, [] { if (QString font = QFontDialog::getFont(&ok, ui.textOutput->font(), This, FONT).toString(); ok) SetOutputFont(font); });
menu->exec(ui.textOutput->mapToGlobal(point));
}
void CopyUnlessMouseDown()
{
if (!(QApplication::mouseButtons() & Qt::LeftButton)) ui.textOutput->copy();
}
}
MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent)
{
This = this;
ui.setupUi(this);
extenWindow = new ExtenWindow(this);
for (auto [text, slot] : Array<const char*, void(&)()>{
{ ATTACH, AttachProcess },
{ LAUNCH, LaunchProcess },
{ CONFIG, ConfigureProcess },
{ DETACH, DetachProcess },
{ FORGET, ForgetProcess },
{ ADD_HOOK, AddHook },
{ REMOVE_HOOKS, RemoveHooks },
{ SAVE_HOOKS, SaveHooks },
{ SEARCH_FOR_HOOKS, FindHooks },
{ SETTINGS, OpenSettings },
{ EXTENSIONS, Extensions }
})
{
auto button = new QPushButton(text, ui.processFrame);
connect(button, &QPushButton::clicked, slot);
ui.processLayout->addWidget(button);
}
ui.processLayout->addItem(new QSpacerItem(0, 0, QSizePolicy::Minimum, QSizePolicy::Expanding));
connect(ui.processCombo, qOverload<int>(&QComboBox::currentIndexChanged), [] { selectedProcessId = ui.processCombo->currentText().split(":")[0].toULong(nullptr, 16); });
connect(ui.ttCombo, qOverload<int>(&QComboBox::activated), this, ViewThread);
connect(ui.textOutput, &QPlainTextEdit::selectionChanged, this, CopyUnlessMouseDown);
connect(ui.textOutput, &QPlainTextEdit::customContextMenuRequested, this, OutputContextMenu);
Settings settings;
if (settings.contains(WINDOW) && QApplication::screenAt(settings.value(WINDOW).toRect().center())) setGeometry(settings.value(WINDOW).toRect());
SetOutputFont(settings.value(FONT, ui.textOutput->font().toString()).toString());
TextThread::filterRepetition = settings.value(FILTER_REPETITION, TextThread::filterRepetition).toBool();
autoAttach = settings.value(AUTO_ATTACH, autoAttach).toBool();
autoAttachSavedOnly = settings.value(ATTACH_SAVED_ONLY, autoAttachSavedOnly).toBool();
showSystemProcesses = settings.value(SHOW_SYSTEM_PROCESSES, showSystemProcesses).toBool();
TextThread::flushDelay = settings.value(FLUSH_DELAY, TextThread::flushDelay).toInt();
TextThread::maxBufferSize = settings.value(MAX_BUFFER_SIZE, TextThread::maxBufferSize).toInt();
TextThread::maxHistorySize = settings.value(MAX_HISTORY_SIZE, TextThread::maxHistorySize).toInt();
Host::defaultCodepage = settings.value(DEFAULT_CODEPAGE, Host::defaultCodepage).toInt();
Host::Start(ProcessConnected, ProcessDisconnected, ThreadAdded, ThreadRemoved, SentenceReceived);
current = &Host::GetThread(Host::console);
Host::AddConsoleOutput(ABOUT);
AttachConsole(ATTACH_PARENT_PROCESS);
WriteConsoleW(GetStdHandle(STD_OUTPUT_HANDLE), CL_OPTIONS, wcslen(CL_OPTIONS), DUMMY, NULL);
auto processes = GetAllProcesses();
int argc;
std::unique_ptr<LPWSTR[], Functor<LocalFree>> argv(CommandLineToArgvW(GetCommandLineW(), &argc));
for (int i = 0; i < argc; ++i)
if (std::wstring arg = argv[i]; arg[0] == L'/' || arg[0] == L'-')
if (arg[1] == L'p' || arg[1] == L'P')
if (DWORD processId = wcstoul(arg.substr(2).c_str(), nullptr, 0)) Host::InjectProcess(processId);
else for (auto [processId, processName] : processes)
if (processName.value_or(L"").find(L"\\" + arg.substr(2)) != std::string::npos) Host::InjectProcess(processId);
std::thread([] { for (; ; Sleep(10000)) AttachSavedProcesses(); }).detach();
}
MainWindow::~MainWindow()
{
Settings().setValue(WINDOW, geometry());
CleanupExtensions();
SetErrorMode(SEM_NOGPFAULTERRORBOX);
ExitProcess(0);
}
void MainWindow::closeEvent(QCloseEvent*)
{
QApplication::quit(); // Need to do this to kill any windows that might've been made by extensions
}

12
GUI/mainwindow.h Normal file
View File

@ -0,0 +1,12 @@
#pragma once
#include "qtcommon.h"
class MainWindow : public QMainWindow
{
public:
explicit MainWindow(QWidget *parent = nullptr);
~MainWindow();
private:
void closeEvent(QCloseEvent*);
};

103
GUI/mainwindow.ui Normal file
View File

@ -0,0 +1,103 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>MainWindow</class>
<widget class="QMainWindow" name="MainWindow">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>1000</width>
<height>600</height>
</rect>
</property>
<property name="windowTitle">
<string>Textractor</string>
</property>
<property name="styleSheet">
<string notr="true">
QPushButton, QComboBox
{
padding-top: 3px;
padding-bottom: 3px;
padding-right: 5px;
padding-left: 5px;
text-align: left;
}
</string>
</property>
<widget class="QWidget" name="centralWidget">
<layout class="QHBoxLayout">
<item>
<widget class="QFrame" name="processFrame">
<property name="enabled">
<bool>true</bool>
</property>
<layout class="QVBoxLayout" name="processLayout">
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<widget class="QComboBox" name="processCombo">
<property name="editable">
<bool>false</bool>
</property>
<property name="insertPolicy">
<enum>QComboBox::InsertAtBottom</enum>
</property>
<property name="sizeAdjustPolicy">
<enum>QComboBox::AdjustToContents</enum>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<layout class="QVBoxLayout">
<item>
<widget class="QComboBox" name="ttCombo">
<property name="editable">
<bool>false</bool>
</property>
<property name="maxVisibleItems">
<number>50</number>
</property>
</widget>
</item>
<item>
<widget class="QPlainTextEdit" name="textOutput">
<property name="font">
<font>
<family>Arial Unicode MS</family>
<pointsize>13</pointsize>
</font>
</property>
<property name="contextMenuPolicy">
<enum>Qt::CustomContextMenu</enum>
</property>
<property name="undoRedoEnabled">
<bool>false</bool>
</property>
<property name="readOnly">
<bool>true</bool>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
</widget>
<layoutdefault spacing="6" margin="6"/>
<resources/>
<connections/>
</ui>

Binary file not shown.

View File

@ -1,43 +1,52 @@
# ITHVNR
# Textractor
## Compiling
![How it looks](screenshot.png)
Before compiling *ITHVNR*, You should get CMake, [Windows Driver Kit 7.1](http://www.microsoft.com/en-us/download/details.aspx?id=11800), and Visual Studio.
[English](README.md) ● [Español](README_ES.md) ● [简体中文](README_SC.md) ● [Русский](README_RU.md) ● [한국어](README_KR.md) ● [ภาษาไทย](README_TH.md) ● [Français](README_FR.md) ● [Italiano](README_IT.md) ● [日本語](README_JP.md) ● [Bahasa Indonesia](README_ID.md) ● [Português](README_PT.md) ● [Deutsch](README_DE.md)
## Downloads
**Textractor** (a.k.a. NextHooker) is an open-source x86/x64 video game text hooker for Windows 7+ (and Wine) based off of [ITHVNR](https://web.archive.org/web/20160202084144/http://www.hongfire.com/forum/showthread.php/438331-ITHVNR-ITH-with-the-VNR-engine).<br>
Watch the [tutorial video](docs/TUTORIAL.md) for a quick rundown on using it.
Releases of *ITHVNR* can be found [here](https://github.com/mireado/ITHVNR/releases).
## Download
~~mireado build server can be found [here](http://mireado.blog.me).~~
## Overview
*ITHVNR* is an open-source x32~~/x64~~ text hooker for Windows.
Basically, ITHVNR base on [Stomp](http://www.hongfire.com/forum/showthread.php/438331-ITHVNR-ITH-with-the-VNR-engine)'s version.
## Activity Graph
[![Throughput Graph](https://graphs.waffle.io/mireado/ITHVNR/throughput.svg)](https://waffle.io/mireado/ITHVNR/metrics/throughput)
Official stable releases of Textractor can be found [here](https://github.com/Artikash/Textractor/releases).<br>
The last release of ITHVNR can be found [here](https://drive.google.com/open?id=13aHF4uIXWn-3YML_k2YCDWhtGgn5-tnO).<br>
Experimental builds of Textractor (with debug info) from the latest source can be found [here](https://ci.appveyor.com/project/Artikash/textractor/history) in the 'Artifacts' section of each job.
## Features
- Open-source
- Hook text
- Highly extensible and customizable
- Auto hook many game engines (including some not supported by VNR!)
- Hook text using /H "hook" codes (most AGTH codes supported)
- Automatically search for possible hook codes
## License
## Support
Let me know of any bugs, games that Textractor has trouble hooking, feature requests, or other suggestions by posting an issue.<br>
If you have trouble hooking a game, please show me a way to freely download it or gift it to me on [Steam](https://steamcommunity.com/profiles/76561198097566313/).
## Developers
## Extensions
- Copyright (C) 2010-2012 [kaosu](qiupf2000@gmail.com)
- Copyright (C) 2015 [zorkzero](zorkzero@hotmail.com)
- VNR engine making by [jichi](http://sakuradite.com/topic)
- ITH updating by [Andys](http://www.hongfire.com/forum/member/126633-andys)
- ITHVNR new GUI & VNR engine migration by [Stomp](http://www.hongfire.com/forum/member/325894-stomp)
See my [Example Extension project](https://github.com/Artikash/ExampleExtension) to see how to build an extension.<br>
See the extensions folder for examples of what extensions can do.
## Special Thanks
## Contributing
- Everybody adding issues!
- [IJEMIN](https://github.com/IJEMIN)
- [Eguni](https://github.com/Eguni)
All contributions are appreciated! Please email me at akashmozumdar@gmail.com if you have any questions about the codebase.<br>
You should use the standard process of making a pull request (fork, branch, commit changes, make PR from your branch to my master).<br>
Contributing a translation is easy: [text.cpp](text.cpp) contains all of the text strings that you need to translate. Translations of this README or the tutorial video transcript are also welcome.
## Compiling
Before compiling Textractor, you need Qt version 5.13 and Visual Studio with CMake support.
Clone Textractor's source and initialize submodules with `git clone https://github.com/Artikash/Textractor.git` and `git submodule update --init`.
You should then be able to just open the source folder in Visual Studio and build.
## Project Architecture
The host injects texthook into the target process and connects to it via 2 pipe files.
texthook waits for the pipe to be connected, then injects a few instructions into any text outputting functions (e.g. TextOut, GetGlyphOutline) that cause their input to be sent through the pipe.<br>
Additional information about hooks is exchanged via shared memory.<br>
The text that the host receives through the pipe is then processed a little before being dispatched back to the GUI.<br>
Finally, the GUI dispatches the text to extensions before displaying it.
## [Developers](docs/CREDITS.md)

52
README_DE.md Normal file
View File

@ -0,0 +1,52 @@
# Textractor
![Wie es aussieht](screenshot.png)
[English](README.md) ● [Español](README_ES.md) ● [简体中文](README_SC.md) ● [Русский](README_RU.md) ● [한국어](README_KR.md) ● [ภาษาไทย](README_TH.md) ● [Français](README_FR.md) ● [Italiano](README_IT.md) ● [日本語](README_JP.md) ● [Bahasa Indonesia](README_ID.md) ● [Português](README_PT.md) ● [Deutsch](README_DE.md)
**Textractor** (a.b.a. NextHooker) ist ein open-source x86/x64 Video spiel Text hooker für Windows 7+ (und Wine) basierend auf [ITHVNR](https://web.archive.org/web/20160202084144/http://www.hongfire.com/forum/showthread.php/438331-ITHVNR-ITH-with-the-VNR-engine).<br>
Schau das [Tutorial Video](docs/TUTORIAL.md) (auf Englisch) an für einen schnellen Überblick wie du Textractor verwendest.
## Download
Der offizielle Release ist [hier](https://github.com/Artikash/Textractor/releases) zu finden.<br>
Der letzte Release von ITHVNR ist [hier](https://drive.google.com/open?id=13aHF4uIXWn-3YML_k2YCDWhtGgn5-tnO).<br>
Der experimentelle Release von Textractor (mit debug Informationen) ist [hier](https://ci.appveyor.com/project/Artikash/textractor/history) in der 'Artifacts'
Kategorie des jeweiligen Jobs.
## Features
- Modular und Anpassbar
- Automatischen 'hooken' von mehreren Engines (einige davon welche keine VNR Support haben!)
- Text 'hooken' mithilfe von /H "hook" Codes (die meisten AGTH codes funktionieren)
- Automatische suche nach funktionierenden Hook's
## Support
Wenn ihr irgendwelche Fehler, Spiele bei denen Textractor nicht funktioniert, oder Fragen/Anmerkungen habt lasst es mich bitte wissen.<br>
Falls ihr Probleme mit einem Spiel habt, schickt mir einen kostenlosen download Link von dem Spiel oder schenkt es mir auf [Steam](https://steamcommunity.com/profiles/76561198097566313/).
## Erweiterungen
Siehe [Example Extension project](https://github.com/Artikash/ExampleExtension) für Anleitungen, wie man eine Erweiterung erstellt.<br>
Im 'Extensions' Ordner sind Beispiele für Erweiterungen.
## Unterstützen
Ich bin dankbar für alle Unterstützungen! Schickt mir eine E-Mail an akashmozumdar@gmail.com falls ihr Fragen zur Codebasis habt.<br>
Verwendet bitte als Standard eine pull Request (fork, branch, commit) zum Master Release.<br>
Zu Übersetzungen beizutragen ist einfach: [text.cpp](text.cpp) enthält alle text Strings welche übersetzt werden sollen. Übersetzungen der README oder des Tutorial Video Transkripts sind ebenfalls willkommen.
## Compiling
Zum Compilen braucht ihr Qt Version 5.13 und Visual Studio mit CMake Unterstützung.
Erstellt einen Clone vom Quellcode und initialisiert die submodule mit 'git clone https://github.com/Artikash/Textractor.git' und 'git submodule update --init'.
Ihr solltet danach in der Lage sein, den Quellordner in Visual Studio zu öffnen und anzufangen.
## Projekt Architektur
Der Host injiziert texthooks in den ziel Prozess und verbindet ihn mit 2 Pipe Dateien.
Texthook wartet auf die Pipe und injiziert Instruktionen für den Text Output (z.b. TextOut, GetGlyphOutline) welche durch die Pipe gesendet werden.<br>
Weitere Informationen werden durch geteilten Speicher ausgetauscht.<br>
Der Text, welchen der Host durch die Pipe erhält, wird dann verarbeitet, bevor er wieder an die GUI gesendet wird.<br>
Zu guter Letzt, sendet die GUI den Text an die Erweiterung, welche einen lesbaren Output anzeigt.
## [Entwickler](docs/CREDITS.md)

24
README_ES.md Normal file
View File

@ -0,0 +1,24 @@
# Textractor
[English](README.md) ● [Español](README_ES.md) ● [简体中文](README_SC.md) ● [Русский](README_RU.md) ● [한국어](README_KR.md) ● [ภาษาไทย](README_TH.md) ● [Français](README_FR.md) ● [Italiano](README_IT.md) ● [日本語](README_JP.md) ● [Bahasa Indonesia](README_ID.md) ● [Português](README_PT.md) ● [Deutsch](README_DE.md)
## [Video tutorial](docs/TUTORIAL.md)
## Descripción general
**Textractor** (también conocido como NextHooker) es un extractor y traductor de texto de código abierto x86/x64 para Windows/Wine bassado en [ITHVNR](https://web.archive.org/web/20160202084144/http://www.hongfire.com/forum/showthread.php/438331-ITHVNR-ITH-with-the-VNR-engine).<br>
![Cómo se ve](screenshot.png)
## Descarga
Textractor se puede descargar [acá](https://github.com/Artikash/Textractor/releases), incluye versiones anteriores.
Las antiguas versiones de ITHVNR se pueden descargar [acá](https://github.com/mireado/ITHVNR/releases).
## Características
- Totalmente personalizable
- Extrae y traduce el texto en muchos motores de juegos y novelas visuales (¡Incluye juegos que no funcionan en VNR/Visual Novel Reader!)
- Extrae el texto usando /H "hook" codes o conocidos también como H-codes (los códigos AGTH también funcionan)
- Extrae el texto directamente usando el /R "read" code

51
README_FR.md Normal file
View File

@ -0,0 +1,51 @@
# Textractor
![À quoi ça ressemble](screenshot.png)
[English](README.md) ● [Español](README_ES.md) ● [简体中文](README_SC.md) ● [Русский](README_RU.md) ● [한국어](README_KR.md) ● [ภาษาไทย](README_TH.md) ● [Français](README_FR.md) ● [Italiano](README_IT.md) ● [日本語](README_JP.md) ● [Bahasa Indonesia](README_ID.md) ● [Português](README_PT.md) ● [Deutsch](README_DE.md)
**Textractor** (a.k.a. NextHooker) est un traducteur de jeux-videos basé surtout sur du texte en open source x86/x64 pour Windows/Wine [ITHVNR](https://web.archive.org/web/20160202084144/http://www.hongfire.com/forum/showthread.php/438331-ITHVNR-ITH-with-the-VNR-engine).<br>
Regarde le [tutorial video](docs/TUTORIAL.md) pour un aperçu rapide de son utilisation.
## Téléchargement
Les versions de Textractor peuvent être trouvées[here](https://github.com/Artikash/Textractor/releases).<br>
Le denière version de THVNR peut etre trouvé [here](https://drive.google.com/open?id=13aHF4uIXWn-3YML_k2YCDWhtGgn5-tnO).
## Nouveautés
- Hautement extensible et personnalisable
- Accrochage automatique de nombreux moteurs de jeu (dont certains non pris en charge par VNR!)
- Les textes de hook qui utilisent des /H "hook" codes (la plupart des codes AGTH pris en charge)
- Extraire directement le texte à l'aide des codes /R "lire"
## Support
Veuillez me signaler les bugs, les jeux dont Textractor a du mal à accrocher, les demandes de fonctionnalités ou d'autres suggestions. <br>
Si vous rencontrez des difficultés pour accrocher un jeu, veuillez m'envoyer un e-mail à un endroit où je peux le télécharger librement/gratuitement ou me le faire cadeau sur [Steam](https://steamcommunity.com/profiles/76561198097566313/).
## Extensions
Regarde mes [Example Extension project](https://github.com/Artikash/ExampleExtension) pour voir comment créer un extension.<br>
Voir le dossier des extensions pour des exemples de ce que les extensions peuvent faire.
## Contribution
Toutes les contributions sont appréciées! Veuillez m'envoyer un e-mail À akashmozumdar@gmail.com si vous avez des questions sur la base de code. <br>
Vous devez utiliser le processus standard de création d'une demande d'extraction (fork, branch, commit changes, make PR from your branch to my master). <br>
Contribuer à une traduction est simple: il suffit de traduire les chaînes dans [text.cpp](text.cpp) ainsi que ce fichier README.
## Compilation
Avant de compiler *Textractor*, vous devriez obtenir Visual Studio avec prise en charge de CMake, ainsi que Qt version 5.13 <br>
Vous devriez ensuite pouvoir simplement ouvrir le dossier dans Visual Studio et faire le build. Exécutez Textractor.exe.
## Architecture du projet
L'hôte (voir GUI / dossier hôte) injecte texthook.dll (créé à partir du dossier texthook) dans le processus cible et s'y connecte via 2 fichiers pipe. <br>
L'hôte écrit dans hostPipe, texthook écrit dans hookPipe. <br>
Texthook attend que le canal soit connecté, puis injecte quelques instructions dans toutes les fonctions de sortie de texte (par exemple TextOut, GetGlyphOutline) qui provoquent l'envoi de leur entrée via le canal. <br>
Des informations supplémentaires sur les hooks sont échangées via la mémoire partagée. <br>
Le texte que l'hôte reçoit via le canal est ensuite traité un peu avant d'être renvoyé à l'interface graphique. <br>
Enfin, l'interface graphique envoie le texte aux extensions avant de l'afficher.
## [Développeurs](docs/CREDITS.md)

53
README_ID.md Normal file
View File

@ -0,0 +1,53 @@
# Textractor
![How it looks](screenshot.png)
[English](README.md) ● [Español](README_ES.md) ● [简体中文](README_SC.md) ● [Русский](README_RU.md) ● [한국어](README_KR.md) ● [ภาษาไทย](README_TH.md) ● [Français](README_FR.md) ● [Italiano](README_IT.md) ● [日本語](README_JP.md) ● [Bahasa Indonesia](README_ID.md) ● [Português](README_PT.md) ● [Deutsch](README_DE.md)
**Textractor** (a.k.a NextHooker) adalah teks hooker video game untuk Windows/Wine x86/x64 berbasis open-source yang didasari oleh [ITHVNR](https://web.archive.org/web/20160202084144/http://www.hongfire.com/forum/showthread.php/438331-ITHVNR-ITH-with-the-VNR-engine).<br>
Lihat [video tutorial](docs/TUTORIAL.md) untuk mengetahui bagaimana cara menggunakannya.
## Pengunduhan
Rilisan Textractor dapat diunduh [disini](https://github.com/Artikash/Textractor/releases).<br>
Rilisan Terakhir ITHVNR dapat diunduh [disini](https://drive.google.com/open?id=13aHF4uIXWn-3YML_k2YCDWhtGgn5-tnO).
## Fitur
- Sangat Ekstensibel
- Tempel otomatis banyak engine game (termasuk beberapa yang tidak didukung oleh VNR)
- Hook teks menggunakan "hook" /H (mendukung semua kode AGTH)
- Mengutip teks secara langsung menggunakan kode /R "read"
## Dukungan
Tolong beritahu saya jika kamu menemukan kutu, game yang tidak dapat di tempel oleh Textractor, permintaan fitur, atau usulan lain.<br>
Jika kamu memiliki masalah dalam menempelkan kedalam game tolong email saya link agar saya dapat mengunduh game tersebut, atau hadiahkan game tersebut di [Steam](https://steamcommunity.com/profiles/76561198097566313/).
## Ekstensi
Lihat [project sampel ekstensi saya](https://github.com/Artikash/ExampleExtension) untuk melihat bagaimana cara membuat ekstensi.<br>
Lihat ekstensi folder untuk melihat sampel ekstensi.
## Kontribusi
Seluruh kontribusi diapresiasi! Tolong email saya di akashmozumdar@gmail.com jika kamu memiliki pertanyaan mengenai kode dasar nya.<br>
Kamu harus menggunakan proses standar dalam membuat permintaan pull(fork, cabang, perubahan commit, membuat PR dari cabang kamu ke master saya).<br>
Berkontribusi dalam penerjemahan dapat dilakukan dengan mudah : cukup terjemahkan string dari [text.cpp](text.cpp) lalu terjemahkan README ini.
## Mengcompile
Sebelum melakukan proses compile *Textractor*, kamu harus memiliki Visual Studio dengan dukungan Cmake, juga dengan Qt version 5.13<br>
Lalu kamu dapat membuka folder di Visual Studio, dan build. Jalankan Textractor.exe.
## Arsitektur Project
Host (lihat folder host) menyuntikan texthook.dll (dibuat dari folder texthook) kedalam target proses dan disambungkan lewat 2 file pipe.<br>
Host menulis ke hostPipe, texthook menulis ke hookPipe.<br>
texthook menunggu pipe tersambung, lalu menyuntikan beberapa instruksi ke teks yang menghasilkan fungsi (contoh: TextOut, GetGlyphOutline) yang membuat input dikirim melewati pipa.<br>
Informasi tambahan tentang hook dipindahkan melewati shared memory.<br>
Teks yang diterima host melewati pipe lalu diproses lagi sebelum dikembalikan ke GUI.<br>
Dan pada akhirnya, GUI melepas teks ke ekstensi sebelum menampilkan teks.
## [Pengembang](docs/CREDITS.md)

52
README_IT.md Normal file
View File

@ -0,0 +1,52 @@
# Textractor
![Come si vede](screenshot.png)
[English](README.md) ● [Español](README_ES.md) ● [简体中文](README_SC.md) ● [Русский](README_RU.md) ● [한국어](README_KR.md) ● [ภาษาไทย](README_TH.md) ● [Français](README_FR.md) ● [Italiano](README_IT.md) ● [日本語](README_JP.md) ● [Bahasa Indonesia](README_ID.md) ● [Português](README_PT.md) ● [Deutsch](README_DE.md)
**Textractor** (a.k.a. NextHooker) è un agganciatore di testi di videogiochi open-source per Windows/Wine basato su[ITHVNR](https://web.archive.org/web/20160202084144/http://www.hongfire.com/forum/showthread.php/438331-ITHVNR-ITH-with-the-VNR-engine).<br>
Guarda il [video tutorial](docs/TUTORIAL.md) per una sintesi veloce sul suo utilizzo.
## Scarica
Le uscite di Textractor possono essere trovate [qui](https://github.com/Artikash/Textractor/releases).<br>
L'ultima uscita di ITHVNR può essere trovata [qui](https://drive.google.com/open?id=13aHF4uIXWn-3YML_k2YCDWhtGgn5-tnO).
## Caratteristiche
- Altamente estensibile e personalizzabile
- Aggancia automaticamente molti engine di gioco (inclusi alcuni non supportati da VNR!)
- Aggancia il testo utilizzando codici /H "hook" (molti codici AGTH supportati)
- Estrae il testo direttamente usando codici /R "read"
## Supporto
Fatemi sapere su qualunque bug, giochi che Textractor ha problemi nell'agganciare, richieste future, o altri suggerimenti.<br>
Se avete dei problemi nel agganciare un gioco vi prego di inviarmi via email un sito dove posso scaricarlo, o regalatemelo su [Steam](https://steamcommunity.com/profiles/76561198097566313/).
## Estenzioni
Guardate il mio [Progetto Example Extension](https://github.com/Artikash/ExampleExtension) per vedere come costruire un estenzione..<br>
Guardate la cartella delle estenzioni per esempi di cosa possono fare le estenzioni.
## Contributi
Tutti i contributi sono apprezzati! Inviatemi un email a akashmozumdar@gmail.com se avete delle domande sul codebase.<br>
Dovreste usare il processo standard di creare una pull request (fork, branch, commit changes, crea PR dal vostro ramo al mio master).<br>
Contribuire alla traduzione è semplice: traduci le stringhe in [text.cpp](text.cpp) cosi come questo README.
## Compiling
Prima di compilare *Textractor*, dovresti ottenere Visual Studio con supporto CMAKE, cosi come Qt versione 5.13<br>
Dovresti essere in grado di aprire la cartella in Visual Studio, e costruire. Avvia Textractor.exe
## Architettura del progetto
L'host (guarda la cartella host) innietta texthook.dll (creato dalla cartella texthook) nel processo e lo connette attraverso due file pipe.<br>
L'host scrive a hostPipe, texthook scrive a hookPipe.<br>
Texthook aspetta per il pipe di essere connesso, poi innietta alcune istruzione in qualunque funzione di immissione del testo (es. TextOut, GetGlyphOutline) che causa il loro input di essere inviato attraverso il pipe.<br>
Informazioni aggiuntive sui ganci soo scambiati attraverso la memorio condivisa.<br>
Il testo che l'host riceve attraverso il pipe è poi processato un poco prima di essere rinviato alla GUI.<br>
Infine, la GUI dispone il testo alle estenzioni prima di mostrarle.
## [Sviluppatori](docs/CREDITS.md)

17
README_JP.md Normal file
View File

@ -0,0 +1,17 @@
# Textractor
[English](README.md) ● [Español](README_ES.md) ● [简体中文](README_SC.md) ● [Русский](README_RU.md) ● [한국어](README_KR.md) ● [ภาษาไทย](README_TH.md) ● [Français](README_FR.md) ● [Italiano](README_IT.md) ● [日本語](README_JP.md) ● [Bahasa Indonesia](README_ID.md) ● [Português](README_PT.md) ● [Deutsch](README_DE.md)
**Textractor** はビジュアルノベル文字抽出プログラム。
![How it looks](screenshot.png)
## ダウンロード
[こっち](https://github.com/Artikash/Textractor/releases)
## 投稿
日本語で不具合報告・機能リクエスト・プルリクエストも読むことが出来ますので、どうぞ日本語で投稿してください。私の方へメールして頂いても結構です。
ただしこちらからの返信に関しては、一言二言程度の短い文章しか書くことが出来ないと思われますので、その点はご了承下さい。

50
README_KR.md Normal file
View File

@ -0,0 +1,50 @@
# Textractor
![How it looks](screenshot.png)
[English](README.md) ● [Español](README_ES.md) ● [简体中文](README_SC.md) ● [Русский](README_RU.md) ● [한국어](README_KR.md) ● [ภาษาไทย](README_TH.md) ● [Français](README_FR.md) ● [Italiano](README_IT.md) ● [日本語](README_JP.md) ● [Bahasa Indonesia](README_ID.md) ● [Português](README_PT.md) ● [Deutsch](README_DE.md)
**Textractor** (a.k.a. NextHooker)는 Windows/Wine에서 작동하는 [ITHVNR](https://web.archive.org/web/20160202084144/http://www.hongfire.com/forum/showthread.php/438331-ITHVNR-ITH-with-the-VNR-engine)을 기반으로 한 오픈소스 x86/x64 비디오게임 텍스트 후커 입니다.<br>
빠른 사용법의 이해를 위해 [tutorial video](docs/TUTORIAL.md) 를 참고하세요.
## 다운로드
[여기](https://github.com/Artikash/Textractor/releases)에서 Textractor 최신버전을 받으실 수 있습니다.<br>
최신버전의 ITHVNR은 [여기](https://drive.google.com/open?id=13aHF4uIXWn-3YML_k2YCDWhtGgn5-tnO)서 받을 수 있습니다.
## 특징
- 높은 확장성과 커스터마이즈
- 많은 게임엔진의 자동 후킹이 가능 (몇몇의 VNR로 후킹 불가능한 경우도 포함)
- /H "hook" 코드를 통한 후킹 (많은 AGTH 코드가 지원됨)
- /R "read" 코드를 통해 직접적으로 텍스트 추출이 가능
## 지원
버그나, 후킹에 문제가 있는 게임이나, 기능요청 혹은 제안들을 알려주시기 바랍니다.<br>
특정 게임의 문제해결을 위하여 해당게임을 무료로 받을 수 있는 링크나 [Steam](https://steamcommunity.com/profiles/76561198097566313/)을 통한 제공을 받고 있습니다.
## 확장기능
어떻게 확장기능을 만드는지 [Example Extension project](https://github.com/Artikash/ExampleExtension) 을 확인해 보시기 바랍니다.<br>
확장기능 폴더를 확인해 확장기능들이 어떤 역할들을 하는지 알아보세요.
## 기여
모든 기여자들에게 감사하고 있습니다! 코드베이스에 궁금한 점이 있다면 akashmozumdar@gmail.com 에 이메일 해 주시기 바랍니다.
## 컴파일링
*Textractor*를 컴파일링 하기 전에, Qt version 5.13과 CMake를 포함한 Visual Studio가 있어야 합니다.<br>
그 이후로는, 단순히 Visual Studio를 통해 폴더를 열고 빌드하는 것으로 실행이 가능합니다.
## 프로젝트 아키텍쳐
The host (see host folder) injects texthook.dll (created from the texthook folder) into the target process and connects to it via 2 pipe files.<br>
Host writes to hostPipe, texthook writes to hookPipe.<br>
texthook waits for the pipe to be connected, then injects a few instructions into any text outputting functions (e.g. TextOut, GetGlyphOutline) that cause their input to be sent through the pipe.<br>
Additional information about hooks is exchanged via shared memory.<br>
The text that the host receives through the pipe is then processed a little before being dispatched back to the GUI.<br>
Finally, the GUI dispatches the text to extensions before displaying it.
## [개발자들](docs/CREDITS.md)

52
README_PT.md Normal file
View File

@ -0,0 +1,52 @@
# Textractor
![Como se Parece](screenshot.png)
[English](README.md) ● [Español](README_ES.md) ● [简体中文](README_SC.md) ● [Русский](README_RU.md) ● [한국어](README_KR.md) ● [ภาษาไทย](README_TH.md) ● [Français](README_FR.md) ● [Italiano](README_IT.md) ● [日本語](README_JP.md) ● [Bahasa Indonesia](README_ID.md) ● [Português](README_PT.md) ● [Deutsch](README_DE.md)
**Textractor** (também conhecido como NextHooker) é um extrator de textos de video-games x86/x64 para Windows/Wine baseado no [ITHVNR](https://web.archive.org/web/20160202084144/http://www.hongfire.com/forum/showthread.php/438331-ITHVNR-ITH-with-the-VNR-engine).<br>
Assista ao [vídeo tutorial](docs/TUTORIAL.md) para uma rápida apresentação de como utilizá-lo.
## Download
As versões lançadas podem ser encontradas [aqui](https://github.com/Artikash/Textractor/releases).<br>
A última versão lançada do ITHVNR pode ser encontrada [aqui](https://drive.google.com/open?id=13aHF4uIXWn-3YML_k2YCDWhtGgn5-tnO).
## Recursos e Funções
- Altamente extensível e personalizável.
- Automaticamente extrai vários games engines (inclusive algumas não compatíveis com VNR!)
- Extrai texto usando códigos "hook" /H (a maioria dos códigos utilizados pelo AGTH são compatíveis.)
- Extrai texto diretamente utilizando códigos "read" /R
## Suporte Técnico
Por favor, deixe-me saber de quaisquer bugs, jogos que o Textractor tenha problema extraindo texto, pedido por recursos ou funções, ou quaisquer outras sugestões.<br>
Se você tiver algum problema extraindo um jogo, favor me mandar um e-mail do lugar de onde eu possa livremente dar download do jogo, ou presenteie-me o jogo no [Steam](https://steamcommunity.com/profiles/76561198097566313/).
## Extensões
Veja o meu [Projeto de Extensão-Exemplo](https://github.com/Artikash/ExampleExtension) para como construir uma extensão.<br>
Veja a pasta de extensões para mais exemplos do que as extensões são capazes de fazerem.
## Contribuindo
Todas contribuições são bem-vindas! Por favor, me mande um e-mail no endereço akashmozumdar@gmail.com caso tenha alguma dúvida quanto ao codebase.<br>
Você deve seguir o processo padrão de fazer um pull request (fork, branch, realizar mudanças, realizar o PR do seu branch para o meu master).<br>
Contribuir com uma tradução é fácil: basta traduzir as linhas do [text.cpp](text.cpp) assim como esse README.
## Compilando
Antes de compilar o *Textractor* você deve ter o Visual Studio com suporte ao CMake, assim como o Qt versão 5.13.<br>
Você deverá então ser capaz de simplesmente abrir uma pasta no Visual Studio e build. Inicie Textractor.exe.
## Arquitetura do Projeto
O host (veja a pasta host) injeta o texthook.dll (criado a partir da pasta texthook) dentro do processo-alvo e se conecta a ele por meio de 2 arquivos pipe.<br>
O Host escreve para hostPipe, o texthook escreve para hookPipe.<br>
O texthook espera pelo pipe estar conectado e então injeta algumas intruções dentro de quaisquer funções que produzam texto (por exemplo: TextOut, GetGlyphOutline) o que faz com que seu produto seja mandado por meio do pipe.<br>
Informação adicional sobre os hooks é trocada por meio da memória compartilhada.<br>
O texto que o host recebe por meio do pipe é então processado um pouco antes de ser despachado devolta para a IGU/GUI.<br>
Finalmente, a IGU/GUI despacha o texto para as extensões antes de mostrá-lo.
## [Desenvolvedores](docs/CREDITS.md)

52
README_RU.md Normal file
View File

@ -0,0 +1,52 @@
# Textractor
![Как это выглядит](screenshot.png)
[English](README.md) ● [Español](README_ES.md) ● [简体中文](README_SC.md) ● [Русский](README_RU.md) ● [한국어](README_KR.md) ● [ภาษาไทย](README_TH.md) ● [Français](README_FR.md) ● [Italiano](README_IT.md) ● [日本語](README_JP.md) ● [Bahasa Indonesia](README_ID.md) ● [Português](README_PT.md) ● [Deutsch](README_DE.md)
**Textractor** (a.k.a. NextHooker) это проект x86/x64 Windows/Wine программы для захвата текста из видеоигр, основанный на [ITHVNR](https://web.archive.org/web/20160202084144/http://www.hongfire.com/forum/showthread.php/438331-ITHVNR-ITH-with-the-VNR-engine).<br>
Смотреть [обучающее видео](docs/TUTORIAL.md) для быстрого ознакомления.
## Загрузка
Выпуски Textractor могут быть найдены [здесь](https://github.com/Artikash/Textractor/releases).<br>
Последний выпуск ITHVNR может быть найден [здесь](https://drive.google.com/open?id=13aHF4uIXWn-3YML_k2YCDWhtGgn5-tnO).
## Возможности
- Высокая расширяемость
- Автозахват текста из множества игровых движков (включая некоторые, неподдерживаемые в VNR!)
- Захват текста с использованием /H "hook" кодов (поддерживается большинство AGTH кодов)
- Прямое извлечение текста с использованием /R "read" кодов
## Поддержка
Сообщайте о любых ошибках, играх, с которыми у Textractor проблемы, запросах о новых функциях или другие предложения.<br>
Если у вас возникают проблемы с захватом текста из какой либо игры, скиньте на электронную почту ссылку на её загрузку или киньте её подарком в [Steam](https://steamcommunity.com/profiles/76561198097566313/) , для тестирования.
## Расширения
Смотрите [Проект примера расширения](https://github.com/Artikash/ExampleExtension), чтобы узнать, как создать расширение.<br>
Также для примера того, что делают расширения, смотрите папку extensions.
## Вклад
Любой вклад приветствуется! Пишите мне(автору) на akashmozumdar@gmail.com, если у вас есть любые вопросы о кодовой базе.<br>
Используйте стандартные действия для создания pull request (fork, branch, commit changes, создайте PR из своей ветки branch на мой master).<br>
Вклад в перевод совсем не сложен: просто переведите строки в [text.cpp](text.cpp), также, как и этот README.
## Компиляция
Перед компиляцией *Textractor*, установите Visual Studio с поддержкой CMake, а также Qt версии 5.13<br>
Тогда вы сможете просто открыть и построить проект в Visual Studio. Запустите Textractor.exe.
## Архитектура проекта
Хост (смотрите папку host) внедряет texthook.dll (созданной из папки texthook) в целевой процесс и подключается к нему через два файла-канала (pipe).<br>
Хост пишет в hostPipe, texthook пишет в hookPipe.<br>
texthook ждет присоединения канала, тогда внедряет некоторые инструкции в любые выводящие текст функции (такие как TextOut, GetGlyphOutline), что вызывает пересылку поступающего в них текста через канал.<br>
Дополнительная информация о хуках размещена через файл просмотра (a.k.a. section object), который сопоставлен с ссылкой на класс TextHook.<br>
Текст, который хост получает через канал, затем немного обрабатывается перед отправкой обратно в графический интерфейс (GUI).<br>
Наконец, GUI отправляет текст расширениям, перед его отображением.
## [Разработчики](docs/CREDITS.md)

49
README_SC.md Normal file
View File

@ -0,0 +1,49 @@
# Textractor
[English](README.md) ● [Español](README_ES.md) ● [简体中文](README_SC.md) ● [Русский](README_RU.md) ● [한국어](README_KR.md) ● [ภาษาไทย](README_TH.md) ● [Français](README_FR.md) ● [Italiano](README_IT.md) ● [日本語](README_JP.md) ● [Bahasa Indonesia](README_ID.md) ● [Português](README_PT.md) ● [Deutsch](README_DE.md)
## 概述
**Textractor** (曾用名: NextHooker) 是一个基于 [ITHVNR](https://web.archive.org/web/20160202084144/http://www.hongfire.com/forum/showthread.php/438331-ITHVNR-ITH-with-the-VNR-engine), 为 Windows/Wine 开发的开源 x86/x64 文本提取器.<br>
![它工作起来的样子](screenshot.png)
## 下载
Textractor 的发行版可以在[这里](https://github.com/Artikash/Textractor/releases)找到.
老版 ITHVNR 可以在[这里](https://github.com/mireado/ITHVNR/releases)找到.
## 特点
- 高度可扩展
- 自动从很多游戏中提取 (包括一些没有被 VNR 支持的!)
- 通过 /H "hook" 码提取文本 (支持大多数 AGTH 码)
- 使用 /R "read" 码直接抽取文本
## 扩展
通过我的[扩展示例项目](https://github.com/Artikash/ExampleExtension)查看如何构建扩展.<br>
通过 extensions 文件夹查看扩展能够做什么.
## 贡献
欢迎一切贡献!如有任何关于代码的疑问,请向 akashmozumdar@gmail.com 发邮件.<br>
你应当使用创建 PR 的标准过程 (分岔 (fork), 分支 (branch), 提交变化, 创建从你的分支到我的 master 分支的 PR).<br>
提供翻译贡献很简单: 只需翻译 [text.cpp](text.cpp) 中的字符串和这份 README 即可.
## 编译
编译 *Textractor* 前, 你应当获取支持 CMake 的 Visual Studio, 以及 Qt 5.13 版.<br>
之后就可以使用 Visual Studio 打开文件夹, 然后构建. 运行 Textractor.exe.
## 项目架构
宿主 (位于 host 文件夹) 向目标进程注入 texthook.dll (由 texthook 文件夹创建) 并通过两个管道文件互联.<br>
宿主向 hostPipe 写入, texthook 向 hookPipe 写入.<br>
texthook 等待管道连接, 之后向一些文本输出函数 (如 TextOut, GetGlyphOutline) 注入一系列指令, 使得它们的输入被沿着管道发送.<br>
其它关于钩子的信息通过一个被 TextHook 类保有引用的文件视图 (曾用名: 段对象) 共享.<br>
之后, 宿主通过管道接收到的文本在传回 GUI 前被简单处理.<br>
最后, GUI 在显示文本前将其分发给扩展.
## [开发者](docs/CREDITS.md)

48
README_TH.md Normal file
View File

@ -0,0 +1,48 @@
# Textractor
![How it looks](screenshot.png)
[English](README.md) ● [Español](README_ES.md) ● [简体中文](README_SC.md) ● [Русский](README_RU.md) ● [한국어](README_KR.md) ● [ภาษาไทย](README_TH.md) ● [Français](README_FR.md) ● [Italiano](README_IT.md) ● [日本語](README_JP.md) ● [Bahasa Indonesia](README_ID.md) ● [Português](README_PT.md) ● [Deutsch](README_DE.md)
**Textractor**
(หรือ NextHooker) คือโปรแกรมโอเพนซอร์ซสำหรับปฏิบัติการที่มีหน้าที่เพื่อเชื่อมกับตัวอักษรกับเกมจากที่มาจากระบบปฏิบัติการ Window/Wine โดยมีแบบดังเดิมมาจาก [ITHVNR](https://web.archive.org/web/20160202084144/http://www.hongfire.com/forum/showthread.php/438331-ITHVNR-ITH-with-the-VNR-engine).<br>
สามารถดูตัวอย่างวิธีการใช้งาน [วีดีโอตัวอย่างการใช้งาน](docs/TUTORIAL.md) เพื่อที่จะแสดงความเข้าใจคร่าวๆเกี่ยวกับโปรแกรม.
## ดาวน์โหลด
Textractor รุ่นล่าสุดสามารถดาวน์โหลดจาก [ที่นี้](https://github.com/Artikash/Textractor/releases).<br>
ITHVNR รุ่นสุดท้ายสามารถดาวน์โหลดได้ [ที่นี้](https://drive.google.com/open?id=13aHF4uIXWn-3YML_k2YCDWhtGgn5-tnO).
## คุณสมบัติ
- Highly extensible and customizable
- ต่อยอดได้ไกล และ ปรับแต่งได้ง่าย
- สามารถเชื่อม/ดึงคำแปลได้จากระบบเกมหลายเกม (รวมทั่งเกมที่ไม่ได้รองรับโดยโปรแกรม Visual Novel Reader)
- สามารถเชื่อมตัวอักษรโดยการใช้ /H "hook" (รหัสเชื่อม) และยังรองรับการใช้รหัสของ AGTH
- สามารถการดึงข้อมูลโดยใช้รหัส /R "read
## ความช่วยเหลือ
ในกรณีที่พบกับปัญหาระหว่างใช้งานโปรแกรม หรือ เกมที่ Textractor ไม่สามารถเชื่อมข้อมูล, หรือแนะนำต่างๆ สามารถแจ้งให้ทราบได้จากเว็บไซต์ที่ดาวน์โหลด Textractor.
ถ้าหากมีเกมใดที่มีปัญหาการเชื่อมกรุณาส่งอีเมลสถานที่ที่สามารถดาวน์โหลดเกมได้ หรือ ส่งของขวัญเกมผ่านทาง [Steam](https://steamcommunity.com/profiles/76561198097566313/).
## ส่วนขยาย
กรุณาลองสำรวจ [ตัวอย่างของส่วนขยาย](https://github.com/Artikash/ExampleExtension) เพื่อที่จะเรียนรู้เกี่ยวกับการเขียนส่วนขยาย.<br>
และลองดูโฟลเดอร์ extensions สำหรับตัวอย่างการทำงานของส่วนขยาย
## การสนับสนุน
การสนับสนุนใดๆนั่นยินดีเป็นอย่างยิ่ง! สามารถส่งอีเมลมาได้ตลอดเวลาที่ akashmozumdar@gmail.com ถ้าหากมีคำถามเกี่ยวกับโค้ด.<br>
## โครงสร้างโปรแกรม
ฐานของโปรแกรม (โฟลเดอร์ host) ส่งข้อมูลจาก texthook.dll (ที่ถูกสร้างจาก texthook โฟลเดอร์) ไปยังเกมเป้าหมาย และ เชื่อมทั่งสองอย่างเข้าด้วยกัน<br>
ฐานโปรแกรมเขียนผ่านฝั่ง hostPipe(ท่อเชื่อมฝั่งฐานข้อมูล) ในขณะที่ตัวดึงตัวอักษรที่ทางฝั่ง hookPipe(ท่อเชื่อมฝั่งดึงข้อมูล).<br>
ตัวดึงตัวอักษรรอการเชื่อมเข้ากับของทั่งสองท่อ หลังจากนั่นส่งคำสั่งไปยังข้อมูลนั่น (เช่น แสดงผลข้อมูล เป็นต้น) และทำให้ข้อมูลส่งผ่านต่อมาออกมาได้ถูกต้อง<br>
ข้อมูลบางอย่างเกี่ยวกับการเชื่อมจะถูกแลกเปลี่ยนผ่านความทรงจำของระบบ (shared memory)
<br>
ตัวอักษรที่ฐานโปรแกรมรับผ่านท่อจะถูกแปลงเล็กน้อยก่อนที่จะแสดงผ่าน GUI <br>
สุดท้ายแล้ว GUI จะส่งข้อมูลตัวอักษรไปยังส่วนขยายต่างๆก่อนที่จะแสดงให้เห็นในหน้าจอ
## [นักพัฒนา](docs/CREDITS.md)

3
README_TU.md Normal file
View File

@ -0,0 +1,3 @@
# WIP
## README not translated to Turkish yet :(

BIN
assets/LoaderDll.dll Normal file

Binary file not shown.

BIN
assets/LocaleEmulator.dll Normal file

Binary file not shown.

BIN
assets/StressTest.txt Normal file

Binary file not shown.

84
cmake/QtUtils.cmake Normal file
View File

@ -0,0 +1,84 @@
macro(msvc_registry_search)
if(NOT DEFINED Qt5_DIR)
if (NOT EXISTS ${QT_ROOT})
# look for user-registry pointing to qtcreator
get_filename_component(QT_ROOT [HKEY_CURRENT_USER\\Software\\Classes\\Applications\\QtProject.QtCreator.pro\\shell\\Open\\Command] PATH)
# get root path
string(REPLACE "/Tools" ";" QT_ROOT "${QT_ROOT}")
list(GET QT_ROOT 0 QT_ROOT)
endif()
file(GLOB QT_VERSIONS "${QT_ROOT}/5.13*")
list(SORT QT_VERSIONS)
# assume the latest version will be last alphabetically
list(REVERSE QT_VERSIONS)
list(GET QT_VERSIONS 0 QT_VERSION)
# fix any double slashes which seem to be common
string(REPLACE "//" "/" QT_VERSION "${QT_VERSION}")
if(MSVC_VERSION GREATER_EQUAL 1920)
set(QT_MSVC 2019)
elseif(MSVC_VERSION GREATER_EQUAL 1910)
set(QT_MSVC 2017)
elseif(MSVC_VERSION GREATER_EQUAL 1900)
set(QT_MSVC 2015)
else()
message(WARNING "Unsupported MSVC toolchain version")
endif()
if(QT_MSVC)
if(CMAKE_CL_64)
SET(QT_SUFFIX "_64")
else()
set(QT_SUFFIX "")
endif()
# MSVC 2015+ is only backwards compatible
if(EXISTS "${QT_VERSION}/msvc${QT_MSVC}${QT_SUFFIX}")
set(Qt5_DIR "${QT_VERSION}/msvc${QT_MSVC}${QT_SUFFIX}/lib/cmake/Qt5")
elseif(QT_MSVC GREATER_EQUAL 2019 AND EXISTS "${QT_VERSION}/msvc2017${QT_SUFFIX}")
set(Qt5_DIR "${QT_VERSION}/msvc2017${QT_SUFFIX}/lib/cmake/Qt5")
elseif(QT_MSVC GREATER_EQUAL 2017 AND EXISTS "${QT_VERSION}/msvc2015${QT_SUFFIX}")
set(Qt5_DIR "${QT_VERSION}/msvc2015${QT_SUFFIX}/lib/cmake/Qt5")
else()
message(WARNING "Required QT5 toolchain is not installed")
endif()
endif()
endif()
endmacro()
macro(find_qt5)
set(CMAKE_INCLUDE_CURRENT_DIR ON)
#set(CMAKE_AUTOMOC ON)
set(CMAKE_AUTOUIC ON)
#add_definitions(-DQT_DEPRECATED_WARNINGS -DQT_DISABLE_DEPRECATED_BEFORE=0x060000)
find_package(Qt5 COMPONENTS ${ARGN})
if(Qt5_FOUND)
if(WIN32 AND TARGET Qt5::qmake AND NOT TARGET Qt5::windeployqt)
get_target_property(_qt5_qmake_location Qt5::qmake IMPORTED_LOCATION)
execute_process(
COMMAND "${_qt5_qmake_location}" -query QT_INSTALL_PREFIX
RESULT_VARIABLE return_code
OUTPUT_VARIABLE qt5_install_prefix
OUTPUT_STRIP_TRAILING_WHITESPACE
)
set(imported_location "${qt5_install_prefix}/bin/windeployqt.exe")
if(EXISTS ${imported_location})
add_executable(Qt5::windeployqt IMPORTED)
set_target_properties(Qt5::windeployqt PROPERTIES
IMPORTED_LOCATION ${imported_location}
)
endif()
endif()
else()
message(FATAL_ERROR "Cannot find QT5!")
endif()
endmacro(find_qt5)

101
deploy.ps1 Normal file
View File

@ -0,0 +1,101 @@
param([string]$version)
cd $PSScriptRoot;
mkdir -Force -Verbose builds;
cd builds;
mkdir -Force -Verbose x86;
mkdir -Force -Verbose x64;
foreach ($language in @{
ENGLISH="";
SPANISH="Spanish";
SIMPLIFIED_CHINESE="Simplified-Chinese";
RUSSIAN="Russian";
TURKISH="Turkish";
INDONESIAN="Indonesian";
PORTUGUESE="Portuguese";
THAI="Thai";
KOREAN="Korean";
ITALIAN="Italian";
FRENCH="French"
}.GetEnumerator())
{
$folder = "Textractor-$($language.Value)-$version";
rm -Force -Recurse -Verbose $folder;
mkdir -Force -Verbose $folder;
foreach ($arch in @("x86", "x64"))
{
cd $arch;
$VS_arch = if ($arch -eq "x86") {"Win32"} else {"x64"};
&"C:\Program Files\CMake\bin\cmake" -G "Visual Studio 16 2019" -A"$VS_arch" -DVERSION="$version" -DTEXT_LANGUAGE="$($language.Key)" -DCMAKE_BUILD_TYPE="Release" ../..;
&"C:\Program Files (x86)\Microsoft Visual Studio\2019\Community\Common7\IDE\devenv" Textractor.sln /build "Release|$VS_arch";
cd ..;
mkdir -Force -Verbose "$folder/$arch";
foreach ($file in @(
"Textractor.exe",
"TextractorCLI.exe",
"texthook.dll"
))
{
copy -Force -Recurse -Verbose -Destination "$folder/$arch" -Path "Release_$arch/$file";
}
foreach ($extension in @(
"Bing Translate",
"Copy to Clipboard",
"DeepL Translate",
"DevTools DeepL Translate",
"DevTools Papago Translate",
"DevTools Systran Translate",
"Extra Newlines",
"Extra Window",
"Google Translate",
"Lua",
"Regex Filter",
"Regex Replacer",
"Remove Repeated Characters",
"Remove Repeated Phrases",
"Remove Repeated Phrases 2",
"Remove 30 Repeated Sentences",
"Replacer",
"Styler",
"Thread Linker"
))
{
copy -Force -Recurse -Verbose -Destination "$folder/$arch/$extension.xdll" -Path "Release_$arch/$extension.dll";
}
}
&"C:\Program Files (x86)\Windows Kits\10\App Certification Kit\signtool.exe" sign /a /v /t "http://timestamp.digicert.com" /fd SHA256 @(dir "$folder\**\*");
}
rm -Force -Recurse -Verbose "Runtime";
mkdir -Force -Verbose "Runtime";
foreach ($arch in @("x86", "x64"))
{
mkdir -Force -Verbose "Runtime/$arch";
foreach ($file in @(
"LoaderDll.dll",
"LocaleEmulator.dll",
"Qt5Core.dll",
"Qt5Gui.dll",
"Qt5Network.dll",
"Qt5WebSockets.dll",
"Qt5WinExtras.dll"
"Qt5Widgets.dll",
"platforms",
"styles"
))
{
copy -Force -Recurse -Verbose -Destination "Runtime/$arch/$file" -Path "Release_$arch/$file";
}
copy -Force -Recurse -Verbose -Destination "Runtime/$arch" -Path "C:/Program Files (x86)/Microsoft Visual Studio/2019/Community/VC/Redist/MSVC/**/$arch/Microsoft.VC142.CRT/*"
}
rm -Force -Recurse -Verbose "Textractor";
mkdir -Force -Verbose "Textractor";
copy -Force -Recurse -Verbose -Destination "Textractor" -Path @("Runtime/*", "Textractor--$version/*");
&"C:\Program Files\7-Zip\7z" a "Textractor-$version-Zip-Version-English-Only.zip" Textractor/ ../INSTALL_THIS_UNICODE_FONT.ttf
cd ..;
&"C:\Program Files (x86)\Inno Setup 6\iscc" -DVERSION="$version" installer.iss;
&"C:\Program Files (x86)\Windows Kits\10\App Certification Kit\signtool.exe" sign /a /v /t "http://timestamp.digicert.com" /fd SHA256 "Builds/Textractor-$version-Setup.exe";

32
docs/CREDITS.md Normal file
View File

@ -0,0 +1,32 @@
# Developers
- Textractor mainly made by [Artikash](https://github.com/Artikash) with the contributions of
- Build system improvements by [DoumanAsh](https://github.com/DoumanAsh)
- Original text removal in Extra Window by [Niakr1s](https://github.com/Niakr1s)
- CLI bugfixes by [tinyAdapter](https://github.com/tinyAdapter)
- Hook codes for WAFFLE and WillPlus by [lgztx96](https://github.com/lgztx96)
- Hook codes for TokyoNecro by [Jazzinghen](https://github.com/Jazzinghen)
- Hook codes for ShinyDays and Artemis by [luojunyuan](https://github.com/luojunyuan)
- Google Chrome DevTools integration by [zeheyler](https://github.com/zeheyler)
- AppVeyor setup by [silas1037](https://github.com/silas1037)
- Improved attach dialog by [tera8m4](https://github.com/tera8m4)
- Regex Replacer, Papago, Systran, improvements to Extra Window, and bugfixes by [Blu3train](https://github.com/Blu3train)
- French translation by [Racky](mailto:maitrenoah@gmail.com) and [Gratusfr](https://github.com/Gratusfr)
- Spanish translation by [scese250](https://github.com/scese250)
- Turkish translation by [niisokusu](https://reddit.com/u/niisokusu)
- Simplified Chinese translation by [tinyAdapter](https://github.com/tinyAdapter), [lgztx96](https://github.com/lgztx96) and [chinanoahli](https://github.com/chinanoahli)
- Russian translation by [TokcDK](https://github.com/TokcDK)
- Indonesian translation by [Hawxone](https://github.com/Hawxone)
- Portuguese translation by [TsumiHokiro](https://github.com/TsumiHokiro)
- Thai translation by [azmadoppler](https://github.com/azmadoppler)
- Korean translation by [O SK](mailto:afkl11@outlook.kr)
- Italian translation by [StarFang208](https://github.com/StarFang208)
- ITHVNR updated by [mireado](https://github.com/mireado), [Eguni](https://github.com/Eguni), and [IJEMIN](https://github.com/IJEMIN)
- ITHVNR originally made by [Stomp](mailto:zorkzero@hotmail.com)
- VNR engine made by [jichi](https://github.com/jichifly)
- ITH updated by [Andys](https://github.com/AndyScull)
- ITH originally made by [kaosu](https://code.google.com/archive/p/interactive-text-hooker)
If you're on this list and want your link changed let Artikash know.
# THANK YOU!!

7
docs/TUTORIAL.md Normal file
View File

@ -0,0 +1,7 @@
# Tutorial Video
https://www.youtube.com/watch?v=eecEOacF6mw
## Updates/Corrections
Automatically finding hooks is now done via the `Search for hooks` button, the method shown in the video is found in the `Search for specific text` option.

64
extensions/CMakeLists.txt Normal file
View File

@ -0,0 +1,64 @@
cmake_policy(SET CMP0037 OLD)
add_library(Bing\ Translate MODULE bingtranslate.cpp translatewrapper.cpp network.cpp extensionimpl.cpp)
add_library(Copy\ to\ Clipboard MODULE copyclipboard.cpp extensionimpl.cpp)
add_library(DeepL\ Translate MODULE deepltranslate.cpp translatewrapper.cpp network.cpp extensionimpl.cpp)
add_library(DevTools\ DeepL\ Translate MODULE devtoolsdeepltranslate.cpp devtools.cpp translatewrapper.cpp network.cpp extensionimpl.cpp)
add_library(DevTools\ Papago\ Translate MODULE devtoolspapagotranslate.cpp devtools.cpp translatewrapper.cpp network.cpp extensionimpl.cpp)
add_library(DevTools\ Systran\ Translate MODULE devtoolssystrantranslate.cpp devtools.cpp translatewrapper.cpp network.cpp extensionimpl.cpp)
add_library(Extra\ Newlines MODULE extranewlines.cpp extensionimpl.cpp)
add_library(Extra\ Window MODULE extrawindow.cpp extensionimpl.cpp)
add_library(Google\ Translate MODULE googletranslate.cpp translatewrapper.cpp network.cpp extensionimpl.cpp)
add_library(Lua MODULE lua.cpp extensionimpl.cpp)
add_library(Regex\ Filter MODULE regexfilter.cpp extensionimpl.cpp)
add_library(Regex\ Replacer MODULE regexreplacer.cpp extensionimpl.cpp)
add_library(Remove\ Repeated\ Characters MODULE removerepeatchar.cpp extensionimpl.cpp)
add_library(Remove\ Repeated\ Phrases MODULE removerepeatphrase.cpp extensionimpl.cpp)
add_library(Remove\ Repeated\ Phrases\ 2 MODULE removerepeatphrase2.cpp extensionimpl.cpp)
add_library(Remove\ 30\ Repeated\ Sentences MODULE removerepeatsentence.cpp extensionimpl.cpp)
add_library(Replacer MODULE replacer.cpp extensionimpl.cpp)
add_library(Styler MODULE styler.cpp extensionimpl.cpp)
add_library(Thread\ Linker MODULE threadlinker.cpp extensionimpl.cpp)
target_precompile_headers(Bing\ Translate REUSE_FROM pch)
target_precompile_headers(Copy\ to\ Clipboard REUSE_FROM pch)
target_precompile_headers(DeepL\ Translate REUSE_FROM pch)
target_precompile_headers(DevTools\ DeepL\ Translate REUSE_FROM pch)
target_precompile_headers(DevTools\ Papago\ Translate REUSE_FROM pch)
target_precompile_headers(DevTools\ Systran\ Translate REUSE_FROM pch)
target_precompile_headers(Extra\ Newlines REUSE_FROM pch)
target_precompile_headers(Extra\ Window REUSE_FROM pch)
target_precompile_headers(Google\ Translate REUSE_FROM pch)
target_precompile_headers(Lua REUSE_FROM pch)
target_precompile_headers(Regex\ Filter REUSE_FROM pch)
target_precompile_headers(Regex\ Replacer REUSE_FROM pch)
target_precompile_headers(Remove\ Repeated\ Characters REUSE_FROM pch)
target_precompile_headers(Remove\ Repeated\ Phrases REUSE_FROM pch)
target_precompile_headers(Remove\ Repeated\ Phrases\ 2 REUSE_FROM pch)
target_precompile_headers(Remove\ 30\ Repeated\ Sentences REUSE_FROM pch)
target_precompile_headers(Replacer REUSE_FROM pch)
target_precompile_headers(Styler REUSE_FROM pch)
target_precompile_headers(Thread\ Linker REUSE_FROM pch)
target_link_libraries(Bing\ Translate winhttp Qt5::Widgets)
target_link_libraries(DeepL\ Translate winhttp Qt5::Widgets)
target_link_libraries(DevTools\ DeepL\ Translate shell32 winhttp Qt5::Widgets Qt5::WebSockets)
target_link_libraries(DevTools\ Papago\ Translate shell32 winhttp Qt5::Widgets Qt5::WebSockets)
target_link_libraries(DevTools\ Systran\ Translate shell32 winhttp Qt5::Widgets Qt5::WebSockets)
target_link_libraries(Extra\ Window Qt5::Widgets)
target_link_libraries(Google\ Translate winhttp Qt5::Widgets)
target_link_libraries(Lua lua53 Qt5::Widgets)
target_link_libraries(Regex\ Filter Qt5::Widgets)
target_link_libraries(Styler Qt5::Widgets)
target_link_libraries(Thread\ Linker Qt5::Widgets)
add_custom_target(Cleaner ALL COMMAND del *.xdll WORKING_DIRECTORY ${CMAKE_FINAL_OUTPUT_DIRECTORY})
if (NOT EXISTS ${CMAKE_FINAL_OUTPUT_DIRECTORY}/Qt5WebSockets.dll AND NOT EXISTS ${CMAKE_FINAL_OUTPUT_DIRECTORY}/Qt5WebSocketsd.dll)
add_custom_command(TARGET DevTools\ DeepL\ Translate
POST_BUILD
COMMAND ${CMAKE_COMMAND} -E remove_directory "${CMAKE_CURRENT_BINARY_DIR}/windeployqt"
COMMAND set PATH=%PATH%$<SEMICOLON>${qt5_install_prefix}/bin
COMMAND Qt5::windeployqt --dir ${CMAKE_FINAL_OUTPUT_DIRECTORY} "${CMAKE_FINAL_OUTPUT_DIRECTORY}/DevTools\ DeepL\ Translate.dll"
)
endif()

View File

@ -0,0 +1,241 @@
#include "qtcommon.h"
#include "translatewrapper.h"
#include "network.h"
extern const wchar_t* TRANSLATION_ERROR;
const char* TRANSLATION_PROVIDER = "Bing Translate";
const char* GET_API_KEY_FROM = "https://www.microsoft.com/en-us/translator/business/trial/#get-started";
extern const QStringList languagesTo
{
"Afrikaans",
"Albanian",
"Amharic",
"Arabic",
"Armenian",
"Assamese",
"Azerbaijani",
"Bangla",
"Bosnian (Latin)",
"Bulgarian",
"Cantonese (Traditional)",
"Catalan",
"Chinese (Simplified)",
"Chinese (Traditional)",
"Croatian",
"Czech",
"Danish",
"Dari",
"Dutch",
"English",
"Estonian",
"Fijian",
"Filipino",
"Finnish",
"French",
"French (Canada)",
"German",
"Greek",
"Gujarati",
"Haitian Creole",
"Hebrew",
"Hindi",
"Hmong Daw",
"Hungarian",
"Icelandic",
"Indonesian",
"Inuktitut",
"Irish",
"Italian",
"Japanese",
"Kannada",
"Kazakh",
"Khmer",
"Klingon",
"Korean",
"Kurdish (Central)",
"Kurdish (Northern)",
"Lao",
"Latvian",
"Lithuanian",
"Malagasy",
"Malay",
"Malayalam",
"Maltese",
"Maori",
"Marathi",
"Myanmar",
"Nepali",
"Norwegian",
"Odia",
"Pashto",
"Persian",
"Polish",
"Portuguese (Brazil)",
"Portuguese (Portugal)",
"Punjabi",
"Queretaro Otomi",
"Romanian",
"Russian",
"Samoan",
"Serbian (Cyrillic)",
"Serbian (Latin)",
"Slovak",
"Slovenian",
"Spanish",
"Swahili",
"Swedish",
"Tahitian",
"Tamil",
"Telugu",
"Thai",
"Tigrinya",
"Tongan",
"Turkish",
"Ukrainian",
"Urdu",
"Vietnamese",
"Welsh",
"Yucatec Maya"
}, languagesFrom = languagesTo;
extern const std::unordered_map<std::wstring, std::wstring> codes
{
{ { L"Afrikaans" }, { L"af" } },
{ { L"Albanian" }, { L"sq" } },
{ { L"Amharic" }, { L"am" } },
{ { L"Arabic" }, { L"ar" } },
{ { L"Armenian" }, { L"hy" } },
{ { L"Assamese" }, { L"as" } },
{ { L"Azerbaijani" }, { L"az" } },
{ { L"Bangla" }, { L"bn" } },
{ { L"Bosnian (Latin)" }, { L"bs" } },
{ { L"Bulgarian" }, { L"bg" } },
{ { L"Cantonese (Traditional)" }, { L"yue" } },
{ { L"Catalan" }, { L"ca" } },
{ { L"Chinese (Simplified)" }, { L"zh-Hans" } },
{ { L"Chinese (Traditional)" }, { L"zh-Hant" } },
{ { L"Croatian" }, { L"hr" } },
{ { L"Czech" }, { L"cs" } },
{ { L"Danish" }, { L"da" } },
{ { L"Dari" }, { L"prs" } },
{ { L"Dutch" }, { L"nl" } },
{ { L"English" }, { L"en" } },
{ { L"Estonian" }, { L"et" } },
{ { L"Fijian" }, { L"fj" } },
{ { L"Filipino" }, { L"fil" } },
{ { L"Finnish" }, { L"fi" } },
{ { L"French" }, { L"fr" } },
{ { L"French (Canada)" }, { L"fr-ca" } },
{ { L"German" }, { L"de" } },
{ { L"Greek" }, { L"el" } },
{ { L"Gujarati" }, { L"gu" } },
{ { L"Haitian Creole" }, { L"ht" } },
{ { L"Hebrew" }, { L"he" } },
{ { L"Hindi" }, { L"hi" } },
{ { L"Hmong Daw" }, { L"mww" } },
{ { L"Hungarian" }, { L"hu" } },
{ { L"Icelandic" }, { L"is" } },
{ { L"Indonesian" }, { L"id" } },
{ { L"Inuktitut" }, { L"iu" } },
{ { L"Irish" }, { L"ga" } },
{ { L"Italian" }, { L"it" } },
{ { L"Japanese" }, { L"ja" } },
{ { L"Kannada" }, { L"kn" } },
{ { L"Kazakh" }, { L"kk" } },
{ { L"Khmer" }, { L"km" } },
{ { L"Klingon" }, { L"tlh-Latn" } },
{ { L"Korean" }, { L"ko" } },
{ { L"Kurdish (Central)" }, { L"ku" } },
{ { L"Kurdish (Northern)" }, { L"kmr" } },
{ { L"Lao" }, { L"lo" } },
{ { L"Latvian" }, { L"lv" } },
{ { L"Lithuanian" }, { L"lt" } },
{ { L"Malagasy" }, { L"mg" } },
{ { L"Malay" }, { L"ms" } },
{ { L"Malayalam" }, { L"ml" } },
{ { L"Maltese" }, { L"mt" } },
{ { L"Maori" }, { L"mi" } },
{ { L"Marathi" }, { L"mr" } },
{ { L"Myanmar" }, { L"my" } },
{ { L"Nepali" }, { L"ne" } },
{ { L"Norwegian" }, { L"nb" } },
{ { L"Odia" }, { L"or" } },
{ { L"Pashto" }, { L"ps" } },
{ { L"Persian" }, { L"fa" } },
{ { L"Polish" }, { L"pl" } },
{ { L"Portuguese (Brazil)" }, { L"pt" } },
{ { L"Portuguese (Portugal)" }, { L"pt-pt" } },
{ { L"Punjabi" }, { L"pa" } },
{ { L"Queretaro Otomi" }, { L"otq" } },
{ { L"Romanian" }, { L"ro" } },
{ { L"Russian" }, { L"ru" } },
{ { L"Samoan" }, { L"sm" } },
{ { L"Serbian (Cyrillic)" }, { L"sr-Cyrl" } },
{ { L"Serbian (Latin)" }, { L"sr-Latn" } },
{ { L"Slovak" }, { L"sk" } },
{ { L"Slovenian" }, { L"sl" } },
{ { L"Spanish" }, { L"es" } },
{ { L"Swahili" }, { L"sw" } },
{ { L"Swedish" }, { L"sv" } },
{ { L"Tahitian" }, { L"ty" } },
{ { L"Tamil" }, { L"ta" } },
{ { L"Telugu" }, { L"te" } },
{ { L"Thai" }, { L"th" } },
{ { L"Tigrinya" }, { L"ti" } },
{ { L"Tongan" }, { L"to" } },
{ { L"Turkish" }, { L"tr" } },
{ { L"Ukrainian" }, { L"uk" } },
{ { L"Urdu" }, { L"ur" } },
{ { L"Vietnamese" }, { L"vi" } },
{ { L"Welsh" }, { L"cy" } },
{ { L"Yucatec Maya" }, { L"yua" } },
{ { L"?" }, { L"auto-detect" } }
};
bool translateSelectedOnly = false, useRateLimiter = true, rateLimitSelected = false, useCache = true, useFilter = true;
int tokenCount = 30, rateLimitTimespan = 60000, maxSentenceSize = 1000;
std::pair<bool, std::wstring> Translate(const std::wstring& text, TranslationParam tlp)
{
if (!tlp.authKey.empty())
{
std::wstring translateFromComponent = tlp.translateFrom == L"?" ? L"" : L"&from=" + codes.at(tlp.translateFrom);
if (HttpRequest httpRequest{
L"Mozilla/5.0 Textractor",
L"api.cognitive.microsofttranslator.com",
L"POST",
FormatString(L"/translate?api-version=3.0&to=%s%s", codes.at(tlp.translateTo), translateFromComponent).c_str(),
FormatString(R"([{"text":"%s"}])", JSON::Escape(WideStringToString(text))),
FormatString(L"Content-Type: application/json; charset=UTF-8\r\nOcp-Apim-Subscription-Key:%s", tlp.authKey).c_str()
})
if (auto translation = Copy(JSON::Parse(httpRequest.response)[0][L"translations"][0][L"text"].String())) return { true, translation.value() };
else return { false, FormatString(L"%s: %s", TRANSLATION_ERROR, httpRequest.response) };
else return { false, FormatString(L"%s (code=%u)", TRANSLATION_ERROR, httpRequest.errorCode) };
}
static std::atomic<int> i = 0;
static Synchronized<std::wstring> token;
if (token->empty()) if (HttpRequest httpRequest{ L"Mozilla/5.0 Textractor", L"www.bing.com", L"GET", L"translator" })
{
std::wstring tokenBuilder;
if (auto tokenPos = httpRequest.response.find(L"[" + std::to_wstring(time(nullptr) / 100)); tokenPos != std::string::npos)
tokenBuilder = FormatString(L"&key=%s&token=%s", httpRequest.response.substr(tokenPos + 1, 13), httpRequest.response.substr(tokenPos + 16, 32));
if (auto tokenPos = httpRequest.response.find(L"IG:\""); tokenPos != std::string::npos)
tokenBuilder += L"&IG=" + httpRequest.response.substr(tokenPos + 4, 32);
if (auto tokenPos = httpRequest.response.find(L"data-iid=\""); tokenPos != std::string::npos)
tokenBuilder += L"&IID=" + httpRequest.response.substr(tokenPos + 10, 15);
if (!tokenBuilder.empty()) token->assign(tokenBuilder);
else return { false, FormatString(L"%s: %s\ntoken not found", TRANSLATION_ERROR, httpRequest.response) };
}
else return { false, FormatString(L"%s: could not acquire token", TRANSLATION_ERROR) };
if (HttpRequest httpRequest{
L"Mozilla/5.0 Textractor",
L"www.bing.com",
L"POST",
FormatString(L"/ttranslatev3?fromLang=%s&to=%s&text=%s%s.%d", codes.at(tlp.translateFrom), codes.at(tlp.translateTo), Escape(text), token.Copy(), i++).c_str()
})
if (auto translation = Copy(JSON::Parse(httpRequest.response)[0][L"translations"][0][L"text"].String())) return { true, translation.value() };
else return { false, FormatString(L"%s (token=%s): %s", TRANSLATION_ERROR, std::exchange(token.Acquire().contents, L""), httpRequest.response) };
else return { false, FormatString(L"%s (code=%u)", TRANSLATION_ERROR, httpRequest.errorCode) };
}

57
extensions/blockmarkup.h Normal file
View File

@ -0,0 +1,57 @@
#pragma once
#include <istream>
template <typename C, int delimiterCount, int blockSize = 0x1000 / sizeof(C)> // windows file block size
class BlockMarkupIterator
{
public:
BlockMarkupIterator(const std::istream& stream, const std::basic_string_view<C>(&delimiters)[delimiterCount]) : streambuf(*stream.rdbuf())
{
std::copy_n(delimiters, delimiterCount, this->delimiters.begin());
}
std::optional<std::array<std::basic_string<C>, delimiterCount>> Next()
{
std::array<std::basic_string<C>, delimiterCount> results;
Find(delimiters[0], true);
for (int i = 0; i < delimiterCount; ++i)
{
const auto delimiter = i + 1 < delimiterCount ? delimiters[i + 1] : end;
if (auto found = Find(delimiter, false)) results[i] = std::move(found.value());
else return {};
}
return results;
}
private:
std::optional<std::basic_string<C>> Find(std::basic_string_view<C> delimiter, bool discard)
{
for (int i = 0; ;)
{
int pos = buffer.find(delimiter, i);
if (pos != std::string::npos)
{
auto result = !discard ? std::optional(std::basic_string(buffer.begin(), buffer.begin() + pos)) : std::nullopt;
buffer.erase(buffer.begin(), buffer.begin() + pos + delimiter.size());
return result;
}
int oldSize = buffer.size();
buffer.resize(oldSize + blockSize);
if (!streambuf.sgetn((char*)(buffer.data() + oldSize), blockSize * sizeof(C))) return {};
i = max(0, oldSize - (int)delimiter.size());
if (discard)
{
buffer.erase(0, i);
i = 0;
}
}
}
static constexpr C endImpl[5] = { '|', 'E', 'N', 'D', '|' };
static constexpr std::basic_string_view<C> end{ endImpl, 5 };
std::basic_streambuf<char>& streambuf;
std::basic_string<C> buffer;
std::array<std::basic_string_view<C>, delimiterCount> delimiters;
};

View File

@ -0,0 +1,16 @@
#include "extension.h"
bool ProcessSentence(std::wstring& sentence, SentenceInfo sentenceInfo)
{
if (sentenceInfo["current select"] && sentenceInfo["process id"] != 0)
{
if (!OpenClipboard(FindWindowW(NULL, L"Textractor"))) return false;
HGLOBAL hMem = GlobalAlloc(GMEM_MOVEABLE, (sentence.size() + 2) * sizeof(wchar_t));
memcpy(GlobalLock(hMem), sentence.c_str(), (sentence.size() + 2) * sizeof(wchar_t));
EmptyClipboard();
SetClipboardData(CF_UNICODETEXT, hMem);
GlobalUnlock(hMem);
CloseClipboard();
}
return false;
}

View File

@ -0,0 +1,180 @@
#include "qtcommon.h"
#include "translatewrapper.h"
#include "network.h"
#include <random>
extern const wchar_t* TRANSLATION_ERROR;
const char* TRANSLATION_PROVIDER = "DeepL Translate";
const char* GET_API_KEY_FROM = "https://www.deepl.com/pro.html#developer";
extern const QStringList languagesTo
{
"Bulgarian",
"Chinese (Simplified)",
"Czech",
"Danish",
"Dutch",
"English (American)",
"English (British)",
"Estonian",
"Finnish",
"French",
"German",
"Greek",
"Hungarian",
"Indonesian",
"Italian",
"Japanese",
"Latvian",
"Lithuanian",
"Polish",
"Portuguese (Brazil)",
"Portuguese (Portugal)",
"Romanian",
"Russian",
"Slovak",
"Slovenian",
"Spanish",
"Swedish",
"Turkish"
},
languagesFrom
{
"Bulgarian",
"Chinese",
"Czech",
"Danish",
"Dutch",
"English",
"Estonian",
"Finnish",
"French",
"German",
"Greek",
"Hungarian",
"Indonesian",
"Italian",
"Japanese",
"Latvian",
"Lithuanian",
"Polish",
"Portuguese",
"Romanian",
"Russian",
"Slovak",
"Slovenian",
"Spanish",
"Swedish",
"Turkish"
};
extern const std::unordered_map<std::wstring, std::wstring> codes
{
{ { L"Bulgarian" }, { L"BG" } },
{ { L"Chinese" }, { L"ZH" } },
{ { L"Chinese (Simplified)" }, { L"ZH" } },
{ { L"Czech" }, { L"CS" } },
{ { L"Danish" }, { L"DA" } },
{ { L"Dutch" }, { L"NL" } },
{ { L"English" }, { L"EN" } },
{ { L"English (American)" }, { L"EN-US" } },
{ { L"English (British)" }, { L"EN-GB" } },
{ { L"Estonian" }, { L"ET" } },
{ { L"Finnish" }, { L"FI" } },
{ { L"French" }, { L"FR" } },
{ { L"German" }, { L"DE" } },
{ { L"Greek" }, { L"EL" } },
{ { L"Hungarian" }, { L"HU" } },
{ { L"Indonesian" }, { L"ID" } },
{ { L"Italian" }, { L"IT" } },
{ { L"Japanese" }, { L"JA" } },
{ { L"Latvian" }, { L"LV" } },
{ { L"Lithuanian" }, { L"LT" } },
{ { L"Polish" }, { L"PL" } },
{ { L"Portuguese" }, { L"PT" } },
{ { L"Portuguese (Brazil)" }, { L"PT-BR" } },
{ { L"Portuguese (Portugal)" }, { L"PT-PT" } },
{ { L"Romanian" }, { L"RO" } },
{ { L"Russian" }, { L"RU" } },
{ { L"Slovak" }, { L"SK" } },
{ { L"Slovenian" }, { L"SL" } },
{ { L"Spanish" }, { L"ES" } },
{ { L"Swedish" }, { L"SV" } },
{ { L"Turkish" }, { L"TR" } },
{ { L"?" }, { L"auto" } }
};
bool translateSelectedOnly = true, useRateLimiter = true, rateLimitSelected = true, useCache = true, useFilter = true;
int tokenCount = 10, rateLimitTimespan = 60000, maxSentenceSize = 1000;
enum KeyType { CAT, REST };
int keyType = REST;
std::pair<bool, std::wstring> Translate(const std::wstring& text, TranslationParam tlp)
{
if (!tlp.authKey.empty())
{
std::string translateFromComponent = tlp.translateFrom == L"?" ? "" : "&source_lang=" + WideStringToString(codes.at(tlp.translateFrom));
if (HttpRequest httpRequest{
L"Mozilla/5.0 Textractor",
tlp.authKey.find(L":fx") == std::string::npos ? L"api.deepl.com" : L"api-free.deepl.com",
L"POST",
keyType == CAT ? L"/v1/translate" : L"/v2/translate",
FormatString("text=%S&auth_key=%S&target_lang=%S", Escape(text), tlp.authKey, codes.at(tlp.translateTo)) + translateFromComponent,
L"Content-Type: application/x-www-form-urlencoded"
}; httpRequest && (httpRequest.response.find(L"translations") != std::string::npos || (httpRequest = HttpRequest{
L"Mozilla/5.0 Textractor",
tlp.authKey.find(L":fx") == std::string::npos ? L"api.deepl.com" : L"api-free.deepl.com",
L"POST",
(keyType = !keyType) == CAT ? L"/v1/translate" : L"/v2/translate",
FormatString("text=%S&auth_key=%S&target_lang=%S", Escape(text), tlp.authKey, codes.at(tlp.translateTo)) + translateFromComponent,
L"Content-Type: application/x-www-form-urlencoded"
})))
// Response formatted as JSON: translation starts with text":" and ends with "}]
if (auto translation = Copy(JSON::Parse(httpRequest.response)[L"translations"][0][L"text"].String())) return { true, translation.value() };
else return { false, FormatString(L"%s: %s", TRANSLATION_ERROR, httpRequest.response) };
else return { false, FormatString(L"%s (code=%u)", TRANSLATION_ERROR, httpRequest.errorCode) };
}
// the following code was reverse engineered from the DeepL website; it's as close as I could make it but I'm not sure what parts of this could be removed and still have it work
int id = 10000 * std::uniform_int_distribution(0, 9999)(std::random_device()) + 1;
int64_t r = _time64(nullptr), n = std::count(text.begin(), text.end(), L'i') + 1;
// user_preferred_langs? what should priority be? does timestamp do anything? other translation quality options?
auto body = FormatString(R"(
{
"id": %d,
"jsonrpc": "2.0",
"method": "LMT_handle_jobs",
"params": {
"priority": -1,
"timestamp": %lld,
"lang": {
"target_lang": "%.2S",
"source_lang_user_selected": "%S"
},
"jobs": [{
"raw_en_sentence": "%s",
"raw_en_context_before": [],
"kind": "default",
"preferred_num_beams": 1,
"quality": "fast",
"raw_en_context_after": []
}]
}
}
)", id, r + (n - r % n), codes.at(tlp.translateTo), codes.at(tlp.translateFrom), JSON::Escape(WideStringToString(text)));
// missing accept-encoding header since it fucks up HttpRequest
if (HttpRequest httpRequest{
L"Mozilla/5.0 Textractor",
L"www2.deepl.com",
L"POST",
L"/jsonrpc",
body,
L"Host: www2.deepl.com\r\nAccept-Language: en-US,en;q=0.5\r\nContent-type: application/json; charset=utf-8\r\nOrigin: https://www.deepl.com\r\nTE: Trailers",
INTERNET_DEFAULT_PORT,
L"https://www.deepl.com/translator",
WINHTTP_FLAG_SECURE
})
if (auto translation = Copy(JSON::Parse(httpRequest.response)[L"result"][L"translations"][0][L"beams"][0][L"postprocessed_sentence"].String())) return { true, translation.value() };
else return { false, FormatString(L"%s: %s", TRANSLATION_ERROR, httpRequest.response) };
else return { false, FormatString(L"%s (code=%u)", TRANSLATION_ERROR, httpRequest.errorCode) };
}

174
extensions/devtools.cpp Normal file
View File

@ -0,0 +1,174 @@
#include "devtools.h"
#include "module.h"
#include <ppltasks.h>
#include <ShlObj.h>
#include <QWebSocket>
#include <QMetaEnum>
#include <QFileDialog>
#include <QMouseEvent>
extern const char* CHROME_LOCATION;
extern const char* START_DEVTOOLS;
extern const char* STOP_DEVTOOLS;
extern const char* HIDE_CHROME;
extern const char* DEVTOOLS_STATUS;
extern const char* AUTO_START;
extern const char* TRANSLATION_PROVIDER;
extern QFormLayout* display;
extern Settings settings;
namespace
{
QLabel* statusLabel;
AutoHandle<> process = NULL;
QWebSocket webSocket;
std::atomic<int> idCounter = 0;
Synchronized<std::unordered_map<int, concurrency::task_completion_event<JSON::Value<wchar_t>>>> mapQueue;
void StatusChanged(QString status)
{
QMetaObject::invokeMethod(statusLabel, std::bind(&QLabel::setText, statusLabel, status));
}
void Start(std::wstring chromePath, bool headless)
{
if (process) DevTools::Close();
auto args = FormatString(
L"%s --proxy-server=direct:// --disable-extensions --disable-gpu --no-first-run --user-data-dir=\"%s\\devtoolscache\" --remote-debugging-port=9222",
chromePath,
std::filesystem::current_path().wstring()
);
args += headless ? L" --window-size=1920,1080 --headless" : L" --window-size=850,900";
DWORD exitCode = 0;
STARTUPINFOW DUMMY = { sizeof(DUMMY) };
PROCESS_INFORMATION processInfo = {};
if (!CreateProcessW(NULL, args.data(), nullptr, nullptr, FALSE, 0, nullptr, nullptr, &DUMMY, &processInfo)) return StatusChanged("StartupFailed");
CloseHandle(processInfo.hThread);
process = processInfo.hProcess;
if (HttpRequest httpRequest{
L"Mozilla/5.0 Textractor",
L"127.0.0.1",
L"POST",
L"/json/list",
"",
NULL,
9222,
NULL,
WINHTTP_FLAG_ESCAPE_DISABLE
})
if (auto list = Copy(JSON::Parse(httpRequest.response).Array())) if (auto it = std::find_if(
list->begin(),
list->end(),
[](const JSON::Value<wchar_t>& object) { return object[L"type"].String() && *object[L"type"].String() == L"page" && object[L"webSocketDebuggerUrl"].String(); }
); it != list->end()) return webSocket.open(S(*(*it)[L"webSocketDebuggerUrl"].String()));
StatusChanged("ConnectingFailed");
}
auto _ = ([]
{
QObject::connect(&webSocket, &QWebSocket::stateChanged,
[](QAbstractSocket::SocketState state) { StatusChanged(QMetaEnum::fromType<QAbstractSocket::SocketState>().valueToKey(state)); });
QObject::connect(&webSocket, &QWebSocket::textMessageReceived, [](QString message)
{
auto result = JSON::Parse(S(message));
auto mapQueue = ::mapQueue.Acquire();
if (auto id = result[L"id"].Number()) if (auto request = mapQueue->find((int)*id); request != mapQueue->end())
{
request->second.set(result);
mapQueue->erase(request);
}
});
}(), 0);
}
namespace DevTools
{
void Initialize()
{
QString chromePath = settings.value(CHROME_LOCATION).toString();
if (chromePath.isEmpty())
{
for (auto [_, process] : GetAllProcesses())
if (process && (process->find(L"\\chrome.exe") != std::string::npos || process->find(L"\\msedge.exe") != std::string::npos)) chromePath = S(process.value());
wchar_t programFiles[MAX_PATH + 100] = {};
for (auto folder : { CSIDL_PROGRAM_FILESX86, CSIDL_PROGRAM_FILES, CSIDL_LOCAL_APPDATA })
{
SHGetFolderPathW(NULL, folder, NULL, SHGFP_TYPE_CURRENT, programFiles);
wcscat_s(programFiles, L"/Google/Chrome/Application/chrome.exe");
if (std::filesystem::exists(programFiles)) chromePath = S(programFiles);
}
}
auto chromePathEdit = new QLineEdit(chromePath);
static struct : QObject
{
bool eventFilter(QObject* object, QEvent* event)
{
if (auto mouseEvent = dynamic_cast<QMouseEvent*>(event))
if (mouseEvent->button() == Qt::LeftButton)
if (QString chromePath = QFileDialog::getOpenFileName(nullptr, TRANSLATION_PROVIDER, "/", "Google Chrome (*.exe)"); !chromePath.isEmpty())
((QLineEdit*)object)->setText(chromePath);
return false;
}
} chromeSelector;
chromePathEdit->installEventFilter(&chromeSelector);
QObject::connect(chromePathEdit, &QLineEdit::textChanged, [chromePathEdit](QString path) { settings.setValue(CHROME_LOCATION, path); });
display->addRow(CHROME_LOCATION, chromePathEdit);
auto headlessCheck = new QCheckBox();
auto startButton = new QPushButton(START_DEVTOOLS), stopButton = new QPushButton(STOP_DEVTOOLS);
headlessCheck->setChecked(settings.value(HIDE_CHROME, true).toBool());
QObject::connect(headlessCheck, &QCheckBox::clicked, [](bool headless) { settings.setValue(HIDE_CHROME, headless); });
QObject::connect(startButton, &QPushButton::clicked, [chromePathEdit, headlessCheck] { Start(S(chromePathEdit->text()), headlessCheck->isChecked()); });
QObject::connect(stopButton, &QPushButton::clicked, &Close);
auto buttons = new QHBoxLayout();
buttons->addWidget(startButton);
buttons->addWidget(stopButton);
display->addRow(HIDE_CHROME, headlessCheck);
auto autoStartCheck = new QCheckBox();
autoStartCheck->setChecked(settings.value(AUTO_START, false).toBool());
QObject::connect(autoStartCheck, &QCheckBox::clicked, [](bool autoStart) { settings.setValue(AUTO_START, autoStart); });
display->addRow(AUTO_START, autoStartCheck);
display->addRow(buttons);
statusLabel = new QLabel("Stopped");
statusLabel->setFrameStyle(QFrame::Panel | QFrame::Sunken);
display->addRow(DEVTOOLS_STATUS, statusLabel);
if (autoStartCheck->isChecked()) QMetaObject::invokeMethod(startButton, &QPushButton::click, Qt::QueuedConnection);
}
void Close()
{
webSocket.close();
for (const auto& [_, task] : mapQueue.Acquire().contents) task.set_exception(std::runtime_error("closed"));
mapQueue->clear();
if (process)
{
TerminateProcess(process, 0);
WaitForSingleObject(process, 1000);
for (int retry = 0; ++retry < 20; Sleep(100))
try { std::filesystem::remove_all(L"devtoolscache"); break; }
catch (std::filesystem::filesystem_error) { continue; }
}
process = NULL;
StatusChanged("Stopped");
}
bool Connected()
{
return webSocket.state() == QAbstractSocket::ConnectedState;
}
JSON::Value<wchar_t> SendRequest(const char* method, const std::wstring& params)
{
concurrency::task_completion_event<JSON::Value<wchar_t>> response;
int id = idCounter += 1;
if (!Connected()) return {};
mapQueue->try_emplace(id, response);
QMetaObject::invokeMethod(&webSocket, std::bind(&QWebSocket::sendTextMessage, &webSocket, S(FormatString(LR"({"id":%d,"method":"%S","params":%s})", id, method, params))));
try { if (auto result = create_task(response).get()[L"result"]) return result; } catch (...) {}
return {};
}
}

10
extensions/devtools.h Normal file
View File

@ -0,0 +1,10 @@
#include "qtcommon.h"
#include "network.h"
namespace DevTools
{
void Initialize();
void Close();
bool Connected();
JSON::Value<wchar_t> SendRequest(const char* method, const std::wstring& params = L"{}");
}

View File

@ -0,0 +1,148 @@
#include "qtcommon.h"
#include "translatewrapper.h"
#include "devtools.h"
extern const wchar_t* ERROR_START_CHROME;
extern const wchar_t* TRANSLATION_ERROR;
const char* TRANSLATION_PROVIDER = "DevTools DeepL Translate";
const char* GET_API_KEY_FROM = nullptr;
extern const QStringList languagesTo
{
"Bulgarian",
"Chinese (Simplified)",
"Czech",
"Danish",
"Dutch",
"English (American)",
"English (British)",
"Estonian",
"Finnish",
"French",
"German",
"Greek",
"Hungarian",
"Italian",
"Japanese",
"Latvian",
"Lithuanian",
"Polish",
"Portuguese",
"Portuguese (Brazilian)",
"Romanian",
"Russian",
"Slovak",
"Slovenian",
"Spanish",
"Swedish"
},
languagesFrom =
{
"Bulgarian",
"Chinese",
"Czech",
"Danish",
"Dutch",
"English",
"Estonian",
"Finnish",
"French",
"German",
"Greek",
"Hungarian",
"Italian",
"Japanese",
"Latvian",
"Lithuanian",
"Polish",
"Portuguese",
"Romanian",
"Russian",
"Slovak",
"Slovenian",
"Spanish",
"Swedish"
};
extern const std::unordered_map<std::wstring, std::wstring> codes
{
{ { L"Bulgarian" }, { L"Bulgarian" } },
{ { L"Chinese" }, { L"Chinese" } },
{ { L"Chinese (Simplified)" }, { L"Chinese (simplified)" } },
{ { L"Czech" }, { L"Czech" } },
{ { L"Danish" }, { L"Danish" } },
{ { L"Dutch" }, { L"Dutch" } },
{ { L"English" }, { L"English" } },
{ { L"English (American)" }, { L"English (American)" } },
{ { L"English (British)" }, { L"English (British)" } },
{ { L"Estonian" }, { L"Estonian" } },
{ { L"Finnish" }, { L"Finnish" } },
{ { L"French" }, { L"French" } },
{ { L"German" }, { L"German" } },
{ { L"Greek" }, { L"Greek" } },
{ { L"Hungarian" }, { L"Hungarian" } },
{ { L"Italian" }, { L"Italian" } },
{ { L"Japanese" }, { L"Japanese" } },
{ { L"Latvian" }, { L"Latvian" } },
{ { L"Lithuanian" }, { L"Lithuanian" } },
{ { L"Polish" }, { L"Polish" } },
{ { L"Portuguese" }, { L"Portuguese" } },
{ { L"Portuguese (Brazilian)" }, { L"Portuguese (Brazilian)" } },
{ { L"Romanian" }, { L"Romanian" } },
{ { L"Russian" }, { L"Russian" } },
{ { L"Slovak" }, { L"Slovak" } },
{ { L"Slovenian" }, { L"Slovenian" } },
{ { L"Spanish" }, { L"Spanish" } },
{ { L"Swedish" }, { L"Swedish" } },
{ { L"?" }, { L"Detect language" } }
};
bool translateSelectedOnly = true, useRateLimiter = true, rateLimitSelected = false, useCache = true, useFilter = true;
int tokenCount = 30, rateLimitTimespan = 60000, maxSentenceSize = 2500;
BOOL WINAPI DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved)
{
switch (ul_reason_for_call)
{
case DLL_PROCESS_ATTACH:
{
DevTools::Initialize();
}
break;
case DLL_PROCESS_DETACH:
{
DevTools::Close();
}
break;
}
return TRUE;
}
std::pair<bool, std::wstring> Translate(const std::wstring& text, TranslationParam tlp)
{
if (!DevTools::Connected()) return { false, FormatString(L"%s: %s", TRANSLATION_ERROR, ERROR_START_CHROME) };
// DevTools can't handle concurrent translations yet
static std::mutex translationMutex;
std::scoped_lock lock(translationMutex);
std::wstring escaped; // DeepL breaks with slash in input
for (auto ch : text) ch == '/' ? escaped += L"\\/" : escaped += ch;
DevTools::SendRequest("Page.navigate", FormatString(LR"({"url":"https://www.deepl.com/en/translator#en/en/%s"})", Escape(escaped)));
for (int retry = 0; ++retry < 20; Sleep(100))
if (Copy(DevTools::SendRequest("Runtime.evaluate", LR"({"expression":"document.readyState"})")[L"result"][L"value"].String()) == L"complete") break;
DevTools::SendRequest("Runtime.evaluate", FormatString(LR"({"expression":"
document.querySelector('.lmt__language_select--source').querySelector('button').click();
document.evaluate(`//*[text()='%s']`,document.querySelector('.lmt__language_select__menu'),null,XPathResult.FIRST_ORDERED_NODE_TYPE,null).singleNodeValue.click();
document.querySelector('.lmt__language_select--target').querySelector('button').click();
document.evaluate(`//*[text()='%s']`,document.querySelector('.lmt__language_select__menu'),null,XPathResult.FIRST_ORDERED_NODE_TYPE,null).singleNodeValue.click();
"})", codes.at(tlp.translateFrom), codes.at(tlp.translateTo)));
for (int retry = 0; ++retry < 100; Sleep(100))
if (auto translation = Copy(DevTools::SendRequest("Runtime.evaluate",
LR"({"expression":"document.querySelector('#target-dummydiv').innerHTML.trim() ","returnByValue":true})"
)[L"result"][L"value"].String())) if (!translation->empty()) return { true, translation.value() };
if (auto errorMessage = Copy(DevTools::SendRequest("Runtime.evaluate",
LR"({"expression":"document.querySelector('div.lmt__system_notification').innerHTML","returnByValue":true})"
)[L"result"][L"value"].String())) return { false, FormatString(L"%s: %s", TRANSLATION_ERROR, errorMessage.value()) };
return { false, TRANSLATION_ERROR };
}

View File

@ -0,0 +1,82 @@
#include "qtcommon.h"
#include "translatewrapper.h"
#include "devtools.h"
extern const wchar_t* ERROR_START_CHROME;
extern const wchar_t* TRANSLATION_ERROR;
const char* TRANSLATION_PROVIDER = "DevTools Papago Translate";
const char* GET_API_KEY_FROM = nullptr;
extern const QStringList languagesTo
{
"Chinese (Simplified)",
"Chinese (Traditional)",
"English",
"French",
"German",
"Hindi",
"Indonesian",
"Italian",
"Japanese",
"Korean",
"Portuguese",
"Russian",
"Spanish",
"Thai",
"Vietnamese",
}, languagesFrom = languagesTo;
extern const std::unordered_map<std::wstring, std::wstring> codes
{
{ { L"Chinese (Simplified)" }, { L"zh-CN" } },
{ { L"Chinese (Traditional)" }, { L"zt-TW" } },
{ { L"English" }, { L"en" } },
{ { L"French" }, { L"fr" } },
{ { L"German" }, { L"de" } },
{ { L"Hindi" }, { L"hi" } },
{ { L"Indonesian" }, { L"id" } },
{ { L"Italian" }, { L"it" } },
{ { L"Japanese" }, { L"ja" } },
{ { L"Korean" }, { L"ko" } },
{ { L"Portuguese" }, { L"pt" } },
{ { L"Russian" }, { L"ru" } },
{ { L"Spanish" }, { L"es" } },
{ { L"Thai" }, { L"th" } },
{ { L"Vietnamese" }, { L"vi" } },
{ { L"?" }, { L"auto" } }
};
bool translateSelectedOnly = true, useRateLimiter = true, rateLimitSelected = false, useCache = true, useFilter = true;
int tokenCount = 30, rateLimitTimespan = 60000, maxSentenceSize = 2500;
BOOL WINAPI DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved)
{
switch (ul_reason_for_call)
{
case DLL_PROCESS_ATTACH:
{
DevTools::Initialize();
}
break;
case DLL_PROCESS_DETACH:
{
DevTools::Close();
}
break;
}
return TRUE;
}
std::pair<bool, std::wstring> Translate(const std::wstring& text, TranslationParam tlp)
{
if (!DevTools::Connected()) return { false, FormatString(L"%s: %s", TRANSLATION_ERROR, ERROR_START_CHROME) };
// DevTools can't handle concurrent translations yet
static std::mutex translationMutex;
std::scoped_lock lock(translationMutex);
DevTools::SendRequest("Page.navigate", FormatString(LR"({"url":"https://papago.naver.com/?sk=%s&tk=%s&st=%s"})", codes.at(tlp.translateFrom), codes.at(tlp.translateTo), Escape(text)));
for (int retry = 0; ++retry < 100; Sleep(100))
if (auto translation = Copy(DevTools::SendRequest("Runtime.evaluate",
LR"({"expression":"document.querySelector('#txtTarget').textContent.trim() ","returnByValue":true})"
)[L"result"][L"value"].String())) if (!translation->empty()) return { true, translation.value() };
return { false, TRANSLATION_ERROR };
}

View File

@ -0,0 +1,152 @@
#include "qtcommon.h"
#include "translatewrapper.h"
#include "devtools.h"
extern const wchar_t* ERROR_START_CHROME;
extern const wchar_t* TRANSLATION_ERROR;
const char* TRANSLATION_PROVIDER = "DevTools Systran Translate";
const char* GET_API_KEY_FROM = nullptr;
extern const QStringList languagesTo
{
"Albanian",
"Arabic",
"Bengali",
"Bulgarian",
"Burmese",
"Catalan",
"Chinese (Simplified)",
"Chinese (Traditional)",
"Croatian",
"Czech",
"Danish",
"Dutch",
"English",
"Estonian",
"Finnish",
"French",
"German",
"Greek",
"Hebrew",
"Hindi",
"Hungarian",
"Indonesian",
"Italian",
"Japanese",
"Korean",
"Latvian",
"Lithuanian",
"Malay",
"Norwegian",
"Pashto",
"Persian",
"Polish",
"Portuguese",
"Romanian",
"Russian",
"Serbian",
"Slovak",
"Slovenian",
"Somali",
"Spanish",
"Swedish",
"Tagalog",
"Tamil",
"Thai",
"Turkish",
"Ukrainian",
"Urdu",
"Vietnamese"
}, languagesFrom = languagesTo;
extern const std::unordered_map<std::wstring, std::wstring> codes
{
{ { L"Albanian" }, { L"sq" } },
{ { L"Arabic" }, { L"ar" } },
{ { L"Bengali" }, { L"bn" } },
{ { L"Bulgarian" }, { L"bg" } },
{ { L"Burmese" }, { L"my" } },
{ { L"Catalan" }, { L"ca" } },
{ { L"Chinese (Simplified)" }, { L"zh" } },
{ { L"Chinese (Traditional)" }, { L"zt" } },
{ { L"Croatian" }, { L"hr" } },
{ { L"Czech" }, { L"cs" } },
{ { L"Danish" }, { L"da" } },
{ { L"Dutch" }, { L"nl" } },
{ { L"English" }, { L"en" } },
{ { L"Estonian" }, { L"et" } },
{ { L"Finnish" }, { L"fi" } },
{ { L"French" }, { L"fr" } },
{ { L"German" }, { L"de" } },
{ { L"Greek" }, { L"el" } },
{ { L"Hebrew" }, { L"he" } },
{ { L"Hindi" }, { L"hi" } },
{ { L"Hungarian" }, { L"hu" } },
{ { L"Indonesian" }, { L"id" } },
{ { L"Italian" }, { L"it" } },
{ { L"Japanese" }, { L"ja" } },
{ { L"Korean" }, { L"ko" } },
{ { L"Latvian" }, { L"lv" } },
{ { L"Lithuanian" }, { L"lt" } },
{ { L"Malay" }, { L"ms" } },
{ { L"Norwegian" }, { L"no" } },
{ { L"Pashto" }, { L"ps" } },
{ { L"Persian" }, { L"fa" } },
{ { L"Polish" }, { L"pl" } },
{ { L"Portuguese" }, { L"pt" } },
{ { L"Romanian" }, { L"ro" } },
{ { L"Russian" }, { L"ru" } },
{ { L"Serbian" }, { L"sr" } },
{ { L"Slovak" }, { L"sk" } },
{ { L"Slovenian" }, { L"sl" } },
{ { L"Somali" }, { L"so" } },
{ { L"Spanish" }, { L"es" } },
{ { L"Swedish" }, { L"sv" } },
{ { L"Tagalog" }, { L"tl" } },
{ { L"Tamil" }, { L"ta" } },
{ { L"Thai" }, { L"th" } },
{ { L"Turkish" }, { L"tr" } },
{ { L"Ukrainian" }, { L"uk" } },
{ { L"Urdu" }, { L"ur" } },
{ { L"Vietnamese" }, { L"vi" } },
{ { L"?" }, { L"autodetect" } }
};
bool translateSelectedOnly = true, useRateLimiter = true, rateLimitSelected = false, useCache = true, useFilter = true;
int tokenCount = 30, rateLimitTimespan = 60000, maxSentenceSize = 2500;
BOOL WINAPI DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved)
{
switch (ul_reason_for_call)
{
case DLL_PROCESS_ATTACH:
{
DevTools::Initialize();
}
break;
case DLL_PROCESS_DETACH:
{
DevTools::Close();
}
break;
}
return TRUE;
}
std::pair<bool, std::wstring> Translate(const std::wstring& text, TranslationParam tlp)
{
if (!DevTools::Connected()) return { false, FormatString(L"%s: %s", TRANSLATION_ERROR, ERROR_START_CHROME) };
// DevTools can't handle concurrent translations yet
static std::mutex translationMutex;
std::scoped_lock lock(translationMutex);
DevTools::SendRequest(
"Page.navigate",
FormatString(LR"({"url":"https://translate.systran.net/?source=%s&target=%s&input=%s"})", codes.at(tlp.translateFrom), codes.at(tlp.translateTo), Escape(text))
);
for (int retry = 0; ++retry < 100; Sleep(100))
if (auto translation = Copy(DevTools::SendRequest("Runtime.evaluate",
LR"({"expression":"document.querySelector('#outputEditor').textContent.trim() ","returnByValue":true})"
)[L"result"][L"value"].String())) if (!translation->empty()) return { true, translation.value() };
return { false, TRANSLATION_ERROR };
}

21
extensions/extension.h Normal file
View File

@ -0,0 +1,21 @@
#pragma once
struct InfoForExtension
{
const char* name;
int64_t value;
};
struct SentenceInfo
{
const InfoForExtension* infoArray;
int64_t operator[](std::string_view propertyName)
{
for (auto info = infoArray; info->name; ++info) // nullptr name marks end of info array
if (propertyName == info->name) return info->value;
return *(int*)0xDEAD = 0; // gives better error message than alternatives
}
};
struct SKIP {};
inline void Skip() { throw SKIP(); }

View File

@ -0,0 +1,33 @@
#include "extension.h"
bool ProcessSentence(std::wstring& sentence, SentenceInfo sentenceInfo);
/*
You shouldn't mess with this or even look at it unless you're certain you know what you're doing.
Param sentence: pointer to sentence received by Textractor (UTF-16).
This can be modified. Textractor uses the modified sentence for future processing and display. If empty (starts with null terminator), Textractor will destroy it.
Textractor will display the sentence after all extensions have had a chance to process and/or modify it.
The buffer is allocated using HeapAlloc(). If you want to make it larger, please use HeapReAlloc().
Param sentenceInfo: pointer to array containing misc info about the sentence. End of array is marked with name being nullptr.
Return value: the buffer used for the sentence. Remember to return a new pointer if HeapReAlloc() gave you one.
This function may be run concurrently with itself: please make sure it's thread safe.
It will not be run concurrently with DllMain.
*/
extern "C" __declspec(dllexport) wchar_t* OnNewSentence(wchar_t* sentence, const InfoForExtension* sentenceInfo)
{
try
{
std::wstring sentenceCopy(sentence);
int oldSize = sentenceCopy.size();
if (ProcessSentence(sentenceCopy, SentenceInfo{ sentenceInfo }))
{
if (sentenceCopy.size() > oldSize) sentence = (wchar_t*)HeapReAlloc(GetProcessHeap(), HEAP_GENERATE_EXCEPTIONS, sentence, (sentenceCopy.size() + 1) * sizeof(wchar_t));
wcscpy_s(sentence, sentenceCopy.size() + 1, sentenceCopy.c_str());
}
}
catch (SKIP)
{
*sentence = L'\0';
}
return sentence;
}

View File

@ -0,0 +1,8 @@
#include "extension.h"
bool ProcessSentence(std::wstring& sentence, SentenceInfo sentenceInfo)
{
if (sentenceInfo["text number"] == 0) return false;
sentence += L"\n";
return true;
}

604
extensions/extrawindow.cpp Normal file
View File

@ -0,0 +1,604 @@
#include "qtcommon.h"
#include "extension.h"
#include "ui_extrawindow.h"
#include "blockmarkup.h"
#include <fstream>
#include <process.h>
#include <QRegularExpression>
#include <QColorDialog>
#include <QFontDialog>
#include <QMenu>
#include <QPainter>
#include <QGraphicsEffect>
#include <QFontMetrics>
#include <QMouseEvent>
#include <QWheelEvent>
#include <QScrollArea>
#include <QAbstractNativeEventFilter>
extern const char* EXTRA_WINDOW_INFO;
extern const char* TOPMOST;
extern const char* OPACITY;
extern const char* SHOW_ORIGINAL;
extern const char* ORIGINAL_AFTER_TRANSLATION;
extern const char* SIZE_LOCK;
extern const char* POSITION_LOCK;
extern const char* CENTERED_TEXT;
extern const char* AUTO_RESIZE_WINDOW_HEIGHT;
extern const char* CLICK_THROUGH;
extern const char* HIDE_MOUSEOVER;
extern const char* DICTIONARY;
extern const char* DICTIONARY_INSTRUCTIONS;
extern const char* BG_COLOR;
extern const char* TEXT_COLOR;
extern const char* TEXT_OUTLINE;
extern const char* OUTLINE_COLOR;
extern const char* OUTLINE_SIZE;
extern const char* OUTLINE_SIZE_INFO;
extern const char* FONT;
constexpr auto DICTIONARY_SAVE_FILE = u8"SavedDictionary.txt";
constexpr int CLICK_THROUGH_HOTKEY = 0xc0d0;
QColor colorPrompt(QWidget* parent, QColor default, const QString& title, bool customOpacity = true)
{
QColor color = QColorDialog::getColor(default, parent, title);
if (customOpacity) color.setAlpha(255 * QInputDialog::getDouble(parent, title, OPACITY, default.alpha() / 255.0, 0, 1, 3, nullptr, Qt::WindowCloseButtonHint));
return color;
}
struct PrettyWindow : QDialog, Localizer
{
PrettyWindow(const char* name)
{
ui.setupUi(this);
ui.display->setGraphicsEffect(outliner = new Outliner);
setWindowFlags(Qt::FramelessWindowHint);
setAttribute(Qt::WA_TranslucentBackground);
settings.beginGroup(name);
QFont font = ui.display->font();
if (font.fromString(settings.value(FONT, font.toString()).toString())) ui.display->setFont(font);
SetBackgroundColor(settings.value(BG_COLOR, backgroundColor).value<QColor>());
SetTextColor(settings.value(TEXT_COLOR, TextColor()).value<QColor>());
outliner->color = settings.value(OUTLINE_COLOR, outliner->color).value<QColor>();
outliner->size = settings.value(OUTLINE_SIZE, outliner->size).toDouble();
autoHide = settings.value(HIDE_MOUSEOVER, autoHide).toBool();
menu.addAction(FONT, this, &PrettyWindow::RequestFont);
menu.addAction(BG_COLOR, [this] { SetBackgroundColor(colorPrompt(this, backgroundColor, BG_COLOR)); });
menu.addAction(TEXT_COLOR, [this] { SetTextColor(colorPrompt(this, TextColor(), TEXT_COLOR)); });
QAction* outlineAction = menu.addAction(TEXT_OUTLINE, this, &PrettyWindow::SetOutline);
outlineAction->setCheckable(true);
outlineAction->setChecked(outliner->size >= 0);
QAction* autoHideAction = menu.addAction(HIDE_MOUSEOVER, this, [this](bool autoHide) { settings.setValue(HIDE_MOUSEOVER, this->autoHide = autoHide); });
autoHideAction->setCheckable(true);
autoHideAction->setChecked(autoHide);
connect(this, &QDialog::customContextMenuRequested, [this](QPoint point) { menu.exec(mapToGlobal(point)); });
connect(ui.display, &QLabel::customContextMenuRequested, [this](QPoint point) { menu.exec(ui.display->mapToGlobal(point)); });
startTimer(50);
}
~PrettyWindow()
{
settings.sync();
}
Ui::ExtraWindow ui;
protected:
void timerEvent(QTimerEvent*) override
{
if (autoHide && geometry().contains(QCursor::pos()))
{
if (!hidden)
{
if (backgroundColor.alphaF() > 0.05) backgroundColor.setAlphaF(0.05);
if (outliner->color.alphaF() > 0.05) outliner->color.setAlphaF(0.05);
QColor hiddenTextColor = TextColor();
if (hiddenTextColor.alphaF() > 0.05) hiddenTextColor.setAlphaF(0.05);
ui.display->setPalette(QPalette(hiddenTextColor, {}, {}, {}, {}, {}, {}));
hidden = true;
repaint();
}
}
else if (hidden)
{
backgroundColor.setAlpha(settings.value(BG_COLOR).value<QColor>().alpha());
outliner->color.setAlpha(settings.value(OUTLINE_COLOR).value<QColor>().alpha());
ui.display->setPalette(QPalette(settings.value(TEXT_COLOR).value<QColor>(), {}, {}, {}, {}, {}, {}));
hidden = false;
repaint();
}
}
QMenu menu{ ui.display };
Settings settings{ this };
private:
void RequestFont()
{
if (QFont font = QFontDialog::getFont(&ok, ui.display->font(), this, FONT); ok)
{
settings.setValue(FONT, font.toString());
ui.display->setFont(font);
}
};
void SetBackgroundColor(QColor color)
{
if (!color.isValid()) return;
if (color.alpha() == 0) color.setAlpha(1);
backgroundColor = color;
repaint();
settings.setValue(BG_COLOR, color.name(QColor::HexArgb));
};
QColor TextColor()
{
return ui.display->palette().color(QPalette::WindowText);
}
void SetTextColor(QColor color)
{
if (!color.isValid()) return;
ui.display->setPalette(QPalette(color, {}, {}, {}, {}, {}, {}));
settings.setValue(TEXT_COLOR, color.name(QColor::HexArgb));
};
void SetOutline(bool enable)
{
if (enable)
{
QColor color = colorPrompt(this, outliner->color, OUTLINE_COLOR);
if (color.isValid()) outliner->color = color;
outliner->size = QInputDialog::getDouble(this, OUTLINE_SIZE, OUTLINE_SIZE_INFO, -outliner->size, 0, INT_MAX, 2, nullptr, Qt::WindowCloseButtonHint);
}
else outliner->size = -outliner->size;
settings.setValue(OUTLINE_COLOR, outliner->color.name(QColor::HexArgb));
settings.setValue(OUTLINE_SIZE, outliner->size);
}
void paintEvent(QPaintEvent*) override
{
QPainter(this).fillRect(rect(), backgroundColor);
}
bool autoHide = false, hidden = false;
QColor backgroundColor{ palette().window().color() };
struct Outliner : QGraphicsEffect
{
void draw(QPainter* painter) override
{
if (size < 0) return drawSource(painter);
QPoint offset;
QPixmap pixmap = sourcePixmap(Qt::LogicalCoordinates, &offset);
offset.setX(offset.x() + size);
for (auto offset2 : Array<QPointF>{ { 0, 1 }, { 0, -1 }, { 1, 0 }, { -1, 0 }, { 1, 1 }, { 1, -1 }, { -1, 1 }, { -1, -1 } })
{
QImage outline = pixmap.toImage();
QPainter outlinePainter(&outline);
outlinePainter.setCompositionMode(QPainter::CompositionMode_SourceIn);
outlinePainter.fillRect(outline.rect(), color);
painter->drawImage(offset + offset2 * size, outline);
}
painter->drawPixmap(offset, pixmap);
}
QColor color{ Qt::black };
double size = -0.5;
}* outliner;
};
class ExtraWindow : public PrettyWindow, QAbstractNativeEventFilter
{
public:
ExtraWindow() : PrettyWindow("Extra Window")
{
ui.display->setTextFormat(Qt::PlainText);
if (settings.contains(WINDOW) && QApplication::screenAt(settings.value(WINDOW).toRect().bottomRight())) setGeometry(settings.value(WINDOW).toRect());
for (auto [name, default, slot] : Array<const char*, bool, void(ExtraWindow::*)(bool)>{
{ TOPMOST, false, &ExtraWindow::SetTopmost },
{ SIZE_LOCK, false, &ExtraWindow::SetSizeLock },
{ POSITION_LOCK, false, &ExtraWindow::SetPositionLock },
{ CENTERED_TEXT, false, &ExtraWindow::SetCenteredText },
{ AUTO_RESIZE_WINDOW_HEIGHT, false, &ExtraWindow::SetAutoResize },
{ SHOW_ORIGINAL, true, &ExtraWindow::SetShowOriginal },
{ ORIGINAL_AFTER_TRANSLATION, true, &ExtraWindow::SetShowOriginalAfterTranslation },
{ DICTIONARY, false, &ExtraWindow::SetUseDictionary },
})
{
// delay processing anything until Textractor has finished initializing
QMetaObject::invokeMethod(this, std::bind(slot, this, default = settings.value(name, default).toBool()), Qt::QueuedConnection);
auto action = menu.addAction(name, this, slot);
action->setCheckable(true);
action->setChecked(default);
}
menu.addAction(CLICK_THROUGH, this, &ExtraWindow::ToggleClickThrough);
ui.display->installEventFilter(this);
qApp->installNativeEventFilter(this);
QMetaObject::invokeMethod(this, [this]
{
RegisterHotKey((HWND)winId(), CLICK_THROUGH_HOTKEY, MOD_ALT | MOD_NOREPEAT, 0x58);
show();
AddSentence(EXTRA_WINDOW_INFO);
}, Qt::QueuedConnection);
}
~ExtraWindow()
{
settings.setValue(WINDOW, geometry());
}
void AddSentence(QString sentence)
{
sanitize(sentence);
sentence.chop(std::distance(std::remove(sentence.begin(), sentence.end(), QChar::Tabulation), sentence.end()));
sentenceHistory.push_back(sentence);
if (sentenceHistory.size() > 1000) sentenceHistory.erase(sentenceHistory.begin());
historyIndex = sentenceHistory.size() - 1;
DisplaySentence();
}
private:
void DisplaySentence()
{
if (sentenceHistory.empty()) return;
QString sentence = sentenceHistory[historyIndex];
if (sentence.contains(u8"\x200b \n"))
if (!showOriginal) sentence = sentence.split(u8"\x200b \n")[1];
else if (showOriginalAfterTranslation) sentence = sentence.split(u8"\x200b \n")[1] + "\n" + sentence.split(u8"\x200b \n")[0];
if (sizeLock && !autoResize)
{
QFontMetrics fontMetrics(ui.display->font(), ui.display);
int low = 0, high = sentence.size(), last = 0;
while (low <= high)
{
int mid = (low + high) / 2;
if (fontMetrics.boundingRect(0, 0, ui.display->width(), INT_MAX, Qt::TextWordWrap, sentence.left(mid)).height() <= ui.display->height())
{
last = mid;
low = mid + 1;
}
else high = mid - 1;
}
sentence = sentence.left(last);
}
ui.display->setText(sentence);
if (autoResize)
resize(width(), height() - ui.display->height() +
QFontMetrics(ui.display->font(), ui.display).boundingRect(0, 0, ui.display->width(), INT_MAX, Qt::TextWordWrap, sentence).height()
);
}
void SetTopmost(bool topmost)
{
for (auto window : { winId(), dictionaryWindow.winId() })
SetWindowPos((HWND)window, topmost ? HWND_TOPMOST : HWND_NOTOPMOST, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE);
settings.setValue(TOPMOST, topmost);
};
void SetPositionLock(bool locked)
{
settings.setValue(POSITION_LOCK, posLock = locked);
};
void SetSizeLock(bool locked)
{
setSizeGripEnabled(!locked);
settings.setValue(SIZE_LOCK, sizeLock = locked);
};
void SetCenteredText(bool centeredText)
{
ui.display->setAlignment(centeredText ? Qt::AlignHCenter : Qt::AlignLeft);
settings.setValue(CENTERED_TEXT, this->centeredText = centeredText);
};
void SetAutoResize(bool autoResize)
{
settings.setValue(AUTO_RESIZE_WINDOW_HEIGHT, this->autoResize = autoResize);
DisplaySentence();
};
void SetShowOriginal(bool showOriginal)
{
settings.setValue(SHOW_ORIGINAL, this->showOriginal = showOriginal);
DisplaySentence();
};
void SetShowOriginalAfterTranslation(bool showOriginalAfterTranslation)
{
settings.setValue(ORIGINAL_AFTER_TRANSLATION, this->showOriginalAfterTranslation = showOriginalAfterTranslation);
DisplaySentence();
};
void SetUseDictionary(bool useDictionary)
{
if (useDictionary)
{
dictionaryWindow.UpdateDictionary();
if (dictionaryWindow.dictionary.empty())
{
std::ofstream(DICTIONARY_SAVE_FILE) << u8"\ufeff" << DICTIONARY_INSTRUCTIONS;
_spawnlp(_P_DETACH, "notepad", "notepad", DICTIONARY_SAVE_FILE, NULL); // show file to user
}
}
settings.setValue(DICTIONARY, this->useDictionary = useDictionary);
}
void ToggleClickThrough()
{
clickThrough = !clickThrough;
for (auto window : { winId(), dictionaryWindow.winId() })
{
unsigned exStyle = GetWindowLongPtrW((HWND)window, GWL_EXSTYLE);
if (clickThrough) exStyle |= WS_EX_TRANSPARENT;
else exStyle &= ~WS_EX_TRANSPARENT;
SetWindowLongPtrW((HWND)window, GWL_EXSTYLE, exStyle);
}
};
void ShowDictionary(QPoint mouse)
{
QString sentence = ui.display->text();
const QFont& font = ui.display->font();
if (cachedDisplayInfo.CompareExchange(ui.display))
{
QFontMetrics fontMetrics(font, ui.display);
int flags = Qt::TextWordWrap | (ui.display->alignment() & (Qt::AlignLeft | Qt::AlignHCenter));
textPositionMap.clear();
for (int i = 0, height = 0, lineBreak = 0; i < sentence.size(); ++i)
{
int block = 1;
for (int charHeight = fontMetrics.boundingRect(0, 0, 1, INT_MAX, flags, sentence.mid(i, 1)).height();
i + block < sentence.size() && fontMetrics.boundingRect(0, 0, 1, INT_MAX, flags, sentence.mid(i, block + 1)).height() < charHeight * 1.5; ++block);
auto boundingRect = fontMetrics.boundingRect(0, 0, ui.display->width(), INT_MAX, flags, sentence.left(i + block));
if (boundingRect.height() > height)
{
height = boundingRect.height();
lineBreak = i;
}
textPositionMap.push_back({
fontMetrics.boundingRect(0, 0, ui.display->width(), INT_MAX, flags, sentence.mid(lineBreak, i - lineBreak + 1)).right() + 1,
height
});
}
}
int i;
for (i = 0; i < textPositionMap.size(); ++i) if (textPositionMap[i].y() > mouse.y() && textPositionMap[i].x() > mouse.x()) break;
if (i == textPositionMap.size() || (mouse - textPositionMap[i]).manhattanLength() > font.pointSize() * 3) return dictionaryWindow.hide();
if (sentence.mid(i) == dictionaryWindow.term) return dictionaryWindow.ShowDefinition();
dictionaryWindow.ui.display->setFixedWidth(ui.display->width() * 3 / 4);
dictionaryWindow.SetTerm(sentence.mid(i));
int left = i == 0 ? 0 : textPositionMap[i - 1].x(), right = textPositionMap[i].x(),
x = textPositionMap[i].x() > ui.display->width() / 2 ? -dictionaryWindow.width() + (right * 3 + left) / 4 : (left * 3 + right) / 4, y = 0;
for (auto point : textPositionMap) if (point.y() > y && point.y() < textPositionMap[i].y()) y = point.y();
dictionaryWindow.move(ui.display->mapToGlobal(QPoint(x, y - dictionaryWindow.height())));
}
bool nativeEventFilter(const QByteArray&, void* message, long* result) override
{
auto msg = (MSG*)message;
if (msg->message == WM_HOTKEY)
if (msg->wParam == CLICK_THROUGH_HOTKEY) return ToggleClickThrough(), true;
return false;
}
bool eventFilter(QObject*, QEvent* event) override
{
if (event->type() == QEvent::MouseButtonPress) mousePressEvent((QMouseEvent*)event);
return false;
}
void timerEvent(QTimerEvent* event) override
{
if (useDictionary && QCursor::pos() != oldPos && (!dictionaryWindow.isVisible() || !dictionaryWindow.geometry().contains(QCursor::pos())))
ShowDictionary(ui.display->mapFromGlobal(QCursor::pos()));
PrettyWindow::timerEvent(event);
}
void mousePressEvent(QMouseEvent* event) override
{
dictionaryWindow.hide();
oldPos = event->globalPos();
}
void mouseMoveEvent(QMouseEvent* event) override
{
if (!posLock) move(pos() + event->globalPos() - oldPos);
oldPos = event->globalPos();
}
void wheelEvent(QWheelEvent* event) override
{
int scroll = event->angleDelta().y();
if (scroll > 0 && historyIndex > 0) --historyIndex;
if (scroll < 0 && historyIndex + 1 < sentenceHistory.size()) ++historyIndex;
DisplaySentence();
}
bool sizeLock, posLock, centeredText, autoResize, showOriginal, showOriginalAfterTranslation, useDictionary, clickThrough;
QPoint oldPos;
class
{
public:
bool CompareExchange(QLabel* display)
{
if (display->text() == text && display->font() == font && display->width() == width && display->alignment() == alignment) return false;
text = display->text();
font = display->font();
width = display->width();
alignment = display->alignment();
return true;
}
private:
QString text;
QFont font;
int width;
Qt::Alignment alignment;
} cachedDisplayInfo;
std::vector<QPoint> textPositionMap;
std::vector<QString> sentenceHistory;
int historyIndex = 0;
class DictionaryWindow : public PrettyWindow
{
public:
DictionaryWindow() : PrettyWindow("Dictionary Window")
{
ui.display->setSizePolicy({ QSizePolicy::Fixed, QSizePolicy::Minimum });
}
void UpdateDictionary()
{
try
{
if (dictionaryFileLastWrite == std::filesystem::last_write_time(DICTIONARY_SAVE_FILE)) return;
dictionaryFileLastWrite = std::filesystem::last_write_time(DICTIONARY_SAVE_FILE);
}
catch (std::filesystem::filesystem_error) { return; }
dictionary.clear();
charStorage.clear();
auto StoreCopy = [&](std::string_view string)
{
auto location = &*charStorage.insert(charStorage.end(), string.begin(), string.end());
charStorage.push_back(0);
return location;
};
charStorage.reserve(std::filesystem::file_size(DICTIONARY_SAVE_FILE));
std::ifstream stream(DICTIONARY_SAVE_FILE);
BlockMarkupIterator savedDictionary(stream, Array<std::string_view>{ "|TERM|", "|DEFINITION|" });
while (auto read = savedDictionary.Next())
{
const auto& [terms, definition] = read.value();
auto storedDefinition = StoreCopy(definition);
std::string_view termsView = terms;
size_t start = 0, end = termsView.find("|TERM|");
while (end != std::string::npos)
{
dictionary.push_back(DictionaryEntry{ StoreCopy(termsView.substr(start, end - start)), storedDefinition });
start = end + 6;
end = termsView.find("|TERM|", start);
}
dictionary.push_back(DictionaryEntry{ StoreCopy(termsView.substr(start)), storedDefinition });
}
std::stable_sort(dictionary.begin(), dictionary.end());
inflections.clear();
stream.seekg(0);
BlockMarkupIterator savedInflections(stream, Array<std::string_view>{ "|ROOT|", "|INFLECTS TO|", "|NAME|" });
while (auto read = savedInflections.Next())
{
const auto& [root, inflectsTo, name] = read.value();
if (!inflections.emplace_back(Inflection{
S(root),
QRegularExpression(QRegularExpression::anchoredPattern(S(inflectsTo)), QRegularExpression::UseUnicodePropertiesOption),
S(name)
}).inflectsTo.isValid()) TEXTRACTOR_MESSAGE(L"Invalid regex: %s", StringToWideString(inflectsTo));
}
}
void SetTerm(QString term)
{
this->term = term;
UpdateDictionary();
definitions.clear();
definitionIndex = 0;
std::unordered_set<const char*> foundDefinitions;
for (term = term.left(100); !term.isEmpty(); term.chop(1))
for (const auto& [rootTerm, definition, inflections] : LookupDefinitions(term, foundDefinitions))
definitions.push_back(
QStringLiteral("<h3>%1 (%5/%6)</h3><small>%2%3</small>%4").arg(
term.split("<<")[0].toHtmlEscaped(),
rootTerm.split("<<")[0].toHtmlEscaped(),
inflections.join(""),
definition
)
);
for (int i = 0; i < definitions.size(); ++i) definitions[i] = definitions[i].arg(i + 1).arg(definitions.size());
ShowDefinition();
}
void ShowDefinition()
{
if (definitions.empty()) return hide();
ui.display->setText(definitions[definitionIndex]);
adjustSize();
resize(width(), 1);
show();
}
struct DictionaryEntry
{
const char* term;
const char* definition;
bool operator<(DictionaryEntry other) const { return strcmp(term, other.term) < 0; }
};
std::vector<DictionaryEntry> dictionary;
QString term;
private:
struct LookupResult
{
QString term;
QString definition;
QStringList inflectionsUsed;
};
std::vector<LookupResult> LookupDefinitions(QString term, std::unordered_set<const char*>& foundDefinitions, QStringList inflectionsUsed = {})
{
std::vector<LookupResult> results;
for (auto [it, end] = std::equal_range(dictionary.begin(), dictionary.end(), DictionaryEntry{ term.toUtf8() }); it != end; ++it)
if (foundDefinitions.emplace(it->definition).second) results.push_back({ term, it->definition, inflectionsUsed });
for (const auto& inflection : inflections) if (auto match = inflection.inflectsTo.match(term); match.hasMatch())
{
QStringList currentInflectionsUsed = inflectionsUsed;
currentInflectionsUsed.push_front(inflection.name);
QString root;
for (const auto& ch : inflection.root) root += ch.isDigit() ? match.captured(ch.digitValue()) : ch;
for (const auto& definition : LookupDefinitions(root, foundDefinitions, currentInflectionsUsed)) results.push_back(definition);
}
return results;
}
void wheelEvent(QWheelEvent* event) override
{
int scroll = event->angleDelta().y();
if (scroll > 0 && definitionIndex > 0) definitionIndex -= 1;
if (scroll < 0 && definitionIndex + 1 < definitions.size()) definitionIndex += 1;
int oldHeight = height();
ShowDefinition();
move(x(), y() + oldHeight - height());
}
struct Inflection
{
QString root;
QRegularExpression inflectsTo;
QString name;
};
std::vector<Inflection> inflections;
std::filesystem::file_time_type dictionaryFileLastWrite;
std::vector<char> charStorage;
std::vector<QString> definitions;
int definitionIndex;
} dictionaryWindow;
} extraWindow;
bool ProcessSentence(std::wstring& sentence, SentenceInfo sentenceInfo)
{
if (sentenceInfo["current select"] && sentenceInfo["text number"] != 0)
QMetaObject::invokeMethod(&extraWindow, [sentence = S(sentence)] { extraWindow.AddSentence(sentence); });
return false;
}

51
extensions/extrawindow.ui Normal file
View File

@ -0,0 +1,51 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>ExtraWindow</class>
<widget class="QDialog" name="ExtraWindow">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>800</width>
<height>300</height>
</rect>
</property>
<property name="contextMenuPolicy">
<enum>Qt::CustomContextMenu</enum>
</property>
<layout class="QHBoxLayout">
<item>
<widget class="QLabel" name="display">
<property name="sizePolicy">
<sizepolicy hsizetype="Ignored" vsizetype="Ignored">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="font">
<font>
<pointsize>16</pointsize>
</font>
</property>
<property name="contextMenuPolicy">
<enum>Qt::CustomContextMenu</enum>
</property>
<property name="lineWidth">
<number>0</number>
</property>
<property name="alignment">
<set>Qt::AlignTop</set>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
<property name="textInteractionFlags">
<set>Qt::TextSelectableByMouse</set>
</property>
</widget>
</item>
</layout>
</widget>
<resources/>
<connections/>
</ui>

View File

@ -0,0 +1,267 @@
#include "qtcommon.h"
#include "translatewrapper.h"
#include "network.h"
extern const wchar_t* TRANSLATION_ERROR;
const char* TRANSLATION_PROVIDER = "Google Translate";
const char* GET_API_KEY_FROM = "https://console.cloud.google.com/marketplace/product/google/translate.googleapis.com";
extern const QStringList languagesTo
{
"Afrikaans",
"Albanian",
"Amharic",
"Arabic",
"Armenian",
"Azerbaijani",
"Basque",
"Belarusian",
"Bengali",
"Bosnian",
"Bulgarian",
"Catalan",
"Cebuano",
"Chichewa",
"Chinese (Simplified)",
"Chinese (Traditional)",
"Corsican",
"Croatian",
"Czech",
"Danish",
"Dutch",
"English",
"Esperanto",
"Estonian",
"Filipino",
"Finnish",
"French",
"Frisian",
"Galician",
"Georgian",
"German",
"Greek",
"Gujarati",
"Haitian Creole",
"Hausa",
"Hawaiian",
"Hebrew",
"Hindi",
"Hmong",
"Hungarian",
"Icelandic",
"Igbo",
"Indonesian",
"Irish",
"Italian",
"Japanese",
"Javanese",
"Kannada",
"Kazakh",
"Khmer",
"Kinyarwanda",
"Korean",
"Kurdish (Kurmanji)",
"Kyrgyz",
"Lao",
"Latin",
"Latvian",
"Lithuanian",
"Luxembourgish",
"Macedonian",
"Malagasy",
"Malay",
"Malayalam",
"Maltese",
"Maori",
"Marathi",
"Mongolian",
"Myanmar (Burmese)",
"Nepali",
"Norwegian",
"Odia (Oriya)",
"Pashto",
"Persian",
"Polish",
"Portuguese",
"Punjabi",
"Romanian",
"Russian",
"Samoan",
"Scots Gaelic",
"Serbian",
"Sesotho",
"Shona",
"Sindhi",
"Sinhala",
"Slovak",
"Slovenian",
"Somali",
"Spanish",
"Sundanese",
"Swahili",
"Swedish",
"Tajik",
"Tamil",
"Tatar",
"Telugu",
"Thai",
"Turkish",
"Turkmen",
"Ukrainian",
"Urdu",
"Uyghur",
"Uzbek",
"Vietnamese",
"Welsh",
"Xhosa",
"Yiddish",
"Yoruba",
"Zulu",
}, languagesFrom = languagesTo;
extern const std::unordered_map<std::wstring, std::wstring> codes
{
{ { L"Afrikaans" }, { L"af" } },
{ { L"Albanian" }, { L"sq" } },
{ { L"Amharic" }, { L"am" } },
{ { L"Arabic" }, { L"ar" } },
{ { L"Armenian" }, { L"hy" } },
{ { L"Azerbaijani" }, { L"az" } },
{ { L"Basque" }, { L"eu" } },
{ { L"Belarusian" }, { L"be" } },
{ { L"Bengali" }, { L"bn" } },
{ { L"Bosnian" }, { L"bs" } },
{ { L"Bulgarian" }, { L"bg" } },
{ { L"Catalan" }, { L"ca" } },
{ { L"Cebuano" }, { L"ceb" } },
{ { L"Chichewa" }, { L"ny" } },
{ { L"Chinese (Simplified)" }, { L"zh-CN" } },
{ { L"Chinese (Traditional)" }, { L"zh-TW" } },
{ { L"Corsican" }, { L"co" } },
{ { L"Croatian" }, { L"hr" } },
{ { L"Czech" }, { L"cs" } },
{ { L"Danish" }, { L"da" } },
{ { L"Dutch" }, { L"nl" } },
{ { L"English" }, { L"en" } },
{ { L"Esperanto" }, { L"eo" } },
{ { L"Estonian" }, { L"et" } },
{ { L"Filipino" }, { L"tl" } },
{ { L"Finnish" }, { L"fi" } },
{ { L"French" }, { L"fr" } },
{ { L"Frisian" }, { L"fy" } },
{ { L"Galician" }, { L"gl" } },
{ { L"Georgian" }, { L"ka" } },
{ { L"German" }, { L"de" } },
{ { L"Greek" }, { L"el" } },
{ { L"Gujarati" }, { L"gu" } },
{ { L"Haitian Creole" }, { L"ht" } },
{ { L"Hausa" }, { L"ha" } },
{ { L"Hawaiian" }, { L"haw" } },
{ { L"Hebrew" }, { L"iw" } },
{ { L"Hindi" }, { L"hi" } },
{ { L"Hmong" }, { L"hmn" } },
{ { L"Hungarian" }, { L"hu" } },
{ { L"Icelandic" }, { L"is" } },
{ { L"Igbo" }, { L"ig" } },
{ { L"Indonesian" }, { L"id" } },
{ { L"Irish" }, { L"ga" } },
{ { L"Italian" }, { L"it" } },
{ { L"Japanese" }, { L"ja" } },
{ { L"Javanese" }, { L"jw" } },
{ { L"Kannada" }, { L"kn" } },
{ { L"Kazakh" }, { L"kk" } },
{ { L"Khmer" }, { L"km" } },
{ { L"Kinyarwanda" }, { L"rw" } },
{ { L"Korean" }, { L"ko" } },
{ { L"Kurdish (Kurmanji)" }, { L"ku" } },
{ { L"Kyrgyz" }, { L"ky" } },
{ { L"Lao" }, { L"lo" } },
{ { L"Latin" }, { L"la" } },
{ { L"Latvian" }, { L"lv" } },
{ { L"Lithuanian" }, { L"lt" } },
{ { L"Luxembourgish" }, { L"lb" } },
{ { L"Macedonian" }, { L"mk" } },
{ { L"Malagasy" }, { L"mg" } },
{ { L"Malay" }, { L"ms" } },
{ { L"Malayalam" }, { L"ml" } },
{ { L"Maltese" }, { L"mt" } },
{ { L"Maori" }, { L"mi" } },
{ { L"Marathi" }, { L"mr" } },
{ { L"Mongolian" }, { L"mn" } },
{ { L"Myanmar (Burmese)" }, { L"my" } },
{ { L"Nepali" }, { L"ne" } },
{ { L"Norwegian" }, { L"no" } },
{ { L"Odia (Oriya)" }, { L"or" } },
{ { L"Pashto" }, { L"ps" } },
{ { L"Persian" }, { L"fa" } },
{ { L"Polish" }, { L"pl" } },
{ { L"Portuguese" }, { L"pt" } },
{ { L"Punjabi" }, { L"pa" } },
{ { L"Romanian" }, { L"ro" } },
{ { L"Russian" }, { L"ru" } },
{ { L"Samoan" }, { L"sm" } },
{ { L"Scots Gaelic" }, { L"gd" } },
{ { L"Serbian" }, { L"sr" } },
{ { L"Sesotho" }, { L"st" } },
{ { L"Shona" }, { L"sn" } },
{ { L"Sindhi" }, { L"sd" } },
{ { L"Sinhala" }, { L"si" } },
{ { L"Slovak" }, { L"sk" } },
{ { L"Slovenian" }, { L"sl" } },
{ { L"Somali" }, { L"so" } },
{ { L"Spanish" }, { L"es" } },
{ { L"Sundanese" }, { L"su" } },
{ { L"Swahili" }, { L"sw" } },
{ { L"Swedish" }, { L"sv" } },
{ { L"Tajik" }, { L"tg" } },
{ { L"Tamil" }, { L"ta" } },
{ { L"Tatar" }, { L"tt" } },
{ { L"Telugu" }, { L"te" } },
{ { L"Thai" }, { L"th" } },
{ { L"Turkish" }, { L"tr" } },
{ { L"Turkmen" }, { L"tk" } },
{ { L"Ukrainian" }, { L"uk" } },
{ { L"Urdu" }, { L"ur" } },
{ { L"Uyghur" }, { L"ug" } },
{ { L"Uzbek" }, { L"uz" } },
{ { L"Vietnamese" }, { L"vi" } },
{ { L"Welsh" }, { L"cy" } },
{ { L"Xhosa" }, { L"xh" } },
{ { L"Yiddish" }, { L"yi" } },
{ { L"Yoruba" }, { L"yo" } },
{ { L"Zulu" }, { L"zu" } },
{ { L"?" }, { L"auto" } }
};
bool translateSelectedOnly = false, useRateLimiter = true, rateLimitSelected = false, useCache = true, useFilter = true;
int tokenCount = 30, rateLimitTimespan = 60000, maxSentenceSize = 1000;
std::pair<bool, std::wstring> Translate(const std::wstring& text, TranslationParam tlp)
{
if (!tlp.authKey.empty())
{
std::wstring translateFromComponent = tlp.translateFrom == L"?" ? L"" : L"&source=" + codes.at(tlp.translateFrom);
if (HttpRequest httpRequest{
L"Mozilla/5.0 Textractor",
L"translation.googleapis.com",
L"POST",
FormatString(L"/language/translate/v2?format=text&target=%s&key=%s%s", codes.at(tlp.translateTo), tlp.authKey, translateFromComponent).c_str(),
FormatString(R"({"q":["%s"]})", JSON::Escape(WideStringToString(text)))
})
if (auto translation = Copy(JSON::Parse(httpRequest.response)[L"data"][L"translations"][0][L"translatedText"].String())) return { true, translation.value() };
else return { false, FormatString(L"%s: %s", TRANSLATION_ERROR, httpRequest.response) };
else return { false, FormatString(L"%s (code=%u)", TRANSLATION_ERROR, httpRequest.errorCode) };
}
if (HttpRequest httpRequest{
L"Mozilla/5.0 Textractor",
L"translate.google.com",
L"GET",
FormatString(L"/m?sl=%s&tl=%s&q=%s", codes.at(tlp.translateFrom), codes.at(tlp.translateTo), Escape(text)).c_str()
})
{
auto start = httpRequest.response.find(L"result-container\">"), end = httpRequest.response.find(L'<', start);
if (end != std::string::npos) return { true, HTML::Unescape(httpRequest.response.substr(start + 18, end - start - 18)) };
return { false, FormatString(L"%s: %s", TRANSLATION_ERROR, httpRequest.response) };
}
else return { false, FormatString(L"%s (code=%u)", TRANSLATION_ERROR, httpRequest.errorCode) };
}

130
extensions/lua.cpp Normal file
View File

@ -0,0 +1,130 @@
#include "qtcommon.h"
#include "extension.h"
#include <QPlainTextEdit>
extern const char* LUA_INTRO;
extern const char* LOAD_SCRIPT;
extern const wchar_t* LUA_ERROR;
constexpr auto LUA_SAVE_FILE = u8"Textractor.lua";
extern "C" // Lua library
{
enum LuaType { LUA_TNIL, LUA_TBOOLEAN, LUA_TLIGHTUSERDATA, LUA_TNUMBER, LUA_TSTRING, LUA_TTABLE, LUA_TFUNCTION, LUA_TUSERDATA, LUA_TTHREAD };
enum LuaStatus { LUA_OK, LUA_YIELD, LUA_ERRRUN, LUA_ERRSYNTAX, LUA_ERRMEM, LUA_ERRGCMM, LUA_ERRERR };
struct lua_State;
lua_State* luaL_newstate();
void luaL_openlibs(lua_State*);
void lua_close(lua_State*);
LuaStatus luaL_loadstring(lua_State*, const char* str);
const char* lua_tolstring(lua_State*, int index, size_t* size);
const char* lua_pushstring(lua_State*, const char* str);
void lua_pushinteger(lua_State*, int64_t n);
void lua_createtable(lua_State*, int narr, int nrec);
void lua_settable(lua_State*, int index);
void lua_settop(lua_State*, int index);
LuaType lua_getglobal(lua_State*, const char* name);
LuaStatus lua_pcallk(lua_State*, int nargs, int nresults, int msgh, void*, void*);
}
bool luaL_dostring(lua_State* L, const char* str)
{
return luaL_loadstring(L, str) || lua_pcallk(L, 0, -1, 0, NULL, NULL);
}
bool logErrors = true;
Synchronized<std::string> script;
std::atomic<int> revCount = 0;
class Window : public QDialog, Localizer
{
public:
Window() : QDialog(nullptr, Qt::WindowMinMaxButtonsHint)
{
connect(&loadButton, &QPushButton::clicked, this, &Window::LoadScript);
if (scriptEditor.toPlainText().isEmpty()) scriptEditor.setPlainText(LUA_INTRO);
layout.addWidget(&scriptEditor);
layout.addWidget(&loadButton);
resize(800, 600);
setWindowTitle("Lua");
QMetaObject::invokeMethod(this, &QWidget::show, Qt::QueuedConnection);
LoadScript();
}
~Window()
{
Save();
}
private:
void LoadScript()
{
revCount += 1;
script->assign(scriptEditor.toPlainText().toUtf8());
Save();
}
void Save()
{
QTextFile(LUA_SAVE_FILE, QIODevice::WriteOnly | QIODevice::Truncate).write(scriptEditor.toPlainText().toUtf8());
}
QHBoxLayout layout{ this };
QPlainTextEdit scriptEditor{ QTextFile(LUA_SAVE_FILE, QIODevice::ReadOnly).readAll(), this };
QPushButton loadButton{ LOAD_SCRIPT, this };
} window;
bool ProcessSentence(std::wstring& sentence, SentenceInfo sentenceInfo)
{
thread_local struct { std::unique_ptr<lua_State, Functor<lua_close>> L{ luaL_newstate() }; operator lua_State*() { return L.get(); } } L;
thread_local auto _ = (luaL_openlibs(L), luaL_dostring(L, "function ProcessSentence() end"));
thread_local int revCount = 0;
if (::revCount > revCount)
{
revCount = ::revCount;
luaL_dostring(L, "ProcessSentence = nil");
if (luaL_dostring(L, script.Copy().c_str()) != LUA_OK)
{
sentence += L"\n" + FormatString(LUA_ERROR, StringToWideString(lua_tolstring(L, 1, nullptr)));
lua_settop(L, 0);
return logErrors;
}
}
if (lua_getglobal(L, "ProcessSentence") != LUA_TFUNCTION)
{
sentence += L"\n" + FormatString(LUA_ERROR, L"ProcessSentence is not a function");
lua_settop(L, 0);
return logErrors;
}
lua_pushstring(L, WideStringToString(sentence).c_str());
lua_createtable(L, 0, 0);
for (auto info = sentenceInfo.infoArray; info->name; ++info)
{
lua_pushstring(L, info->name);
lua_pushinteger(L, info->value);
lua_settable(L, 3);
}
if (lua_pcallk(L, 2, 1, 0, NULL, NULL) != LUA_OK)
{
sentence += L"\n" + FormatString(LUA_ERROR, StringToWideString(lua_tolstring(L, 1, nullptr)));
lua_settop(L, 0);
return logErrors;
}
if (const char* newSentence = lua_tolstring(L, 1, nullptr))
{
sentence = StringToWideString(newSentence);
lua_settop(L, 0);
return true;
}
lua_settop(L, 0);
return false;
}

65
extensions/network.cpp Normal file
View File

@ -0,0 +1,65 @@
#include "network.h"
HttpRequest::HttpRequest(
const wchar_t* agentName,
const wchar_t* serverName,
const wchar_t* action,
const wchar_t* objectName,
std::string body,
const wchar_t* headers,
DWORD port,
const wchar_t* referrer,
DWORD requestFlags,
const wchar_t* httpVersion,
const wchar_t** acceptTypes
)
{
static std::atomic<HINTERNET> internet = NULL;
if (!internet) internet = WinHttpOpen(agentName, WINHTTP_ACCESS_TYPE_DEFAULT_PROXY, NULL, NULL, 0);
if (internet)
if (InternetHandle connection = WinHttpConnect(internet, serverName, port, 0))
if (InternetHandle request = WinHttpOpenRequest(connection, action, objectName, httpVersion, referrer, acceptTypes, requestFlags))
if (WinHttpSendRequest(request, headers, -1UL, body.empty() ? NULL : body.data(), body.size(), body.size(), NULL))
{
WinHttpReceiveResponse(request, NULL);
//DWORD size = 0;
//WinHttpQueryHeaders(request, WINHTTP_QUERY_RAW_HEADERS_CRLF, WINHTTP_HEADER_NAME_BY_INDEX, NULL, &size, WINHTTP_NO_HEADER_INDEX);
//this->headers.resize(size);
//WinHttpQueryHeaders(request, WINHTTP_QUERY_RAW_HEADERS_CRLF, WINHTTP_HEADER_NAME_BY_INDEX, this->headers.data(), &size, WINHTTP_NO_HEADER_INDEX);
std::string data;
DWORD availableSize, downloadedSize;
do
{
availableSize = 0;
WinHttpQueryDataAvailable(request, &availableSize);
if (!availableSize) break;
std::vector<char> buffer(availableSize);
WinHttpReadData(request, buffer.data(), availableSize, &downloadedSize);
data.append(buffer.data(), downloadedSize);
} while (availableSize > 0);
response = StringToWideString(data);
this->connection = std::move(connection);
this->request = std::move(request);
}
else errorCode = GetLastError();
else errorCode = GetLastError();
else errorCode = GetLastError();
else errorCode = GetLastError();
}
std::wstring Escape(const std::wstring& text)
{
std::wstring escaped;
for (unsigned char ch : WideStringToString(text)) escaped += FormatString(L"%%%02X", (int)ch);
return escaped;
}
std::string Escape(const std::string& text)
{
std::string escaped;
for (unsigned char ch : text) escaped += FormatString("%%%02X", (int)ch);
return escaped;
}
TEST(assert(JSON::Parse<wchar_t>(LR"([{"string":"hello world","boolean":false,"number":1.67e+4,"null":null,"array":[]},"hello world"])")));

233
extensions/network.h Normal file
View File

@ -0,0 +1,233 @@
#pragma once
#include <winhttp.h>
#include <variant>
using InternetHandle = AutoHandle<Functor<WinHttpCloseHandle>>;
struct HttpRequest
{
HttpRequest(
const wchar_t* agentName,
const wchar_t* serverName,
const wchar_t* action,
const wchar_t* objectName,
std::string body = "",
const wchar_t* headers = NULL,
DWORD port = INTERNET_DEFAULT_PORT,
const wchar_t* referrer = NULL,
DWORD requestFlags = WINHTTP_FLAG_SECURE | WINHTTP_FLAG_ESCAPE_DISABLE,
const wchar_t* httpVersion = NULL,
const wchar_t** acceptTypes = NULL
);
operator bool() { return errorCode == ERROR_SUCCESS; }
std::wstring response;
std::wstring headers;
InternetHandle connection = NULL;
InternetHandle request = NULL;
DWORD errorCode = ERROR_SUCCESS;
};
std::wstring Escape(const std::wstring& text);
std::string Escape(const std::string& text);
namespace HTML
{
template <typename C>
std::basic_string<C> Unescape(std::basic_string<C> text)
{
constexpr C
lt[] = { '&', 'l', 't', ';' },
gt[] = { '&', 'g', 't', ';' },
apos1[] = { '&', 'a', 'p', 'o', 's', ';' },
apos2[] = { '&', '#', '3', '9', ';' },
apos3[] = { '&', '#', 'x', '2', '7', ';' },
apos4[] = { '&', '#', 'X', '2', '7', ';' },
quot[] = { '&', 'q', 'u', 'o', 't', ';' },
amp[] = { '&', 'a', 'm', 'p', ';' };
for (int i = 0; i < text.size(); ++i)
if (text[i] == '&')
for (auto [original, length, replacement] : Array<const C*, size_t, C>{
{ lt, std::size(lt), '<' },
{ gt, std::size(gt), '>' },
{ apos1, std::size(apos1), '\'' },
{ apos2, std::size(apos2), '\'' },
{ apos3, std::size(apos3), '\'' },
{ apos4, std::size(apos4), '\'' },
{ quot, std::size(quot), '"' },
{ amp, std::size(amp), '&' }
}) if (std::char_traits<C>::compare(text.data() + i, original, length) == 0) text.replace(i, length, 1, replacement);
return text;
}
}
namespace JSON
{
template <typename C>
std::basic_string<C> Escape(std::basic_string<C> text)
{
int oldSize = text.size();
text.resize(text.size() + std::count_if(text.begin(), text.end(), [](C ch) { return ch == '\n' || ch == '\r' || ch == '\t' || ch == '\\' || ch == '"'; }));
auto out = text.rbegin();
for (int i = oldSize - 1; i >= 0; --i)
{
if (text[i] == '\n') *out++ = 'n';
else if (text[i] == '\t') *out++ = 't';
else if (text[i] == '\r') *out++ = 'r';
else if (text[i] == '\\' || text[i] == '"') *out++ = text[i];
else
{
*out++ = text[i];
continue;
}
*out++ = '\\';
}
text.erase(std::remove_if(text.begin(), text.end(), [](uint64_t ch) { return ch < 0x20 || ch == 0x7f; }), text.end());
return text;
}
template <typename C> struct UTF {};
template <> struct UTF<wchar_t>
{
inline static std::wstring FromCodepoint(unsigned codepoint) { return { (wchar_t)codepoint }; } // TODO: surrogate pairs
};
template <typename C>
struct Value : private std::variant<std::monostate, std::nullptr_t, bool, double, std::basic_string<C>, std::vector<Value<C>>, std::unordered_map<std::basic_string<C>, Value<C>>>
{
using std::variant<std::monostate, std::nullptr_t, bool, double, std::basic_string<C>, std::vector<Value<C>>, std::unordered_map<std::basic_string<C>, Value<C>>>::variant;
explicit operator bool() const { return index(); }
bool IsNull() const { return index() == 1; }
auto Boolean() const { return std::get_if<bool>(this); }
auto Number() const { return std::get_if<double>(this); }
auto String() const { return std::get_if<std::basic_string<C>>(this); }
auto Array() const { return std::get_if<std::vector<Value<C>>>(this); }
auto Object() const { return std::get_if<std::unordered_map<std::basic_string<C>, Value<C>>>(this); }
const Value<C>& operator[](std::basic_string<C> key) const
{
if (auto object = Object()) if (auto it = object->find(key); it != object->end()) return it->second;
return failure;
}
const Value<C>& operator[](int i) const
{
if (auto array = Array()) if (i < array->size()) return array->at(i);
return failure;
}
static const Value<C> failure;
};
template <typename C> const Value<C> Value<C>::failure;
template <typename C, int maxDepth = 25>
Value<C> Parse(const std::basic_string<C>& text, int64_t& i, int depth)
{
if (depth > maxDepth) return {};
C ch;
auto SkipWhitespace = [&]
{
while (i < text.size() && (text[i] == ' ' || text[i] == '\n' || text[i] == '\r' || text[i] == '\t')) ++i;
if (i >= text.size()) return true;
ch = text[i];
return false;
};
auto ExtractString = [&]
{
std::basic_string<C> unescaped;
i += 1;
for (; i < text.size(); ++i)
{
auto ch = text[i];
if (ch == '"') return i += 1, unescaped;
if (ch == '\\')
{
ch = text[i + 1];
if (ch == 'u' && isxdigit(text[i + 2]) && isxdigit(text[i + 3]) && isxdigit(text[i + 4]) && isxdigit(text[i + 5]))
{
char charCode[] = { (char)text[i + 2], (char)text[i + 3], (char)text[i + 4], (char)text[i + 5], 0 };
unescaped += UTF<C>::FromCodepoint(strtoul(charCode, nullptr, 16));
i += 5;
continue;
}
for (auto [original, value] : Array<char, char>{ { 'b', '\b' }, {'f', '\f'}, {'n', '\n'}, {'r', '\r'}, {'t', '\t'} }) if (ch == original)
{
unescaped.push_back(value);
goto replaced;
}
unescaped.push_back(ch);
replaced: i += 1;
}
else unescaped.push_back(ch);
}
return unescaped;
};
if (SkipWhitespace()) return {};
constexpr C nullStr[] = { 'n', 'u', 'l', 'l' }, trueStr[] = { 't', 'r', 'u', 'e' }, falseStr[] = { 'f', 'a', 'l', 's', 'e' };
if (ch == nullStr[0])
if (std::char_traits<C>::compare(text.data() + i, nullStr, std::size(nullStr)) == 0) return i += std::size(nullStr), nullptr;
else return {};
if (ch == trueStr[0])
if (std::char_traits<C>::compare(text.data() + i, trueStr, std::size(trueStr)) == 0) return i += std::size(trueStr), true;
else return {};
if (ch == falseStr[0])
if (std::char_traits<C>::compare(text.data() + i, falseStr, std::size(falseStr)) == 0) return i += std::size(falseStr), false;
else return {};
if (ch == '-' || (ch >= '0' && ch <= '9'))
{
std::string number;
for (; i < text.size() && ((text[i] >= '0' && text[i] <= '9') || text[i] == '-' || text[i] == '+' || text[i] == 'e' || text[i] == 'E' || text[i] == '.'); ++i)
number.push_back(text[i]);
return strtod(number.c_str(), NULL);
}
if (ch == '"') return ExtractString();
if (ch == '[')
{
std::vector<Value<C>> array;
while (true)
{
i += 1;
if (SkipWhitespace()) return {};
if (ch == ']') return i += 1, Value<C>(array);
if (!array.emplace_back(Parse<C, maxDepth>(text, i, depth + 1))) return {};
if (SkipWhitespace()) return {};
if (ch == ']') return i += 1, Value<C>(array);
if (ch != ',') return {};
}
}
if (ch == '{')
{
std::unordered_map<std::basic_string<C>, Value<C>> object;
while (true)
{
i += 1;
if (SkipWhitespace()) return {};
if (ch == '}') return i += 1, Value<C>(object);
if (ch != '"') return {};
auto key = ExtractString();
if (SkipWhitespace() || ch != ':') return {};
i += 1;
if (!(object[std::move(key)] = Parse<C, maxDepth>(text, i, depth + 1))) return {};
if (SkipWhitespace()) return {};
if (ch == '}') return i += 1, Value<C>(object);
if (ch != ',') return {};
}
}
return {};
}
template <typename C>
Value<C> Parse(const std::basic_string<C>& text)
{
int64_t start = 0;
return Parse(text, start, 0);
}
}

View File

@ -0,0 +1,72 @@
#include "qtcommon.h"
#include "extension.h"
#include "ui_regexfilter.h"
#include "module.h"
#include "blockmarkup.h"
#include <fstream>
extern const char* REGEX_FILTER;
extern const char* INVALID_REGEX;
extern const char* CURRENT_FILTER;
const char* REGEX_SAVE_FILE = "SavedRegexFilters.txt";
std::optional<std::wregex> regex;
std::wstring replace = L"$1";
concurrency::reader_writer_lock m;
DWORD (*GetSelectedProcessId)() = [] { return 0UL; };
class Window : public QDialog, Localizer
{
public:
Window() : QDialog(nullptr, Qt::WindowMinMaxButtonsHint)
{
ui.setupUi(this);
connect(ui.regexEdit, &QLineEdit::textEdited, this, &Window::SetRegex);
connect(ui.saveButton, &QPushButton::clicked, this, &Window::Save);
setWindowTitle(REGEX_FILTER);
QMetaObject::invokeMethod(this, &QWidget::show, Qt::QueuedConnection);
}
void SetRegex(QString regex)
{
ui.regexEdit->setText(regex);
std::scoped_lock lock(m);
if (!regex.isEmpty()) try { ::regex = S(regex); }
catch (std::regex_error) { return ui.output->setText(INVALID_REGEX); }
else ::regex = std::nullopt;
ui.output->setText(QString(CURRENT_FILTER).arg(regex));
}
private:
void Save()
{
auto formatted = FormatString(
L"\xfeff|PROCESS|%s|FILTER|%s|END|\r\n",
GetModuleFilename(GetSelectedProcessId()).value_or(FormatString(L"Error getting name of process 0x%X", GetSelectedProcessId())),
S(ui.regexEdit->text())
);
std::ofstream(REGEX_SAVE_FILE, std::ios::binary | std::ios::app).write((const char*)formatted.c_str(), formatted.size() * sizeof(wchar_t));
}
Ui::FilterWindow ui;
} window;
bool ProcessSentence(std::wstring& sentence, SentenceInfo sentenceInfo)
{
static auto _ = GetSelectedProcessId = (DWORD(*)())sentenceInfo["get selected process id"];
if (sentenceInfo["text number"] == 0) return false;
if (/*sentenceInfo["current select"] && */!regex) if (auto processName = GetModuleFilename(sentenceInfo["process id"]))
{
std::ifstream stream(REGEX_SAVE_FILE, std::ios::binary);
BlockMarkupIterator savedFilters(stream, Array<std::wstring_view>{ L"|PROCESS|", L"|FILTER|" });
std::vector<std::wstring> regexes;
while (auto read = savedFilters.Next()) if (read->at(0) == processName) regexes.push_back(std::move(read->at(1)));
if (!regexes.empty()) QMetaObject::invokeMethod(&window, std::bind(&Window::SetRegex, &window, S(regexes.back())), Qt::BlockingQueuedConnection);
}
concurrency::reader_writer_lock::scoped_lock_read readLock(m);
if (regex) sentence = std::regex_replace(sentence, regex.value(), replace);
return true;
}

61
extensions/regexfilter.ui Normal file
View File

@ -0,0 +1,61 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>FilterWindow</class>
<widget class="QDialog">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>500</width>
<height>80</height>
</rect>
</property>
<layout class="QVBoxLayout">
<item>
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QLineEdit" name="regexEdit"/>
</item>
<item>
<widget class="QPushButton" name="saveButton">
<property name="text">
<string>Save</string>
</property>
</widget>
</item>
</layout>
</item>
<item>
<widget class="QLabel" name="output">
<property name="text">
<string/>
</property>
<property name="alignment">
<set>Qt::AlignCenter</set>
</property>
</widget>
</item>
<item>
<widget class="QLabel">
<property name="text">
<string>&lt;a href=&quot;https://regexr.com&quot;&gt;regexr.com&lt;/a&gt;</string>
</property>
<property name="textFormat">
<enum>Qt::RichText</enum>
</property>
<property name="alignment">
<set>Qt::AlignCenter</set>
</property>
<property name="openExternalLinks">
<bool>true</bool>
</property>
<property name="textInteractionFlags">
<set>Qt::TextBrowserInteraction</set>
</property>
</widget>
</item>
</layout>
</widget>
<resources/>
<connections/>
</ui>

View File

@ -0,0 +1,72 @@
#include "extension.h"
#include "module.h"
#include "blockmarkup.h"
#include <fstream>
#include <process.h>
extern const wchar_t* REGEX_REPLACER_INSTRUCTIONS;
const char* REPLACE_SAVE_FILE = "SavedRegexReplacements.txt";
std::atomic<std::filesystem::file_time_type> replaceFileLastWrite = {};
concurrency::reader_writer_lock m;
std::vector<std::tuple<std::wregex, std::wstring, std::regex_constants::match_flag_type>> replacements;
void UpdateReplacements()
{
try
{
if (replaceFileLastWrite.exchange(std::filesystem::last_write_time(REPLACE_SAVE_FILE)) == std::filesystem::last_write_time(REPLACE_SAVE_FILE)) return;
std::scoped_lock lock(m);
replacements.clear();
std::ifstream stream(REPLACE_SAVE_FILE, std::ios::binary);
BlockMarkupIterator savedFilters(stream, Array<std::wstring_view>{ L"|REGEX|", L"|BECOMES|", L"|MODIFIER|" });
while (auto read = savedFilters.Next())
{
const auto& [regex, replacement, modifier] = read.value();
try
{
replacements.emplace_back(
std::wregex(regex, modifier.find(L'i') == std::string::npos ? std::regex::ECMAScript : std::regex::icase),
replacement,
modifier.find(L'g') == std::string::npos ? std::regex_constants::format_first_only : std::regex_constants::format_default
);
}
catch (std::regex_error) {}
}
}
catch (std::filesystem::filesystem_error) { replaceFileLastWrite.store({}); }
}
BOOL WINAPI DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved)
{
switch (ul_reason_for_call)
{
case DLL_PROCESS_ATTACH:
{
UpdateReplacements();
if (replacements.empty())
{
auto file = std::ofstream(REPLACE_SAVE_FILE, std::ios::binary) << "\xff\xfe";
for (auto ch : std::wstring_view(REGEX_REPLACER_INSTRUCTIONS))
file << (ch == L'\n' ? std::string_view("\r\0\n", 4) : std::string_view((char*)&ch, 2));
SpawnThread([] { _spawnlp(_P_DETACH, "notepad", "notepad", REPLACE_SAVE_FILE, NULL); }); // show file to user
}
}
break;
case DLL_PROCESS_DETACH:
{
}
break;
}
return TRUE;
}
bool ProcessSentence(std::wstring& sentence, SentenceInfo sentenceInfo)
{
UpdateReplacements();
concurrency::reader_writer_lock::scoped_lock_read readLock(m);
for (const auto& [regex, replacement, flags] : replacements) sentence = std::regex_replace(sentence, regex, replacement, flags);
return true;
}

View File

@ -0,0 +1,54 @@
#include "extension.h"
bool ProcessSentence(std::wstring& sentence, SentenceInfo sentenceInfo)
{
if (sentenceInfo["text number"] == 0) return false;
std::vector<int> repeatNumbers(sentence.size() + 1, 0);
for (int i = 0; i < sentence.size(); ++i)
{
if (sentence[i] != sentence[i + 1])
{
int j = i;
while (sentence[j] == sentence[i] && --j >= 0);
repeatNumbers[i - j] += 1;
}
}
int repeatNumber = std::distance(repeatNumbers.begin(), std::max_element(repeatNumbers.rbegin(), repeatNumbers.rend()).base() - 1);
if (repeatNumber < 2) return false;
std::wstring newSentence;
for (int i = 0; i < sentence.size();)
{
newSentence.push_back(sentence[i]);
for (int j = i; j <= sentence.size(); ++j)
{
if (j == sentence.size() || sentence[i] != sentence[j])
{
i += (j - i) % repeatNumber == 0 ? repeatNumber : 1;
break;
}
}
}
sentence = newSentence;
return true;
}
TEST(
{
InfoForExtension nonConsole[] = { { "text number", 1 }, {} };
std::wstring repeatedChars = L"aaaaaaaaaaaabbbbbbcccdddaabbbcccddd";
std::wstring someRepeatedChars = L"abcdefaabbccddeeff";
ProcessSentence(repeatedChars, { nonConsole });
ProcessSentence(someRepeatedChars, { nonConsole });
assert(repeatedChars.find(L"aaaabbcd") == 0);
assert(someRepeatedChars == L"abcdefabcdef");
std::wstring empty = L"", one = L" ", normal = L"This is a normal sentence. はい";
ProcessSentence(empty, { nonConsole });
ProcessSentence(one, { nonConsole });
ProcessSentence(normal, { nonConsole });
assert(empty == L"" && one == L" " && normal == L"This is a normal sentence. はい");
}
);

View File

@ -0,0 +1,97 @@
#include "extension.h"
std::vector<int> GenerateSuffixArray(const std::wstring& text)
{
std::vector<int> suffixArray(text.size());
for (int i = 0; i < text.size(); ++i) suffixArray[i] = i;
// The below code is a more efficient way of doing this:
// std::sort(suffixArray.begin(), suffixArray.end(), [&](int a, int b) { return wcscmp(text.c_str() + a, text.c_str() + b) > 0; });
std::stable_sort(suffixArray.begin(), suffixArray.end(), [&](int a, int b) { return text[a] > text[b]; });
std::vector<int> eqClasses(text.begin(), text.end());
std::vector<int> count(text.size());
for (int length = 1; length < text.size(); length *= 2)
{
// Determine equivalence class up to length, by checking length / 2 equivalence of suffixes and their following length / 2 suffixes
std::vector<int> prevEqClasses = eqClasses;
eqClasses[suffixArray[0]] = 0;
for (int i = 1; i < text.size(); ++i)
{
int currentSuffix = suffixArray[i], lastSuffix = suffixArray[i - 1];
if (currentSuffix + length < text.size() && prevEqClasses[currentSuffix] == prevEqClasses[lastSuffix] &&
prevEqClasses[currentSuffix + length / 2] == prevEqClasses[lastSuffix + length / 2]
)
eqClasses[currentSuffix] = eqClasses[lastSuffix];
else eqClasses[currentSuffix] = i;
}
// Sort within equivalence class based on order of following suffix after length (orders up to length * 2)
for (int i = 0; i < text.size(); ++i) count[i] = i;
for (auto suffix : std::vector(suffixArray))
{
int precedingSuffix = suffix - length;
if (precedingSuffix >= 0) suffixArray[count[eqClasses[precedingSuffix]]++] = precedingSuffix;
}
}
for (int i = 0; i + 1 < text.size(); ++i)
assert(wcscmp(text.c_str() + suffixArray[i], text.c_str() + suffixArray[i + 1]) > 0);
return suffixArray;
}
constexpr wchar_t ERASED = 0xf246; // inside Unicode private use area
bool ProcessSentence(std::wstring& sentence, SentenceInfo sentenceInfo)
{
if (sentenceInfo["text number"] == 0) return false;
// This algorithm looks for repeating substrings (in other words, common prefixes among the set of suffixes) of the sentence with length > 6
// It then looks for any regions of characters at least twice as long as the substring made up only of characters in the substring, and erases them
// If this results in the substring being completely erased from the string, the substring is copied to the last location where it was located in the original string
auto timeout = GetTickCount64() + 30'000; // give up if taking over 30 seconds
std::vector<int> suffixArray = GenerateSuffixArray(sentence);
for (int i = 0; i + 1 < sentence.size() && GetTickCount64() < timeout; ++i)
{
int commonPrefixLength = 0;
for (int j = suffixArray[i], k = suffixArray[i + 1]; j < sentence.size() && k < sentence.size(); ++j, ++k)
if (sentence[j] != ERASED && sentence[j] == sentence[k]) commonPrefixLength += 1;
else break;
if (commonPrefixLength > 6)
{
std::wstring substring(sentence, suffixArray[i], commonPrefixLength);
bool substringCharMap[0x10000] = {};
for (auto ch : substring) substringCharMap[ch] = true;
for (int regionSize = 0, j = 0; j <= sentence.size(); ++j)
if (substringCharMap[sentence[j]]) regionSize += 1;
else if (regionSize >= commonPrefixLength * 2)
while (regionSize > 0) sentence[j - regionSize--] = ERASED;
else regionSize = 0;
if (!wcsstr(sentence.c_str(), substring.c_str())) std::copy(substring.begin(), substring.end(), sentence.begin() + max(suffixArray[i], suffixArray[i + 1]));
}
}
sentence.erase(std::remove(sentence.begin(), sentence.end(), ERASED), sentence.end());
return true;
}
TEST(
{
InfoForExtension nonConsole[] = { { "text number", 1 }, {} };
std::wstring cyclicRepeats = L"Name: '_abcdefg_abcdefg_abcdefg_abcdefg_abcdefg'";
std::wstring buildupRepeats = L"Name: '__a_ab_abc_abcd_abcde_abcdef_abcdefg'";
std::wstring breakdownRepeats = L"Name: '_abcdefg_abcdef_abcde_abcd_abc_ab_a_'";
ProcessSentence(cyclicRepeats, { nonConsole });
ProcessSentence(buildupRepeats, { nonConsole });
ProcessSentence(breakdownRepeats, { nonConsole });
assert(cyclicRepeats == L"Name: '_abcdefg'");
assert(buildupRepeats == L"Name: '_abcdefg'");
assert(breakdownRepeats == L"Name: '_abcdefg'");
std::wstring empty = L"", one = L" ", normal = L"This is a normal sentence. はい";
ProcessSentence(empty, { nonConsole });
ProcessSentence(one, { nonConsole });
ProcessSentence(normal, { nonConsole });
assert(empty == L"" && one == L" " && normal == L"This is a normal sentence. はい");
}
);

View File

@ -0,0 +1,52 @@
#include "extension.h"
bool ProcessSentence(std::wstring& sentence, SentenceInfo sentenceInfo)
{
if (sentenceInfo["text number"] == 0) return false;
// This algorithm looks at all the prefixes of the sentence: if a prefix is found later in the sentence, it is removed from the beginning and the process is repeated
auto timeout = GetTickCount64() + 30'000; // give up if taking over 30 seconds
auto data = std::make_unique<wchar_t[]>(sentence.size() + 1);
wcscpy_s(data.get(), sentence.size() + 1, sentence.c_str());
wchar_t* dataEnd = data.get() + sentence.size();
int skip = 0, count = 0;
for (wchar_t* end = dataEnd; end - data.get() > skip && GetTickCount64() < timeout; --end)
{
std::swap(*end, *dataEnd);
int junkLength = end - data.get() - skip;
auto junkFound = wcsstr(sentence.c_str() + skip + junkLength, data.get() + skip);
std::swap(*end, *dataEnd);
if (junkFound)
{
if (count && junkLength < min(skip / count, 4)) break;
skip += junkLength;
count += 1;
end = dataEnd;
}
}
if (count && skip / count >= 3)
{
sentence = data.get() + skip;
return true;
}
return false;
}
TEST(
{
InfoForExtension nonConsole[] = { { "text number", 1 }, {} };
std::wstring cyclicRepeats = L"_abcde_abcdef_abcdefg_abcdefg_abcdefg_abcdefg_abcdefg";
std::wstring buildupRepeats = L"__a_ab_abc_abcd_abcde_abcdef_abcdefg";
ProcessSentence(cyclicRepeats, { nonConsole });
ProcessSentence(buildupRepeats, { nonConsole });
assert(cyclicRepeats == L"_abcdefg");
assert(buildupRepeats == L"_abcdefg");
std::wstring empty = L"", one = L" ", normal = L"This is a normal sentence. はい";
ProcessSentence(empty, { nonConsole });
ProcessSentence(one, { nonConsole });
ProcessSentence(normal, { nonConsole });
assert(empty == L"" && one == L" " && normal == L"This is a normal sentence. はい");
}
);

View File

@ -0,0 +1,44 @@
#include "extension.h"
int sentenceCacheSize = 30;
BOOL WINAPI DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved)
{
switch (ul_reason_for_call)
{
case DLL_PROCESS_ATTACH:
{
wchar_t filePath[MAX_PATH];
GetModuleFileNameW(hModule, filePath, MAX_PATH);
if (wchar_t* fileName = wcsrchr(filePath, L'\\')) swscanf_s(fileName, L"\\Remove %d Repeated Sentences.xdll", &sentenceCacheSize);
}
break;
case DLL_PROCESS_DETACH:
{
}
break;
}
return TRUE;
}
bool ProcessSentence(std::wstring& sentence, SentenceInfo sentenceInfo)
{
uint64_t textNumber = sentenceInfo["text number"];
if (textNumber == 0) return false;
static std::deque<Synchronized<std::vector<std::wstring>>> cache;
static std::mutex m;
m.lock();
if (textNumber + 1 > cache.size()) cache.resize(textNumber + 1);
auto prevSentences = cache[textNumber].Acquire();
m.unlock();
auto& inserted = prevSentences->emplace_back(sentence);
auto firstLocation = std::find(prevSentences->begin(), prevSentences->end(), sentence);
if (&*firstLocation != &inserted)
{
prevSentences->erase(firstLocation);
sentence.clear();
}
if (prevSentences->size() > sentenceCacheSize) prevSentences->erase(prevSentences->begin());
return sentence.empty();
}

142
extensions/replacer.cpp Normal file
View File

@ -0,0 +1,142 @@
#include "extension.h"
#include "blockmarkup.h"
#include <cwctype>
#include <fstream>
#include <sstream>
#include <process.h>
extern const wchar_t* REPLACER_INSTRUCTIONS;
constexpr auto REPLACE_SAVE_FILE = u8"SavedReplacements.txt";
std::atomic<std::filesystem::file_time_type> replaceFileLastWrite = {};
concurrency::reader_writer_lock m;
class Trie
{
public:
Trie(const std::istream& replacementScript)
{
BlockMarkupIterator replacementScriptParser(replacementScript, Array<std::wstring_view>{ L"|ORIG|", L"|BECOMES|" });
while (auto read = replacementScriptParser.Next())
{
const auto& [original, replacement] = read.value();
Node* current = &root;
for (auto ch : original) if (!Ignore(ch)) current = Next(current, ch);
if (current != &root)
current->value = charStorage.insert(charStorage.end(), replacement.c_str(), replacement.c_str() + replacement.size() + 1) - charStorage.begin();
}
}
std::wstring Replace(const std::wstring& sentence) const
{
std::wstring result;
for (int i = 0; i < sentence.size();)
{
std::wstring_view replacement(sentence.c_str() + i, 1);
int originalLength = 1;
const Node* current = &root;
for (int j = i; current && j <= sentence.size(); ++j)
{
if (current->value >= 0)
{
replacement = charStorage.data() + current->value;
originalLength = j - i;
}
if (!Ignore(sentence[j])) current = Next(current, sentence[j]) ? Next(current, sentence[j]) : Next(current, L'^');
}
result += replacement;
i += originalLength;
}
return result;
}
bool Empty()
{
return root.charMap.empty();
}
private:
static bool Ignore(wchar_t ch)
{
return ch <= 0x20 || iswspace(ch);
}
template <typename Node>
static Node* Next(Node* node, wchar_t ch)
{
auto it = std::lower_bound(node->charMap.begin(), node->charMap.end(), ch, [](const auto& one, auto two) { return one.first < two; });
if (it != node->charMap.end() && it->first == ch) return it->second.get();
if constexpr (!std::is_const_v<Node>) return node->charMap.insert(it, { ch, std::make_unique<Node>() })->second.get();
return nullptr;
}
struct Node
{
std::vector<std::pair<wchar_t, std::unique_ptr<Node>>> charMap;
ptrdiff_t value = -1;
} root;
std::vector<wchar_t> charStorage;
} trie = { std::istringstream("") };
void UpdateReplacements()
{
try
{
if (replaceFileLastWrite.exchange(std::filesystem::last_write_time(REPLACE_SAVE_FILE)) == std::filesystem::last_write_time(REPLACE_SAVE_FILE)) return;
std::scoped_lock lock(m);
trie = Trie(std::ifstream(REPLACE_SAVE_FILE, std::ios::binary));
}
catch (std::filesystem::filesystem_error) { replaceFileLastWrite.store({}); }
}
BOOL WINAPI DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved)
{
switch (ul_reason_for_call)
{
case DLL_PROCESS_ATTACH:
{
UpdateReplacements();
if (trie.Empty())
{
auto file = std::ofstream(REPLACE_SAVE_FILE, std::ios::binary) << "\xff\xfe";
for (auto ch : std::wstring_view(REPLACER_INSTRUCTIONS))
file << (ch == L'\n' ? std::string_view("\r\0\n", 4) : std::string_view((char*)&ch, 2));
SpawnThread([] { _spawnlp(_P_DETACH, "notepad", "notepad", REPLACE_SAVE_FILE, NULL); }); // show file to user
}
}
break;
case DLL_PROCESS_DETACH:
{
}
break;
}
return TRUE;
}
bool ProcessSentence(std::wstring& sentence, SentenceInfo)
{
UpdateReplacements();
concurrency::reader_writer_lock::scoped_lock_read readLock(m);
sentence = trie.Replace(sentence);
return true;
}
TEST(
{
std::wstring replacementScript = LR"(
|ORIG||BECOMES|goodbye |END|Ignore this text
And this text   
|ORIG||BECOMES|idiot|END|
|ORIG| |BECOMES| hello|END||ORIG|delet^this|BECOMES||END|)";
Trie replacements(std::istringstream(std::string{ (const char*)replacementScript.c_str(), replacementScript.size() * sizeof(wchar_t) }));
std::wstring original = LR"(Don't replace this 
delete this)";
std::wstring replaced = Trie(std::move(replacements)).Replace(original);
assert(replaced == L"Don't replace thisgoodbye idiot hello");
}
);

54
extensions/styler.cpp Normal file
View File

@ -0,0 +1,54 @@
#include "qtcommon.h"
#include "extension.h"
#include <QPlainTextEdit>
extern const char* LOAD_SCRIPT;
constexpr auto STYLE_SAVE_FILE = u8"Textractor.qss";
class Window : public QDialog, Localizer
{
public:
Window() : QDialog(nullptr, Qt::WindowMinMaxButtonsHint)
{
connect(&loadButton, &QPushButton::clicked, this, &Window::LoadScript);
if (scriptEditor.toPlainText().isEmpty())
scriptEditor.setPlainText("/*\nhttps://www.google.com/search?q=Qt+stylesheet+gallery\nhttps://doc.qt.io/qt-5/stylesheet-syntax.html\n*/");
layout.addWidget(&scriptEditor);
layout.addWidget(&loadButton);
resize(800, 600);
setWindowTitle("Styler");
QMetaObject::invokeMethod(this, &QWidget::show, Qt::QueuedConnection);
LoadScript();
}
~Window()
{
qApp->setStyleSheet("");
Save();
}
private:
void LoadScript()
{
qApp->setStyleSheet(scriptEditor.toPlainText());
Save();
}
void Save()
{
QTextFile(STYLE_SAVE_FILE, QIODevice::WriteOnly | QIODevice::Truncate).write(scriptEditor.toPlainText().toUtf8());
}
QHBoxLayout layout{ this };
QPlainTextEdit scriptEditor{ QTextFile(STYLE_SAVE_FILE, QIODevice::ReadOnly).readAll(), this };
QPushButton loadButton{ LOAD_SCRIPT, this };
} window;
bool ProcessSentence(std::wstring& sentence, SentenceInfo sentenceInfo)
{
return false;
}

View File

@ -0,0 +1,79 @@
#include "qtcommon.h"
#include "extension.h"
#include "ui_threadlinker.h"
#include <QKeyEvent>
extern const char* THREAD_LINKER;
extern const char* LINK;
extern const char* UNLINK;
extern const char* THREAD_LINK_FROM;
extern const char* THREAD_LINK_TO;
extern const char* HEXADECIMAL;
std::unordered_map<int64_t, std::unordered_set<int64_t>> links;
std::unordered_set<int64_t> universalLinks, empty;
bool separateSentences = false; // allow user to change?
concurrency::reader_writer_lock m;
class Window : public QDialog, Localizer
{
public:
Window() : QDialog(nullptr, Qt::WindowMinMaxButtonsHint)
{
ui.setupUi(this);
ui.linkButton->setText(LINK);
ui.unlinkButton->setText(UNLINK);
connect(ui.linkButton, &QPushButton::clicked, this, &Window::Link);
connect(ui.unlinkButton, &QPushButton::clicked, this, &Window::Unlink);
setWindowTitle(THREAD_LINKER);
QMetaObject::invokeMethod(this, &QWidget::show, Qt::QueuedConnection);
}
private:
void Link()
{
bool ok1, ok2, ok3, ok4;
QString fromInput = QInputDialog::getText(this, THREAD_LINK_FROM, HEXADECIMAL, QLineEdit::Normal, "All", &ok1, Qt::WindowCloseButtonHint);
int from = fromInput.toInt(&ok2, 16);
if (ok1 && (fromInput == "All" || ok2))
{
int to = QInputDialog::getText(this, THREAD_LINK_TO, HEXADECIMAL, QLineEdit::Normal, "", &ok3, Qt::WindowCloseButtonHint).toInt(&ok4, 16);
if (ok3 && ok4)
{
std::scoped_lock lock(m);
if ((ok2 ? links[from] : universalLinks).insert(to).second)
ui.linkList->addItem((ok2 ? QString::number(from, 16) : "All") + "->" + QString::number(to, 16));
}
}
}
void Unlink()
{
if (ui.linkList->currentItem())
{
QStringList link = ui.linkList->currentItem()->text().split("->");
ui.linkList->takeItem(ui.linkList->currentRow());
std::scoped_lock lock(m);
(link[0] == "All" ? universalLinks : links[link[0].toInt(nullptr, 16)]).erase(link[1].toInt(nullptr, 16));
}
}
void keyPressEvent(QKeyEvent* event) override
{
if (event->key() == Qt::Key_Delete) Unlink();
}
Ui::LinkWindow ui;
} window;
bool ProcessSentence(std::wstring& sentence, SentenceInfo sentenceInfo)
{
concurrency::reader_writer_lock::scoped_lock_read readLock(m);
auto action = separateSentences ? sentenceInfo["add sentence"] : sentenceInfo["add text"];
auto it = links.find(sentenceInfo["text number"]);
for (const auto& linkSet : { it != links.end() ? it->second : empty, sentenceInfo["text number"] > 1 ? universalLinks : empty })
for (auto link : linkSet)
((void(*)(int64_t, const wchar_t*))action)(link, sentence.c_str());
return false;
}

View File

@ -0,0 +1,47 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>LinkWindow</class>
<widget class="QDialog">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>400</width>
<height>300</height>
</rect>
</property>
<layout class="QHBoxLayout">
<item>
<widget class="QListWidget" name="linkList"/>
</item>
<item>
<layout class="QVBoxLayout">
<item>
<spacer>
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
</spacer>
</item>
<item>
<widget class="QPushButton" name="linkButton">
</widget>
</item>
<item>
<widget class="QPushButton" name="unlinkButton">
</widget>
</item>
<item>
<spacer>
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
</spacer>
</item>
</layout>
</item>
</layout>
</widget>
<resources/>
<connections/>
</ui>

View File

@ -0,0 +1,212 @@
#include "qtcommon.h"
#include "extension.h"
#include "translatewrapper.h"
#include "blockmarkup.h"
#include <concurrent_priority_queue.h>
#include <fstream>
#include <QComboBox>
extern const char* NATIVE_LANGUAGE;
extern const char* TRANSLATE_TO;
extern const char* TRANSLATE_FROM;
extern const char* TRANSLATE_SELECTED_THREAD_ONLY;
extern const char* RATE_LIMIT_ALL_THREADS;
extern const char* RATE_LIMIT_SELECTED_THREAD;
extern const char* USE_TRANS_CACHE;
extern const char* FILTER_GARBAGE;
extern const char* MAX_TRANSLATIONS_IN_TIMESPAN;
extern const char* TIMESPAN;
extern const char* MAX_SENTENCE_SIZE;
extern const char* API_KEY;
extern const wchar_t* SENTENCE_TOO_LARGE_TO_TRANS;
extern const wchar_t* TRANSLATION_ERROR;
extern const wchar_t* TOO_MANY_TRANS_REQUESTS;
extern const char* TRANSLATION_PROVIDER;
extern const char* GET_API_KEY_FROM;
extern const QStringList languagesTo, languagesFrom;
extern bool translateSelectedOnly, useRateLimiter, rateLimitSelected, useCache, useFilter;
extern int tokenCount, rateLimitTimespan, maxSentenceSize;
std::pair<bool, std::wstring> Translate(const std::wstring& text, TranslationParam tlp);
QFormLayout* display;
Settings settings;
namespace
{
Synchronized<TranslationParam> tlp;
Synchronized<std::unordered_map<std::wstring, std::wstring>> translationCache;
std::string CacheFile()
{
return FormatString("%s Cache (%S).txt", TRANSLATION_PROVIDER, tlp->translateTo);
}
void SaveCache()
{
std::wstring allTranslations(L"\xfeff");
for (const auto& [sentence, translation] : translationCache.Acquire().contents)
allTranslations.append(L"|SENTENCE|").append(sentence).append(L"|TRANSLATION|").append(translation).append(L"|END|\r\n");
std::ofstream(CacheFile(), std::ios::binary | std::ios::trunc).write((const char*)allTranslations.c_str(), allTranslations.size() * sizeof(wchar_t));
}
void LoadCache()
{
translationCache->clear();
std::ifstream stream(CacheFile(), std::ios::binary);
BlockMarkupIterator savedTranslations(stream, Array<std::wstring_view>{ L"|SENTENCE|", L"|TRANSLATION|" });
auto translationCache = ::translationCache.Acquire();
while (auto read = savedTranslations.Next())
{
auto& [sentence, translation] = read.value();
translationCache->try_emplace(std::move(sentence), std::move(translation));
}
}
}
class Window : public QDialog, Localizer
{
public:
Window() : QDialog(nullptr, Qt::WindowMinMaxButtonsHint)
{
display = new QFormLayout(this);
settings.beginGroup(TRANSLATION_PROVIDER);
auto translateToCombo = new QComboBox(this);
translateToCombo->addItems(languagesTo);
int i = -1;
if (settings.contains(TRANSLATE_TO)) i = translateToCombo->findText(settings.value(TRANSLATE_TO).toString());
if (i < 0) i = translateToCombo->findText(NATIVE_LANGUAGE, Qt::MatchStartsWith);
if (i < 0) i = translateToCombo->findText("English", Qt::MatchStartsWith);
translateToCombo->setCurrentIndex(i);
SaveTranslateTo(translateToCombo->currentText());
display->addRow(TRANSLATE_TO, translateToCombo);
connect(translateToCombo, &QComboBox::currentTextChanged, this, &Window::SaveTranslateTo);
auto translateFromCombo = new QComboBox(this);
translateFromCombo->addItem("?");
translateFromCombo->addItems(languagesFrom);
i = -1;
if (settings.contains(TRANSLATE_FROM)) i = translateFromCombo->findText(settings.value(TRANSLATE_FROM).toString());
if (i < 0) i = 0;
translateFromCombo->setCurrentIndex(i);
SaveTranslateFrom(translateFromCombo->currentText());
display->addRow(TRANSLATE_FROM, translateFromCombo);
connect(translateFromCombo, &QComboBox::currentTextChanged, this, &Window::SaveTranslateFrom);
for (auto [value, label] : Array<bool&, const char*>{
{ translateSelectedOnly, TRANSLATE_SELECTED_THREAD_ONLY },
{ useRateLimiter, RATE_LIMIT_ALL_THREADS },
{ rateLimitSelected, RATE_LIMIT_SELECTED_THREAD },
{ useCache, USE_TRANS_CACHE },
{ useFilter, FILTER_GARBAGE }
})
{
value = settings.value(label, value).toBool();
auto checkBox = new QCheckBox(this);
checkBox->setChecked(value);
display->addRow(label, checkBox);
connect(checkBox, &QCheckBox::clicked, [label, &value](bool checked) { settings.setValue(label, value = checked); });
}
for (auto [value, label] : Array<int&, const char*>{
{ tokenCount, MAX_TRANSLATIONS_IN_TIMESPAN },
{ rateLimitTimespan, TIMESPAN },
{ maxSentenceSize, MAX_SENTENCE_SIZE },
})
{
value = settings.value(label, value).toInt();
auto spinBox = new QSpinBox(this);
spinBox->setRange(0, INT_MAX);
spinBox->setValue(value);
display->addRow(label, spinBox);
connect(spinBox, qOverload<int>(&QSpinBox::valueChanged), [label, &value](int newValue) { settings.setValue(label, value = newValue); });
}
if (GET_API_KEY_FROM)
{
auto keyEdit = new QLineEdit(settings.value(API_KEY).toString(), this);
tlp->authKey = S(keyEdit->text());
QObject::connect(keyEdit, &QLineEdit::textChanged, [](QString key) { settings.setValue(API_KEY, S(tlp->authKey = S(key))); });
auto keyLabel = new QLabel(QString("<a href=\"%1\">%2</a>").arg(GET_API_KEY_FROM, API_KEY), this);
keyLabel->setOpenExternalLinks(true);
display->addRow(keyLabel, keyEdit);
}
setWindowTitle(TRANSLATION_PROVIDER);
QMetaObject::invokeMethod(this, &QWidget::show, Qt::QueuedConnection);
}
~Window()
{
SaveCache();
}
private:
void SaveTranslateTo(QString language)
{
SaveCache();
settings.setValue(TRANSLATE_TO, S(tlp->translateTo = S(language)));
LoadCache();
}
void SaveTranslateFrom(QString language)
{
settings.setValue(TRANSLATE_FROM, S(tlp->translateFrom = S(language)));
}
} window;
bool ProcessSentence(std::wstring& sentence, SentenceInfo sentenceInfo)
{
if (sentenceInfo["text number"] == 0) return false;
static class
{
public:
bool Request()
{
DWORD64 current = GetTickCount64(), token;
while (tokens.try_pop(token)) if (token > current - rateLimitTimespan)
{
tokens.push(token); // popped one too many
break;
}
bool available = tokens.size() < tokenCount;
if (available) tokens.push(current);
return available;
}
private:
concurrency::concurrent_priority_queue<DWORD64, std::greater<DWORD64>> tokens;
} rateLimiter;
bool cache = false;
std::wstring translation;
if (useFilter)
{
Trim(sentence);
sentence.erase(std::remove_if(sentence.begin(), sentence.end(), [](wchar_t ch) { return ch < ' ' && ch != '\n'; }), sentence.end());
}
if (sentence.empty()) return true;
if (sentence.size() > maxSentenceSize) translation = SENTENCE_TOO_LARGE_TO_TRANS;
if (useCache)
{
auto translationCache = ::translationCache.Acquire();
if (auto it = translationCache->find(sentence); it != translationCache->end()) translation = it->second;
}
if (translation.empty() && (!translateSelectedOnly || sentenceInfo["current select"]))
if (rateLimiter.Request() || !useRateLimiter || (!rateLimitSelected && sentenceInfo["current select"])) std::tie(cache, translation) = Translate(sentence, tlp.Copy());
else translation = TOO_MANY_TRANS_REQUESTS;
if (cache) translationCache->operator[](sentence) = translation;
if (useFilter) Trim(translation);
for (int i = 0; i < translation.size(); ++i) if (translation[i] == '\r' && translation[i + 1] == '\n') translation[i] = 0x200b; // for some reason \r appears as newline - no need to double
if (translation.empty()) translation = TRANSLATION_ERROR;
(sentence += L"\x200b \n") += translation;
return true;
}
extern const std::unordered_map<std::wstring, std::wstring> codes;
TEST(
{
assert(Translate(L"こんにちは", { L"English", L"?", L"" }).second.find(L"ello") == 1 || strstr(TRANSLATION_PROVIDER, "DevTools"));
for (auto languages : { languagesFrom, languagesTo }) for (auto language : languages)
assert(codes.count(S(language)));
assert(codes.count(L"?"));
}
);

View File

@ -0,0 +1,6 @@
#pragma once
struct TranslationParam
{
std::wstring translateTo, translateFrom, authKey;
};

View File

@ -1,44 +0,0 @@
/* Copyright (C) 2010-2012 kaosu (qiupf2000@gmail.com)
* This file is part of the Interactive Text Hooker.
* Interactive Text Hooker 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 <http://www.gnu.org/licenses/>.
*/
#include "CustomFilter.h"
void CustomFilter::Insert(WORD number)
{
set.insert(number);
}
void CustomFilter::Erase(WORD number)
{
set.erase(number);
}
bool CustomFilter::Find(WORD number) const
{
return set.find(number) != set.end();
}
void CustomFilter::Clear()
{
set.clear();
}
void CustomFilter::Traverse(CustomFilterCallBack callback, PVOID param)
{
for (auto ch = set.begin(); ch != set.end(); ++ch)
callback(*ch, param);
}

View File

@ -1,34 +0,0 @@
/* Copyright (C) 2010-2012 kaosu (qiupf2000@gmail.com)
* This file is part of the Interactive Text Hooker.
* Interactive Text Hooker 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 <http://www.gnu.org/licenses/>.
*/
#pragma once
#include "ITH.h"
typedef void (*CustomFilterCallBack) (WORD, PVOID);
class CustomFilter
{
public:
bool Find(WORD number) const;
void Insert(WORD number);
void Erase(WORD number);
void Clear();
void Traverse(CustomFilterCallBack callback, PVOID param);
private:
std::set<WORD> set;
};

View File

@ -1,37 +0,0 @@
/* Copyright (C) 2010-2012 kaosu (qiupf2000@gmail.com)
* This file is part of the Interactive Text Hooker.
* Interactive Text Hooker 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 <http://www.gnu.org/licenses/>.
*/
#pragma once
#include <Windows.h>
#include <string>
#include <sstream>
#include <ios>
#include <memory>
#include <stdexcept>
#include <assert.h>
#include <WindowsX.h>
#include <algorithm>
#include <Psapi.h>
#include <vector>
#include <map>
#include <CommCtrl.h>
#include <intrin.h>
#include <sstream>
#include <regex>
#include <set>
#include "profile/pugixml.hpp"
#pragma warning(disable: 4146)

View File

@ -1,82 +0,0 @@
// Generated by ResEdit 1.6.5
// Copyright (C) 2006-2015
// http://www.resedit.net
#include <windows.h>
#include <commctrl.h>
#include <richedit.h>
#include "resource.h"
//
// Dialog resources
//
LANGUAGE LANG_JAPANESE, SUBLANG_JAPANESE_JAPAN
IDD_DIALOG2 DIALOGEX 100, 100, 341, 210
STYLE DS_MODALFRAME | DS_SHELLFONT | WS_CAPTION | WS_VISIBLE | WS_POPUP | WS_SYSMENU
CAPTION "Process Explorer"
FONT 8, "MS Shell Dlg", 400, 0, 1
{
DEFPUSHBUTTON "OK", IDOK, 281, 189, 53, 14, 0, WS_EX_LEFT
PUSHBUTTON "Remove Profile", IDC_BUTTON6, 226, 189, 53, 14, 0, WS_EX_LEFT
CONTROL "", IDC_LIST1, WC_LISTVIEW, WS_TABSTOP | WS_BORDER | LVS_ALIGNLEFT | LVS_SHOWSELALWAYS | LVS_SINGLESEL | LVS_REPORT, 7, 20, 327, 164, WS_EX_LEFT
LTEXT "Process", IDC_STATIC, 7, 7, 65, 13, SS_LEFT | SS_CENTERIMAGE, WS_EX_LEFT
PUSHBUTTON "Attach", IDC_BUTTON2, 61, 189, 53, 14, 0, WS_EX_LEFT
PUSHBUTTON "Detach", IDC_BUTTON3, 116, 189, 53, 14, 0, WS_EX_LEFT
PUSHBUTTON "Add Profile", IDC_BUTTON5, 171, 189, 53, 14, 0, WS_EX_LEFT
PUSHBUTTON "Refresh", IDC_BUTTON1, 7, 189, 53, 14, 0, WS_EX_LEFT
}
LANGUAGE LANG_JAPANESE, SUBLANG_JAPANESE_JAPAN
IDD_DIALOG4 DIALOGEX 150, 100, 123, 185
STYLE DS_MODALFRAME | DS_SHELLFONT | WS_CAPTION | WS_VISIBLE | WS_POPUP | WS_SYSMENU
CAPTION "Option"
FONT 8, "MS Shell Dlg", 400, 0, 1
{
DEFPUSHBUTTON "OK", IDOK, 8, 164, 50, 14, 0, WS_EX_LEFT
PUSHBUTTON "Cancel", IDCANCEL, 65, 164, 50, 14, 0, WS_EX_LEFT
EDITTEXT IDC_EDIT1, 60, 7, 55, 14, ES_AUTOHSCROLL, WS_EX_LEFT
LTEXT "Split time", IDC_STATIC, 7, 7, 47, 13, SS_LEFT | SS_CENTERIMAGE, WS_EX_LEFT
EDITTEXT IDC_EDIT2, 60, 25, 55, 14, ES_AUTOHSCROLL, WS_EX_LEFT
LTEXT "Process delay", IDC_STATIC, 7, 26, 47, 13, SS_LEFT | SS_CENTERIMAGE, WS_EX_LEFT
EDITTEXT IDC_EDIT3, 60, 45, 55, 14, ES_AUTOHSCROLL, WS_EX_LEFT
LTEXT "Inject delay", IDC_STATIC, 7, 45, 47, 13, SS_LEFT | SS_CENTERIMAGE, WS_EX_LEFT
EDITTEXT IDC_EDIT4, 60, 65, 55, 14, ES_AUTOHSCROLL, WS_EX_LEFT
LTEXT "Insert delay", IDC_STATIC, 7, 65, 47, 13, SS_LEFT | SS_CENTERIMAGE, WS_EX_LEFT
AUTOCHECKBOX "Auto attach", IDC_CHECK1, 7, 87, 54, 10, 0, WS_EX_LEFT
AUTOCHECKBOX "Auto insert", IDC_CHECK2, 62, 87, 50, 10, 0, WS_EX_LEFT
AUTOCHECKBOX "Auto copy to clipboard", IDC_CHECK3, 7, 103, 88, 10, 0, WS_EX_LEFT
AUTOCHECKBOX "Auto suppress repetition", IDC_CHECK4, 7, 119, 95, 10, 0, WS_EX_LEFT
AUTOCHECKBOX "Reset character filter", IDC_CHECK6, 7, 149, 81, 8, 0, WS_EX_LEFT
AUTOCHECKBOX "Enable global filter", IDC_CHECK5, 7, 134, 75, 10, 0, WS_EX_LEFT
}
//
// Icon resources
//
LANGUAGE LANG_JAPANESE, SUBLANG_JAPANESE_JAPAN
IDI_ICON1 ICON "icon1.ico"
//
// Version Information resources
//
LANGUAGE LANG_NEUTRAL, SUBLANG_NEUTRAL
1 VERSIONINFO
FILEVERSION 0,0,0,0
PRODUCTVERSION 0,0,0,0
FILEOS VOS_UNKNOWN
FILETYPE VFT_UNKNOWN
FILESUBTYPE VFT2_UNKNOWN
FILEFLAGSMASK 0
FILEFLAGS 0
{
}

View File

@ -1,145 +0,0 @@
#include "ProcessWindow.h"
#include "resource.h"
#include "host/host.h"
#include "host/hookman.h"
#include "ProfileManager.h"
#include "profile/Profile.h"
extern HookManager* man; // main.cpp
extern ProfileManager* pfman; // ProfileManager.cpp
ProcessWindow::ProcessWindow(HWND hDialog) : hDlg(hDialog)
{
hbRefresh = GetDlgItem(hDlg, IDC_BUTTON1);
hbAttach = GetDlgItem(hDlg, IDC_BUTTON2);
hbDetach = GetDlgItem(hDlg, IDC_BUTTON3);
hbAddProfile = GetDlgItem(hDlg, IDC_BUTTON5);
hbRemoveProfile = GetDlgItem(hDlg, IDC_BUTTON6);
EnableWindow(hbAddProfile, FALSE);
EnableWindow(hbRemoveProfile, FALSE);
hlProcess = GetDlgItem(hDlg, IDC_LIST1);
heOutput = GetDlgItem(hDlg, IDC_EDIT1);
ListView_SetExtendedListViewStyleEx(hlProcess, LVS_EX_FULLROWSELECT, LVS_EX_FULLROWSELECT);
InitProcessDlg();
RefreshProcess();
EnableWindow(hbDetach, FALSE);
EnableWindow(hbAttach, FALSE);
}
void ProcessWindow::InitProcessDlg()
{
LVCOLUMN lvc = {};
lvc.mask = LVCF_FMT | LVCF_TEXT | LVCF_WIDTH;
lvc.fmt = LVCFMT_RIGHT; // left-aligned column
lvc.cx = 40;
lvc.pszText = L"PID";
ListView_InsertColumn(hlProcess, 0, &lvc);
lvc.cx = 100;
lvc.fmt = LVCFMT_LEFT; // left-aligned column
lvc.pszText = L"Name";
ListView_InsertColumn(hlProcess, 1, &lvc);
}
void ProcessWindow::RefreshProcess()
{
ListView_DeleteAllItems(hlProcess);
LVITEM item = {};
item.mask = LVIF_TEXT | LVIF_PARAM | LVIF_STATE;
DWORD idProcess[1024], cbNeeded;
WCHAR path[MAX_PATH];
if (EnumProcesses(idProcess, sizeof(idProcess), &cbNeeded))
{
DWORD len = cbNeeded / sizeof(DWORD);
for (DWORD i = 0; i < len; ++i)
{
DWORD pid = idProcess[i];
UniqueHandle hProcess(OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, FALSE, pid));
if (hProcess)
{
if (GetProcessImageFileName(hProcess.get(), path, MAX_PATH))
{
WCHAR buffer[256];
std::swprintf(buffer, L"%d", pid);
PWCHAR name = wcsrchr(path, L'\\') + 1;
item.pszText = buffer;
item.lParam = pid;
ListView_InsertItem(hlProcess, &item);
ListView_SetItemText(hlProcess, item.iItem, 1, name);
}
}
}
}
}
void ProcessWindow::AttachProcess()
{
DWORD pid = GetSelectedPID();
if (Host_InjectByPID(pid))
{
Host_HijackProcess(pid);
RefreshThreadWithPID(pid, true);
}
}
void ProcessWindow::DetachProcess()
{
DWORD pid = GetSelectedPID();
if (Host_ActiveDetachProcess(pid) == 0)
RefreshThreadWithPID(pid, false);
}
void ProcessWindow::CreateProfileForSelectedProcess()
{
DWORD pid = GetSelectedPID();
auto path = GetProcessPath(pid);
if (!path.empty())
{
Profile* pf = pfman->CreateProfile(pid);
pfman->SaveProfiles();
RefreshThread(ListView_GetSelectionMark(hlProcess));
}
}
void ProcessWindow::DeleteProfileForSelectedProcess()
{
DWORD pid = GetSelectedPID();
auto path = GetProcessPath(pid);
if (!path.empty())
{
pfman->DeleteProfile(path);
RefreshThread(ListView_GetSelectionMark(hlProcess));
}
}
void ProcessWindow::RefreshThread(int index)
{
LVITEM item = {};
item.mask = LVIF_PARAM;
item.iItem = index;
ListView_GetItem(hlProcess, &item);
DWORD pid = item.lParam;
bool isAttached = man->GetProcessRecord(pid) != NULL;
RefreshThreadWithPID(pid, isAttached);
}
void ProcessWindow::RefreshThreadWithPID(DWORD pid, bool isAttached)
{
EnableWindow(hbDetach, isAttached);
EnableWindow(hbAttach, !isAttached);
auto path = GetProcessPath(pid);
bool hasProfile = !path.empty() && pfman->HasProfile(path);
EnableWindow(hbAddProfile, isAttached && !hasProfile);
EnableWindow(hbRemoveProfile, hasProfile);
if (pid == GetCurrentProcessId())
EnableWindow(hbAttach, FALSE);
}
DWORD ProcessWindow::GetSelectedPID()
{
LVITEM item = {};
item.mask = LVIF_PARAM;
item.iItem = ListView_GetSelectionMark(hlProcess);
ListView_GetItem(hlProcess, &item);
return item.lParam;
}

View File

@ -1,23 +0,0 @@
#pragma once
#include "ITH.h"
class ProcessWindow
{
public:
ProcessWindow(HWND hDialog);
void InitProcessDlg();
void RefreshProcess();
void AttachProcess();
void DetachProcess();
void CreateProfileForSelectedProcess();
void DeleteProfileForSelectedProcess();
void RefreshThread(int index);
private:
void RefreshThreadWithPID(DWORD pid, bool isAttached);
DWORD GetSelectedPID();
HWND hDlg;
HWND hlProcess;
HWND hbRefresh,hbAttach,hbDetach,hbAddProfile,hbRemoveProfile;
HWND heOutput;
};

View File

@ -1,218 +0,0 @@
#include "ProfileManager.h"
#include "profile/Profile.h"
#include "host/host.h"
#include "host/hookman.h"
#include "vnrhook/include/types.h"
#include "vnrhook/include/const.h"
#include "utility.h"
#include "profile/misc.h"
extern HookManager* man; // main.cpp
extern LONG auto_inject, auto_insert, inject_delay; // main.cpp
extern LONG insert_delay, process_time; // main.cpp
bool MonitorFlag;
ProfileManager* pfman;
DWORD WINAPI MonitorThread(LPVOID lpThreadParameter);
ProfileManager::ProfileManager() :
hMonitorThread(IthCreateThread(MonitorThread, 0))
{
LoadProfiles();
}
ProfileManager::~ProfileManager()
{
SaveProfiles();
WaitForSingleObject(hMonitorThread.get(), 0);
}
Profile* ProfileManager::GetProfile(DWORD pid)
{
std::wstring path = GetProcessPath(pid);
if (!path.empty())
{
auto node = profile_tree.find(path);
if (node != profile_tree.end())
return node->second.get();
}
return NULL;
}
bool ProfileManager::CreateProfile(pugi::xml_node game)
{
auto file = game.child(L"File");
auto profile = game.child(L"Profile");
if (!file || !profile)
return false;
auto path = file.attribute(L"Path");
if (!path)
return false;
auto profile_title = game.attribute(L"Title");
auto title = profile_title ? profile_title.value() : L"";
auto pf = new Profile(title);
if (!pf->XmlReadProfile(profile))
return false;
CSLock lock(cs);
auto& oldProfile = profile_tree[path.value()];
if (!oldProfile)
oldProfile.swap(profile_ptr(pf));
return true;
}
Profile* ProfileManager::CreateProfile(DWORD pid)
{
CSLock lock(cs);
auto path = GetProcessPath(pid);
auto& pf = profile_tree[path];
if (!pf)
{
std::wstring title = GetProcessTitle(pid);
pf.reset(new Profile(title));
}
return pf.get();
}
void ProfileManager::WriteProfileXml(const std::wstring& path, Profile& pf, pugi::xml_node root)
{
auto game = root.append_child(L"Game");
auto file_node = game.append_child(L"File");
file_node.append_attribute(L"Path") = path.c_str();
auto profile_node = game.append_child(L"Profile");
pf.XmlWriteProfile(profile_node);
if (!pf.Title().empty())
{
if (!game.attribute(L"Title"))
game.append_attribute(L"Title");
game.attribute(L"Title") = pf.Title().c_str();
}
}
void ProfileManager::LoadProfiles()
{
pugi::xml_document doc;
UniqueHandle hFile(IthCreateFile(L"ITH_Profile.xml", GENERIC_READ, FILE_SHARE_READ, OPEN_EXISTING));
if (hFile.get() == INVALID_HANDLE_VALUE)
return;
DWORD size = GetFileSize(hFile.get(), NULL);
std::unique_ptr<char[]> buffer(new char[size]);
ReadFile(hFile.get(), buffer.get(), size, &size, NULL);
auto result = doc.load_buffer(buffer.get(), size);
if (!result)
return;
auto root = doc.root().child(L"ITH_Profile");
if (!root)
return;
for (auto game = root.begin(); game != root.end(); ++game)
CreateProfile(*game);
}
void ProfileManager::SaveProfiles()
{
pugi::xml_document doc;
auto root = doc.append_child(L"ITH_Profile");
for (auto it = profile_tree.begin(); it != profile_tree.end(); ++it) {
auto& path = it->first;
auto& profile = it->second;
WriteProfileXml(path, *profile, root);
}
UniqueHandle hFile(IthCreateFile(L"ITH_Profile.xml", GENERIC_WRITE, 0, CREATE_ALWAYS));
if (hFile.get() != INVALID_HANDLE_VALUE)
{
FileWriter fw(hFile.get());
doc.save(fw);
}
}
void ProfileManager::DeleteProfile(const std::wstring& path)
{
CSLock lock(cs);
profile_tree.erase(profile_tree.find(path));
}
Profile* ProfileManager::GetProfile(const std::wstring& path)
{
if (path.empty())
return nullptr;
auto it = profile_tree.find(path);
if (it == profile_tree.end())
return nullptr;
return it->second.get();
}
bool ProfileManager::HasProfile(const std::wstring& path)
{
return profile_tree.find(path) != profile_tree.end();
}
DWORD ProfileManager::CountProfiles()
{
return profile_tree.size();
}
DWORD WINAPI InjectThread(LPVOID lpThreadParameter)
{
DWORD pid = (DWORD)lpThreadParameter;
Sleep(inject_delay);
if (man == NULL)
return 0;
DWORD status = Host_InjectByPID(pid);
if (!auto_insert)
return status;
if (status == -1)
return status;
Sleep(insert_delay);
const Profile* pf = pfman->GetProfile(pid);
if (pf)
{
SendParam sp;
sp.type = 0;
for (auto hp = pf->Hooks().begin(); hp != pf->Hooks().end(); ++hp)
{
std::string name = toMultiByteString((*hp)->Name());
Host_InsertHook(pid, const_cast<HookParam*>(&(*hp)->HP()), name.c_str());
}
}
return status;
}
DWORD WINAPI MonitorThread(LPVOID lpThreadParameter)
{
while (MonitorFlag)
{
DWORD aProcesses[1024], cbNeeded, cProcesses;
if (!EnumProcesses(aProcesses, sizeof(aProcesses), &cbNeeded))
break;
cProcesses = cbNeeded / sizeof(DWORD);
for (size_t i = 0; i < cProcesses; ++i)
{
Sleep(process_time);
if (!auto_inject || man == NULL || man->GetProcessRecord(aProcesses[i]))
continue;
std::wstring process_path = GetProcessPath(aProcesses[i]);
if (!process_path.empty() && pfman->HasProfile(process_path))
{
UniqueHandle hThread(IthCreateThread(InjectThread, aProcesses[i]));
WaitForSingleObject(hThread.get(), 0);
}
}
}
return 0;
}
DWORD SaveProcessProfile(DWORD pid)
{
std::wstring path = GetProcessPath(pid);
if (path.empty())
return 0;
pugi::xml_document doc;
pugi::xml_node profile_node = doc.append_child(L"Profile");
man->GetProfile(pid, profile_node);
Profile* pf = pfman->GetProfile(pid);
if (pf != NULL)
pf->Clear();
else
pf = pfman->CreateProfile(pid);
pf->XmlReadProfile(profile_node);
return 0;
}

View File

@ -1,35 +0,0 @@
#pragma once
#include "ITH.h"
#include "utility.h" // UniqueHandle, CriticalSection
class Profile;
class ProfileManager
{
public:
ProfileManager();
~ProfileManager();
Profile* CreateProfile(DWORD pid);
Profile* GetProfile(DWORD pid);
Profile* GetProfile(const std::wstring& path);
void LoadProfiles();
void SaveProfiles();
void DeleteProfile(const std::wstring& path);
void UpdateHookAddresses(DWORD pid);
bool HasProfile(const std::wstring& path);
private:
typedef std::unique_ptr<Profile> profile_ptr;
typedef std::map<std::wstring, profile_ptr> profile_map;
ProfileManager(const ProfileManager&);
ProfileManager operator=(const ProfileManager&);
DWORD CountProfiles();
bool CreateProfile(pugi::xml_node game);
void WriteProfileXml(const std::wstring& path, Profile& pf, pugi::xml_node doc);
// locate profile with executable path
profile_map profile_tree;
CriticalSection cs;
UniqueHandle hMonitorThread;
};

View File

@ -1,41 +0,0 @@
#include "TextBuffer.h"
DWORD WINAPI FlushThread(LPVOID lParam); // window.cpp
TextBuffer::TextBuffer(HWND edit) : hThread(IthCreateThread(FlushThread, (DWORD)this)),
hEdit(edit),
running(true)
{
}
TextBuffer::~TextBuffer()
{
running = false;
WaitForSingleObject(hThread.get(), 0);
}
void TextBuffer::AddText(LPCWSTR str, int len, bool line)
{
CSLock lock(cs);
if (len > 0)
this->str.append(str, len);
line_break = line;
}
void TextBuffer::Flush()
{
CSLock lock(cs);
if (line_break || str.empty())
return;
DWORD t = Edit_GetTextLength(hEdit);
Edit_SetSel(hEdit, t, -1);
Edit_ReplaceSel(hEdit, str.c_str());
str.clear();
}
void TextBuffer::ClearBuffer()
{
CSLock lock(cs);
str.clear();
line_break = false;
}

View File

@ -1,21 +0,0 @@
#pragma once
#include "ITH.h"
#include "utility.h" // UniqueHandle, CriticalSection
class TextBuffer
{
public:
TextBuffer(HWND edit);
~TextBuffer();
void Flush();
void AddText(LPCWSTR str, int len, bool line);
void ClearBuffer();
bool Running() { return running; }
private:
CriticalSection cs;
bool line_break, running;
UniqueHandle hThread;
HWND hEdit;
std::wstring str;
};

View File

@ -1,73 +0,0 @@
/* Copyright (C) 2010-2012 kaosu (qiupf2000@gmail.com)
* This file is part of the Interactive Text Hooker.
* Interactive Text Hooker 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 <http://www.gnu.org/licenses/>.
*/
#include "ITH.h"
#include "host/host.h"
#include "vnrhook/include/const.h"
#include "vnrhook/include/types.h"
#include "language.h"
#include "utility.h"
#include "profile/misc.h"
extern HookManager* man;
extern HWND hwndProcessComboBox;
DWORD ProcessCommand(const std::wstring& cmd, DWORD pid)
{
using std::wregex;
using std::regex_match;
std::match_results<std::wstring::const_iterator> m;
if (regex_match(cmd, m, wregex(L"/pn(.+)", wregex::icase)))
{
pid = Host_GetPIDByName(m[1].str().c_str());
if (pid == 0)
return 0;
Host_InjectByPID(pid);
}
else if (regex_match(cmd, m, wregex(L"/p(\\d+)", wregex::icase)))
{
pid = std::stoul(m[1].str());
Host_InjectByPID(pid);
}
else if (regex_match(cmd, m, wregex(L"/h(.+)", wregex::icase)))
{
HookParam hp = {};
if (Parse(m[1].str(), hp))
Host_InsertHook(pid, &hp);
}
else if (regex_match(cmd, m, wregex(L":l([[:xdigit:]]+)-([[:xdigit:]]+)", wregex::icase)))
{
DWORD from = std::stoul(m[1].str(), NULL, 16);
DWORD to = std::stoul(m[2].str(), NULL, 16);
Host_AddLink(from, to);
}
else if (regex_match(cmd, m, wregex(L":u([[:xdigit:]]+)", wregex::icase)))
{
DWORD from = std::stoul(m[1].str(), NULL, 16);
Host_UnLink(from);
}
else if (regex_match(cmd, m, wregex(L":(?:h|help)", wregex::icase)))
{
ConsoleOutput(Usage);
}
else
{
ConsoleOutput(L"Unknown command. Type :h or :help for help.");
}
return 0;
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

View File

@ -1,132 +0,0 @@
/* Copyright (C) 2010-2012 kaosu (qiupf2000@gmail.com)
* This file is part of the Interactive Text Hooker.
* Interactive Text Hooker 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 <http://www.gnu.org/licenses/>.
*/
const wchar_t* Warning=L"Warning!";
//command.cpp
const wchar_t* ErrorSyntax=L"Syntax error";
const wchar_t* Usage = L"Syntax:\r\n\
\r\n\
:H[ELP] - print help\r\n\
:Lfrom-to - link from thread 'from' to thread 'to'\r\n\
:Ufrom - unlink link from thread 'from'\r\n\
\r\n\
'from' and 'to' and hexadecimal thread numbers. The thread number is the first number in the combo box.\r\n\
\r\n\
Loader options:\r\n\
/P[{process_id|Nprocess_name}] - attach to process\r\n\
\r\n\
Hook options:\r\n\
/H[X]{A|B|W|S|Q}[N][data_offset[*drdo]][:sub_offset[*drso]]@addr[:module[:{name|#ordinal}]]\r\n\
\r\n\
All numbers in /H (except ordinal) are hexadecimal without any prefixes";
const wchar_t* ExtendedUsage = L"/H[X]{A|B|W|S|Q}[N][data_offset[*drdo]][:sub_offset[*drso]]@addr[:[module[:{name|#ordinal}]]]\r\n\
\r\n\
Set additional custom hook\r\n\
\r\n\
Hook types :\r\n\
A - DBCS char\r\n\
B - DBCS char(big-endian)\r\n\
W - UCS2 char\r\n\
S - MBCS string\r\n\
Q - UTF-16 string\r\n\
\r\n\
Parameters:\r\n\
X - use hardware breakpoints\r\n\
N - don't use contexts\r\n\
data_offset - stack offset to char / string pointer\r\n\
drdo - add a level of indirection to data_offset\r\n\
sub_offset - stack offset to subcontext\r\n\
drso - add a level of indirection to sub_offset\r\n\
addr - address of the hook\r\n\
module - name of the module to use as base for 'addr'\r\n\
name - name of the 'module' export to use as base for 'addr'\r\n\
ordinal - number of the 'module' export ordinal to use as base for 'addr'\r\n\
\r\n\
Negative values of 'data_offset' and 'sub_offset' refer to registers: \r\n\
- 4 for EAX, -8 for ECX, -C for EDX, -10 for EBX, -14 for ESP, -18 for EBP, -1C for ESI, -20 for EDI\r\n\
\r\n\
\"Add a level of indirection\" means in C/C++ style: (*(ESP+data_offset)+drdo) instead of (ESP+data_offset)\r\n\
\r\n\
All numbers except ordinal are hexadecimal without any prefixes";
//inject.cpp
const wchar_t* ErrorRemoteThread=L"Can't create remote thread.";
const wchar_t* ErrorOpenProcess=L"Can't open process.";
const wchar_t* ErrorNoProcess=L"Process not found";
const wchar_t* SelfAttach=L"Please do not attach to ITH.exe";
const wchar_t* AlreadyAttach=L"Process already attached.";
const wchar_t* FormatInject=L"Inject process %d. Module base %.8X";
//main.cpp
const wchar_t* NotAdmin=L"Can't enable SeDebugPrevilege. ITH might malfunction.\r\n\
Please run ITH as administrator or turn off UAC.";
//pipe.cpp
const wchar_t* ErrorCreatePipe=L"Can't create text pipe or too many instance.";
const wchar_t* FormatDetach=L"Process %d detached.";
const wchar_t* ErrorCmdQueueFull=L"Command queue full.";
const wchar_t* ErrorNoAttach=L"No process attached.";
//profile.cpp
const wchar_t* ErrorMonitor=L"Can't monitor process.";
//utility.cpp
const wchar_t* InitMessage=L"Copyright (C) 2010-2012 kaosu <qiupf2000@gmail.com>\r\n\
Copyright (C) 2015 zorkzero <zorkzero@hotmail.com>\r\n\
Source code <https://code.google.com/p/interactive-text-hooker/>\r\n\
General discussion <https://groups.google.com/forum/?fromgroups#!forum/interactive-text-hooker>";
const wchar_t* BackgroundMsg=L"Type \":h\" or \":help\" for help.";
const wchar_t* ErrorLinkExist=L"Link exist.";
const wchar_t* ErrorCylicLink=L"Link failed. No cyclic link allowed.";
const wchar_t* FormatLink=L"Link from thread%.4x to thread%.4x.";
const wchar_t* ErrorLink=L"Link failed. Source or/and destination thread not found.";
const wchar_t* ErrorDeleteCombo=L"Error delete from combo.";
//window.cpp
const wchar_t* ClassName=L"ITH";
const wchar_t* ClassNameAdmin=L"ITH (Administrator)";
const wchar_t* ErrorNotSplit=L"Need to enable split first!";
const wchar_t* ErrorNotModule=L"Need to enable module first!";
//Main window buttons
const wchar_t* ButtonTitleProcess=L"Process";
const wchar_t* ButtonTitleThread=L"Thread";
const wchar_t* ButtonTitleHook=L"Hook";
const wchar_t* ButtonTitleProfile=L"Profile";
const wchar_t* ButtonTitleOption=L"Option";
const wchar_t* ButtonTitleClear=L"Clear";
const wchar_t* ButtonTitleSave=L"Save";
const wchar_t* ButtonTitleTop=L"Top";
//Hook window
const wchar_t* SpecialHook=L"Special hook, no AGTH equivalent.";
//Process window
const wchar_t* TabTitlePID=L"PID";
const wchar_t* TabTitleMemory=L"Memory";
const wchar_t* TabTitleName=L"Name";
const wchar_t* TabTitleTID=L"TID";
const wchar_t* TabTitleStart=L"Start";
const wchar_t* TabTitleModule=L"Module";
const wchar_t* TabTitleState=L"State";
const wchar_t* SuccessAttach=L"Attach ITH to process successfully.";
const wchar_t* FailAttach=L"Failed to attach ITH to process.";
const wchar_t* SuccessDetach=L"ITH detach from process.";
const wchar_t* FailDetach=L"Detach failed.";
//Profile window
const wchar_t* ProfileExist=L"Profile already exists.";
const wchar_t* SuccessAddProfile=L"Profile added.";
const wchar_t* FailAddProfile=L"Fail to add profile";
const wchar_t* TabTitleNumber=L"No.";
const wchar_t* NoFile=L"Can't find file.";
const wchar_t* PathDismatch=L"Process name dismatch, continue?";
const wchar_t* SuccessImportProfile=L"Import profile success";
//const wchar_t* SuccessAddProfile=L"Profile added.";

View File

@ -1,86 +0,0 @@
/* Copyright (C) 2010-2012 kaosu (qiupf2000@gmail.com)
* This file is part of the Interactive Text Hooker.
* Interactive Text Hooker 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 <http://www.gnu.org/licenses/>.
*/
#pragma once
extern const wchar_t* Warning;
//command.cpp
extern const wchar_t* ErrorSyntax;
extern const wchar_t* Usage;
extern const wchar_t* ExtendedUsage;
//inject.cpp
extern const wchar_t* ErrorRemoteThread;
extern const wchar_t* ErrorOpenProcess;
extern const wchar_t* ErrorNoProcess;
extern const wchar_t* SelfAttach;
extern const wchar_t* AlreadyAttach;
extern const wchar_t* FormatInject;
//main.cpp
extern const wchar_t* NotAdmin;
//pipe.cpp
extern const wchar_t* ErrorCreatePipe;
extern const wchar_t* FormatDetach;
extern const wchar_t* ErrorCmdQueueFull;
extern const wchar_t* ErrorNoAttach;
//profile.cpp
extern const wchar_t* ErrorMonitor;
//utility.cpp
extern const wchar_t* InitMessage;
extern const wchar_t* BackgroundMsg;
extern const wchar_t* ErrorLinkExist;
extern const wchar_t* ErrorCylicLink;
extern const wchar_t* FormatLink;
extern const wchar_t* ErrorLink;
extern const wchar_t* ErrorDeleteCombo;
//window.cpp
extern const wchar_t* ClassName;
extern const wchar_t* ClassNameAdmin;
extern const wchar_t* ErrorNotSplit;
extern const wchar_t* ErrorNotModule;
//Main window buttons
extern const wchar_t* ButtonTitleProcess;
extern const wchar_t* ButtonTitleThread;
extern const wchar_t* ButtonTitleHook;
extern const wchar_t* ButtonTitleProfile;
extern const wchar_t* ButtonTitleOption;
extern const wchar_t* ButtonTitleClear;
extern const wchar_t* ButtonTitleSave;
extern const wchar_t* ButtonTitleTop;
//Hook window
extern const wchar_t* SpecialHook;
//Process window
extern const wchar_t* TabTitlePID;
extern const wchar_t* TabTitleMemory;
extern const wchar_t* TabTitleName;
extern const wchar_t* TabTitleTID;
extern const wchar_t* TabTitleStart;
extern const wchar_t* TabTitleModule;
extern const wchar_t* TabTitleState;
extern const wchar_t* SuccessAttach;
extern const wchar_t* FailAttach;
extern const wchar_t* SuccessDetach;
extern const wchar_t* FailDetach;
//Profile window
extern const wchar_t* ProfileExist;
extern const wchar_t* SuccessAddProfile;
extern const wchar_t* FailAddProfile;
extern const wchar_t* TabTitleNumber;
extern const wchar_t* NoFile;
extern const wchar_t* PathDismatch;
extern const wchar_t* SuccessImportProfile;

View File

@ -1,286 +0,0 @@
/* Copyright (C) 2010-2012 kaosu (qiupf2000@gmail.com)
* This file is part of the Interactive Text Hooker.
* Interactive Text Hooker 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 <http://www.gnu.org/licenses/>.
*/
#include "ITH.h"
#include "host/host.h"
#include "host/hookman.h"
#include "host/settings.h"
#include "CustomFilter.h"
#include "profile/Profile.h"
#include "ProfileManager.h"
HINSTANCE hIns;
ATOM MyRegisterClass(HINSTANCE hInstance);
BOOL InitInstance(HINSTANCE hInstance, DWORD nCmdShow, RECT *rc);
RECT window;
extern HWND hMainWnd; // windows.cpp
extern bool MonitorFlag; // ProfileManager.cpp
extern ProfileManager* pfman; // ProfileManager.cpp
extern "C" {
BOOL IthInitSystemService();
void IthCloseSystemService();
}
CustomFilter* uni_filter;
CustomFilter* mb_filter;
HookManager* man;
Settings* setman;
LONG split_time, cyclic_remove, global_filter;
LONG process_time, inject_delay, insert_delay,
auto_inject, auto_insert, clipboard_flag;
std::map<std::wstring, long> setting;
void RecordMBChar(WORD mb, PVOID f)
{
auto filter = (pugi::xml_node*)f;
DWORD m = mb;
WCHAR buffer[16];
std::swprintf(buffer, L"m%04X", m);
filter->append_attribute(buffer) = L"0";
}
void RecordUniChar(WORD uni, PVOID f)
{
auto filter = (pugi::xml_node*)f;
DWORD m = uni;
WCHAR buffer[16];
std::swprintf(buffer, L"u%04X", m);
filter->append_attribute(buffer) = L"0";
std::wstring text = filter->text().get();
text += (wchar_t)m;
filter->text().set(text.c_str());
}
void SaveSettings()
{
WINDOWPLACEMENT wndpl;
wndpl.length = sizeof(WINDOWPLACEMENT);
GetWindowPlacement(hMainWnd, &wndpl);
setting[L"window_left"] = wndpl.rcNormalPosition.left;
setting[L"window_right"] = wndpl.rcNormalPosition.right;
setting[L"window_top"] = wndpl.rcNormalPosition.top;
setting[L"window_bottom"] = wndpl.rcNormalPosition.bottom;
setting[L"split_time"] = split_time;
setting[L"process_time"] = process_time;
setting[L"inject_delay"] = inject_delay;
setting[L"insert_delay"] = insert_delay;
setting[L"auto_inject"] = auto_inject;
setting[L"auto_insert"] = auto_insert;
setting[L"auto_copy"] = clipboard_flag;
setting[L"auto_suppress"] = cyclic_remove;
setting[L"global_filter"] = global_filter;
UniqueHandle hFile(IthCreateFile(L"ITH.xml", GENERIC_WRITE, FILE_SHARE_READ, CREATE_ALWAYS));
if (hFile.get() != INVALID_HANDLE_VALUE)
{
FileWriter fw(hFile.get());
pugi::xml_document doc;
auto root = doc.root().append_child(L"ITH_Setting");
for (auto it = setting.begin(); it != setting.end(); ++it)
root.append_attribute(it->first.c_str()).set_value(it->second);
auto filter = root.append_child(L"SingleCharFilter");
filter.append_child(pugi::xml_node_type::node_pcdata);
mb_filter->Traverse(RecordMBChar, &filter);
uni_filter->Traverse(RecordUniChar, &filter);
doc.save(fw);
}
}
void DefaultSettings()
{
setting[L"split_time"] = 200;
setting[L"process_time"] = 50;
setting[L"inject_delay"] = 3000;
setting[L"insert_delay"] = 500;
setting[L"auto_inject"] = 1;
setting[L"auto_insert"] = 1;
setting[L"auto_copy"] = 0;
setting[L"auto_suppress"] = 0;
setting[L"global_filter"] = 0;
setting[L"window_left"] = 100;
setting[L"window_right"] = 800;
setting[L"window_top"] = 100;
setting[L"window_bottom"] = 600;
}
void InitializeSettings()
{
split_time = setting[L"split_time"];
process_time = setting[L"process_time"];
inject_delay = setting[L"inject_delay"];
insert_delay = setting[L"insert_delay"];
auto_inject = setting[L"auto_inject"];
auto_insert = setting[L"auto_insert"];
clipboard_flag = setting[L"auto_copy"];
cyclic_remove = setting[L"auto_suppress"];
global_filter = setting[L"global_filter"];
window.left = setting[L"window_left"];
window.right = setting[L"window_right"];
window.top = setting[L"window_top"];
window.bottom = setting[L"window_bottom"];
if (auto_inject > 1)
auto_inject = 1;
if (auto_insert > 1)
auto_insert = 1;
if (clipboard_flag > 1)
clipboard_flag = 1;
if (cyclic_remove > 1)
cyclic_remove = 1;
if (window.right < window.left || window.right - window.left < 600)
window.right = window.left + 600;
if (window.bottom < window.top || window.bottom - window.top < 200)
window.bottom = window.top + 200;
}
void LoadSettings()
{
UniqueHandle hFile(IthCreateFile(L"ITH.xml", GENERIC_READ, FILE_SHARE_READ, OPEN_EXISTING));
if (hFile.get() != INVALID_HANDLE_VALUE)
{
DWORD size = GetFileSize(hFile.get(), NULL);
std::unique_ptr<char[]> buffer(new char[size]);
ReadFile(hFile.get(), buffer.get(), size, &size, NULL);
pugi::xml_document doc;
auto result = doc.load_buffer_inplace(buffer.get(), size);
if (!result)
return;
auto root = doc.root().child(L"ITH_Setting");
for (auto attr = root.attributes_begin(); attr != root.attributes_end(); ++attr)
{
auto it = setting.find(attr->name());
if (it != setting.end())
it->second = std::stoul(attr->value());
}
auto filter = root.child(L"SingleCharFilter");
if (filter)
{
for (auto attr = filter.attributes_begin(); attr != filter.attributes_end(); ++attr)
{
if (attr->name()[0] == L'm')
{
DWORD c = std::stoul(attr->name() + 1, NULL, 16);
mb_filter->Insert(c & 0xFFFF);
}
else if (attr->name()[0] == L'u')
{
DWORD c = std::stoul(attr->name() + 1, NULL, 16);
uni_filter->Insert(c & 0xFFFF);
}
}
std::wstring filter_value = filter.text().get();
for (auto it = filter_value.begin(); it != filter_value.end(); ++it)
{
WCHAR filter_unichar[2] = { *it, L'\0' };
char filter_mbchar[4];
WC_MB(filter_unichar, filter_mbchar, 4);
mb_filter->Insert(*(WORD*)filter_mbchar);
uni_filter->Insert(*it);
}
}
}
}
extern LPCWSTR ClassName, ClassNameAdmin;
static WCHAR mutex[] = L"ITH_RUNNING";
DWORD FindITH()
{
HWND hwnd = FindWindow(ClassName, ClassName);
if (hwnd == NULL)
hwnd = FindWindow(ClassName, ClassNameAdmin);
if (hwnd)
{
ShowWindow(hwnd, SW_SHOWNORMAL);
SetForegroundWindow(hwnd);
return 0;
}
return 1;
}
LONG WINAPI UnhandledExcept(_EXCEPTION_POINTERS *ExceptionInfo)
{
wchar_t path_name[512]; // fully qualified path name
WCHAR code[16];
EXCEPTION_RECORD* rec = ExceptionInfo->ExceptionRecord;
std::swprintf(code, L"%08X", rec->ExceptionCode);
MEMORY_BASIC_INFORMATION info;
if (VirtualQuery(rec->ExceptionAddress, &info, sizeof(info)))
{
if (GetModuleFileName((HMODULE)info.AllocationBase, path_name, 512))
{
LPWSTR name = wcsrchr(path_name, L'\\');
if (name)
{
DWORD addr = (DWORD)rec->ExceptionAddress;
std::swprintf(name, L"%s:%08X", name + 1, addr - (DWORD)info.AllocationBase);
MessageBox(NULL, name, code, MB_OK);
TerminateProcess(GetCurrentProcess(), 0);
}
}
}
std::swprintf(path_name, L"%08X", rec->ExceptionAddress);
MessageBox(NULL, path_name, code, MB_OK);
TerminateProcess(GetCurrentProcess(), 0);
return 0;
}
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
{
InitCommonControls();
if (!IthInitSystemService())
TerminateProcess(GetCurrentProcess(), 0);
CreateMutex(NULL, TRUE, L"ITH_MAIN_RUNNING");
if (Host_Open())
{
SetUnhandledExceptionFilter(UnhandledExcept);
Host_GetHookManager(&man);
Host_GetSettings(&setman);
setman->splittingInterval = 200;
MonitorFlag = true;
pfman = new ProfileManager();
mb_filter = new CustomFilter();
uni_filter = new CustomFilter();
DefaultSettings();
LoadSettings();
InitializeSettings();
setman->splittingInterval = split_time;
setman->clipboardFlag = clipboard_flag > 0;
hIns = hInstance;
MyRegisterClass(hIns);
InitInstance(hIns, FALSE, &window);
MSG msg;
while (GetMessage(&msg, NULL, 0, 0))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
//delete mb_filter;
//delete uni_filter;
delete pfman;
MonitorFlag = false;
man = NULL;
}
else
{
FindITH();
}
Host_Close();
IthCloseSystemService();
TerminateProcess(GetCurrentProcess(), 0);
}

View File

@ -1,23 +0,0 @@
#ifndef IDC_STATIC
#define IDC_STATIC (-1)
#endif
#define IDD_DIALOG2 102
#define IDD_DIALOG4 104
#define IDI_ICON1 110
#define IDC_CHECK1 1000
#define IDC_CHECK2 1001
#define IDC_CHECK3 1002
#define IDC_CHECK4 1003
#define IDC_CHECK5 1004
#define IDC_EDIT1 1011
#define IDC_EDIT2 1012
#define IDC_EDIT3 1013
#define IDC_EDIT4 1014
#define IDC_BUTTON1 1020
#define IDC_BUTTON2 1021
#define IDC_BUTTON3 1022
#define IDC_BUTTON5 1024
#define IDC_LIST1 1028
#define IDC_BUTTON6 40000
#define IDC_CHECK6 40001

View File

@ -1,303 +0,0 @@
/* Copyright (C) 2010-2012 kaosu (qiupf2000@gmail.com)
* This file is part of the Interactive Text Hooker.
* Interactive Text Hooker 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 <http://www.gnu.org/licenses/>.
*/
#include "utility.h"
#include "host/host.h"
#include "host/hookman.h"
#include "vnrhook/include/types.h"
#include "vnrhook/include/const.h"
#include "profile/misc.h"
extern HookManager* man; // main.cpp
std::wstring GetDriveLetter(const std::wstring& devicePath);
std::wstring GetWindowsPath(const std::wstring& fileObjectPath);
PVOID GetAllocationBase(DWORD pid, LPCVOID);
std::wstring GetModuleFileNameAsString(DWORD pid, PVOID allocationBase);
std::wstring GetModuleFileNameAsString();
std::wstring GetProcessPath(HANDLE hProc);
void ConsoleOutput(LPCWSTR text)
{
man->AddConsoleOutput(text);
}
void ConsoleOutput(LPCSTR text)
{
int wc_length = MB_WC_count(text, -1);
LPWSTR wc = new WCHAR[wc_length];
MB_WC(text, wc, wc_length);
man->AddConsoleOutput(wc);
delete wc;
}
std::wstring GetProcessPath(DWORD pid)
{
UniqueHandle hProc(OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, FALSE, pid));
if (hProc)
return GetProcessPath(hProc.get());
else
return L"";
}
std::wstring GetProcessPath(HANDLE hProc)
{
wchar_t path[MAX_PATH];
GetProcessImageFileName(hProc, path, MAX_PATH);
return GetWindowsPath(path);
}
std::wstring GetWindowsPath(const std::wstring& path)
{
// path is in device form
// \Device\HarddiskVolume2\Windows\System32\taskhost.exe
auto pathOffset = path.find(L'\\', 1) + 1;
pathOffset = path.find(L'\\', pathOffset);
std::wstring devicePath = path.substr(0, pathOffset); // \Device\HarddiskVolume2
std::wstring dosDrive = GetDriveLetter(devicePath); // C:
if (dosDrive.empty())
return path;
std::wstring dosPath = dosDrive; // C:
dosPath += path.substr(pathOffset); // C:\Windows\System32\taskhost.exe
return dosPath;
}
std::wstring GetDriveLetter(const std::wstring& devicePath)
{
for (wchar_t drive = L'A'; drive <= L'Z'; drive++)
{
wchar_t szDriveName[3] = { drive, L':', L'\0' };
wchar_t szTarget[512];
if (QueryDosDevice(szDriveName, szTarget, 512))
if (devicePath.compare(szTarget) == 0)
return szDriveName;
}
return L"";
}
std::wstring GetCode(const HookParam& hp, DWORD pid)
{
std::wstring code(L"/H");
WCHAR c;
if (hp.type & PRINT_DWORD)
c = L'H';
else if (hp.type & USING_UNICODE)
{
if (hp.type & USING_STRING)
c = L'Q';
else if (hp.type & STRING_LAST_CHAR)
c = L'L';
else
c = L'W';
}
else
{
if (hp.type & USING_STRING)
c = L'S';
else if (hp.type & BIG_ENDIAN)
c = L'A';
else if (hp.type & STRING_LAST_CHAR)
c = L'E';
else
c = L'B';
}
code += c;
if (hp.type & NO_CONTEXT)
code += L'N';
if (hp.offset >> 31)
code += L"-" + ToHexString(-(hp.offset + 4));
else
code += ToHexString(hp.offset);
if (hp.type & DATA_INDIRECT)
{
if (hp.index >> 31)
code += L"*-" + ToHexString(-hp.index);
else
code += L"*" + ToHexString(hp.index);
}
if (hp.type & USING_SPLIT)
{
if (hp.split >> 31)
code += L":-" + ToHexString(-(4 + hp.split));
else
code += L":" + ToHexString(hp.split);
}
if (hp.type & SPLIT_INDIRECT)
{
if (hp.split_index >> 31)
code += L"*-" + ToHexString(-hp.split_index);
else
code += L"*" + ToHexString(hp.split_index);
}
if (pid)
{
PVOID allocationBase = GetAllocationBase(pid, (LPCVOID)hp.address);
if (allocationBase)
{
std::wstring path = GetModuleFileNameAsString(pid, allocationBase);
if (!path.empty())
{
auto fileName = path.substr(path.rfind(L'\\') + 1);
DWORD relativeHookAddress = hp.address - (DWORD)allocationBase;
code += L"@" + ToHexString(relativeHookAddress) + L":" + fileName;
return code;
}
}
}
if (hp.module)
{
code += L"@" + ToHexString(hp.address) + L"!" + ToHexString(hp.module);
if (hp.function)
code += L"!" + ToHexString(hp.function);
}
else
{
// Hack. The original address is stored in the function field
// if (module == NULL && function != NULL).
// MODULE_OFFSET and FUNCTION_OFFSET are removed from HookParam.type in
// TextHook::UnsafeInsertHookCode() and can not be used here.
if (hp.function)
code += L"@" + ToHexString(hp.function);
else
code += L"@" + ToHexString(hp.address) + L":";
}
return code;
}
std::wstring GetModuleFileNameAsString(DWORD pid, PVOID allocationBase)
{
const ProcessRecord* pr = man->GetProcessRecord(pid);
if (pr)
{
HANDLE hProc = pr->process_handle;
WCHAR path[MAX_PATH];
if (GetModuleFileNameEx(hProc, (HMODULE)allocationBase, path, MAX_PATH))
return path;
}
return L"";
}
PVOID GetAllocationBase(DWORD pid, LPCVOID addr)
{
const ProcessRecord *pr = man->GetProcessRecord(pid);
if (pr)
{
MEMORY_BASIC_INFORMATION info;
HANDLE hProc = pr->process_handle;
if (VirtualQueryEx(hProc, addr, &info, sizeof(info)))
{
if (info.Type & MEM_IMAGE)
return info.AllocationBase;
}
}
return NULL;
}
struct TitleParam
{
DWORD pid, buffer_len, retn_len;
std::wstring buffer;
};
BOOL CALLBACK EnumProc(HWND hwnd, LPARAM lParam)
{
TitleParam* p = (TitleParam*)lParam;
DWORD pid;
GetWindowThreadProcessId(hwnd, &pid);
if (pid == p->pid)
{
if (GetWindowLong(hwnd, GWL_STYLE) & WS_VISIBLE)
{
int len = GetWindowTextLength(hwnd);
std::unique_ptr<wchar_t[]> result(new wchar_t[len + 1]);
GetWindowText(hwnd, result.get(), len + 1);
p->buffer = result.get();
p->retn_len = p->buffer.size();
if (!p->buffer.empty())
return FALSE;
}
}
return TRUE;
}
std::wstring GetProcessTitle(DWORD pid)
{
TitleParam p;
p.pid = pid;
p.buffer_len = 0;
p.retn_len = 0;
EnumWindows(EnumProc, (LPARAM)&p);
return p.buffer;
}
WindowsError::WindowsError(DWORD error_code) : error_code(error_code), msg("")
{
CHAR str[512];
std::sprintf(str, "error code 0x%8x", error_code);
msg = str;
}
const char *WindowsError::what() const
{
return msg.c_str();
}
HANDLE IthCreateThread(LPVOID start_addr, DWORD param)
{
return CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)start_addr, (LPVOID)param, 0, NULL);
}
std::wstring GetModuleFileNameAsString()
{
WCHAR path[MAX_PATH];
GetModuleFileName(NULL, path, MAX_PATH);
return path;
}
bool IthCreateDirectory(LPCWSTR name)
{
std::wstring path = GetModuleFileNameAsString();
path = path.substr(0, path.rfind(L'\\') + 1) + name;
BOOL error_code = CreateDirectory(path.c_str(), NULL);
return error_code != 0 || GetLastError() == ERROR_ALREADY_EXISTS;
}
HANDLE IthCreateFile(LPCWSTR name, DWORD option, DWORD share, DWORD disposition)
{
std::wstring path = GetModuleFileNameAsString();
path = path.substr(0, path.rfind(L'\\') + 1) + name;
return CreateFile(path.c_str(), option, share, NULL, disposition, FILE_ATTRIBUTE_NORMAL, NULL);
}
//SJIS->Unicode. mb must be null-terminated. wc_length is the length of wc in characters.
int MB_WC(const char* mb, wchar_t* wc, int wc_length)
{
return MultiByteToWideChar(932, 0, mb, -1, wc, wc_length);
}
// Count characters in wide string. mb_length is the number of bytes from mb to convert or
// -1 if the string is null terminated.
int MB_WC_count(const char* mb, int mb_length)
{
return MultiByteToWideChar(932, 0, mb, mb_length, NULL, 0);
}
// Unicode->SJIS. Analogous to MB_WC.
int WC_MB(const wchar_t *wc, char* mb, int mb_length)
{
return WideCharToMultiByte(932, 0, wc, -1, mb, mb_length, NULL, NULL);
}

View File

@ -1,105 +0,0 @@
#pragma once
#include "ITH.h"
struct HookParam;
struct ProcessRecord;
DWORD ProcessCommand(const std::wstring& cmd, DWORD pid);
std::wstring GetProcessPath(DWORD pid);
void ConsoleOutput(LPCWSTR);
void ConsoleOutput(LPCSTR text);
std::wstring GetProcessTitle(DWORD pid);
std::wstring GetCode(const HookParam& hp, DWORD pid = 0);
// http://codesequoia.wordpress.com/2012/08/26/stdunique_ptr-for-windows-handles/
struct HandleDeleter
{
typedef HANDLE pointer;
void operator() (HANDLE h)
{
if (h != INVALID_HANDLE_VALUE) {
CloseHandle(h);
}
}
};
typedef std::unique_ptr<HANDLE, HandleDeleter> UniqueHandle;
class FileWriter : public pugi::xml_writer
{
HANDLE hFile;
public:
FileWriter(HANDLE hFile) : hFile(hFile) {};
~FileWriter() {};
virtual void write(const void* data, size_t size)
{
DWORD dwNumberOfBytesWritten;
WriteFile(hFile, data, size, &dwNumberOfBytesWritten, NULL);
}
};
class WindowsError : public std::exception
{
private:
std::string msg;
DWORD error_code;
public:
WindowsError(DWORD error_code);
virtual const char *what() const;
};
HANDLE IthCreateThread(LPVOID start_addr, DWORD param);
bool IthCreateDirectory(LPCWSTR name);
HANDLE IthCreateFile(LPCWSTR name, DWORD option, DWORD share, DWORD disposition);
int MB_WC(const char* mb, wchar_t* wc, int wc_length);
int MB_WC_count(const char* mb, int mb_length);
int WC_MB(const wchar_t *wc, char* mb, int mb_length);
bool Parse(const std::wstring& cmd, HookParam& hp);
// http://jrdodds.blogs.com/blog/2004/08/raii_in_c.html
class CriticalSection
{
public:
CriticalSection()
{
::InitializeCriticalSection(&m_rep);
}
~CriticalSection()
{
::DeleteCriticalSection(&m_rep);
}
void Enter()
{
::EnterCriticalSection(&m_rep);
}
void Leave()
{
::LeaveCriticalSection(&m_rep);
}
private:
CriticalSection(const CriticalSection&);
CriticalSection& operator=(const CriticalSection&);
CRITICAL_SECTION m_rep;
};
class CSLock
{
public:
CSLock(CriticalSection& a_section)
: m_section(a_section)
{
m_section.Enter();
}
~CSLock()
{
m_section.Leave();
}
private:
CSLock(const CSLock&);
CSLock& operator=(const CSLock&);
CriticalSection& m_section;
};

View File

@ -1,2 +0,0 @@
const wchar_t* build_date=L"27.01.2013";
const WCHAR program_version[] = L"@CPACK_PACKAGE_VERSION_MAJOR@.@CPACK_PACKAGE_VERSION_MINOR@.@CPACK_PACKAGE_VERSION_PATCH@";

View File

@ -1,866 +0,0 @@
/* Copyright (C) 2010-2012 kaosu (qiupf2000@gmail.com)
* This file is part of the Interactive Text Hooker.
* Interactive Text Hooker 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 <http://www.gnu.org/licenses/>.
*/
#include "window.h"
#include "ProcessWindow.h"
#include "resource.h"
#include "language.h"
#include "host/host.h"
#include "host/hookman.h"
#include "vnrhook/include/const.h"
#include "version.h"
#include "ProfileManager.h"
#include "host/settings.h"
#include "CustomFilter.h"
#include "profile/Profile.h"
#include "TextBuffer.h"
#include "profile/misc.h"
#define CMD_SIZE 512
static WNDPROC proc, proccmd, procChar;
static WCHAR last_cmd[CMD_SIZE];
extern HINSTANCE hIns; // main.cpp
HWND hMainWnd, hwndCombo, hwndProcessComboBox, hwndEdit, hwndCmd;
HWND hwndProcess;
HWND hwndOption, hwndTop, hwndClear, hwndSave, hwndRemoveLink, hwndRemoveHook;
HWND hProcDlg, hOptionDlg;
HBRUSH hWhiteBrush;
DWORD background;
ProcessWindow* pswnd;
TextBuffer* texts;
extern ProfileManager* pfman; // ProfileManager.cpp
extern HookManager* man; // main.cpp
extern CustomFilter* mb_filter; // main.cpp
extern CustomFilter* uni_filter; // main.cpp
extern Settings* setman; // main.cpp
#define COMMENT_BUFFER_LENGTH 512
static WCHAR comment_buffer[COMMENT_BUFFER_LENGTH];
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam);
void SaveSettings(); // main.cpp
extern LONG split_time, process_time, inject_delay, insert_delay,
auto_inject, auto_insert, clipboard_flag, cyclic_remove, global_filter; //main.cpp
static int last_select, last_edit;
ATOM MyRegisterClass(HINSTANCE hInstance)
{
WNDCLASSEX wcex;
wcex.cbSize = sizeof(WNDCLASSEX);
wcex.style = CS_HREDRAW | CS_VREDRAW;
wcex.lpfnWndProc = WndProc;
wcex.cbClsExtra = 0;
wcex.cbWndExtra = 0;
wcex.hInstance = hInstance;
wcex.hIcon = NULL;
wcex.hCursor = NULL;
wcex.hbrBackground = GetStockBrush(WHITE_BRUSH);
wcex.lpszMenuName = NULL;
wcex.lpszClassName = ClassName;
wcex.hIconSm = LoadIcon(hInstance, (LPWSTR)IDI_ICON1);
return RegisterClassEx(&wcex);
}
BOOL InitInstance(HINSTANCE hInstance, DWORD nAdmin, RECT* rc)
{
hIns = hInstance;
LPCWSTR name = (nAdmin) ? ClassNameAdmin : ClassName;
hMainWnd = CreateWindow(ClassName, name, WS_OVERLAPPEDWINDOW | WS_CLIPCHILDREN,
rc->left, rc->top, rc->right - rc->left, rc->bottom - rc->top, NULL, NULL, hInstance, 0);
if (!hMainWnd)
return FALSE;
ShowWindow(hMainWnd, SW_SHOWNORMAL);
UpdateWindow(hMainWnd);
return TRUE;
}
DWORD SaveProcessProfile(DWORD pid); // ProfileManager.cpp
BOOL CALLBACK OptionDlgProc(HWND hDlg, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
switch (uMsg)
{
case WM_INITDIALOG:
{
SetWindowText(GetDlgItem(hDlg, IDC_EDIT1), std::to_wstring((long long)split_time).c_str());
SetWindowText(GetDlgItem(hDlg, IDC_EDIT2), std::to_wstring((long long)process_time).c_str());
SetWindowText(GetDlgItem(hDlg, IDC_EDIT3), std::to_wstring((long long)inject_delay).c_str());
SetWindowText(GetDlgItem(hDlg, IDC_EDIT4), std::to_wstring((long long)insert_delay).c_str());
CheckDlgButton(hDlg, IDC_CHECK1, auto_inject);
CheckDlgButton(hDlg, IDC_CHECK2, auto_insert);
CheckDlgButton(hDlg, IDC_CHECK3, clipboard_flag);
CheckDlgButton(hDlg, IDC_CHECK4, cyclic_remove);
CheckDlgButton(hDlg, IDC_CHECK5, global_filter);
}
return TRUE;
case WM_COMMAND:
{
DWORD wmId = LOWORD(wParam);
DWORD wmEvent = HIWORD(wParam);
switch (wmId)
{
case IDOK:
{
WCHAR str[128];
GetWindowText(GetDlgItem(hDlg, IDC_EDIT1), str, 0x80);
DWORD st = std::stoul(str);
split_time = st > 100 ? st : 100;
GetWindowText(GetDlgItem(hDlg, IDC_EDIT2), str, 0x80);
DWORD pt = std::stoul(str);
process_time = pt > 50 ? pt : 50;
GetWindowText(GetDlgItem(hDlg, IDC_EDIT3), str, 0x80);
DWORD jd = std::stoul(str);
inject_delay = jd > 1000 ? jd : 1000;
GetWindowText(GetDlgItem(hDlg, IDC_EDIT4), str, 0x80);
DWORD sd = std::stoul(str);
insert_delay = sd > 200 ? sd : 200;
if (IsDlgButtonChecked(hDlg, IDC_CHECK6))
{
man->ResetRepeatStatus();
}
auto_inject = IsDlgButtonChecked(hDlg, IDC_CHECK1);
auto_insert = IsDlgButtonChecked(hDlg, IDC_CHECK2);
clipboard_flag = IsDlgButtonChecked(hDlg, IDC_CHECK3);
cyclic_remove = IsDlgButtonChecked(hDlg, IDC_CHECK4);
global_filter = IsDlgButtonChecked(hDlg, IDC_CHECK5);
setman->clipboardFlag = clipboard_flag;
setman->splittingInterval = split_time;
if (auto_inject == 0) auto_insert = 0;
}
case IDCANCEL:
EndDialog(hDlg, 0);
hOptionDlg = NULL;
break;
}
return TRUE;
}
default:
return FALSE;
}
return FALSE;
}
BOOL CALLBACK ProcessDlgProc(HWND hDlg, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
switch (uMsg)
{
case WM_INITDIALOG:
{
pswnd = new ProcessWindow(hDlg);
return TRUE;
}
case WM_COMMAND:
{
DWORD wmId, wmEvent;
wmId = LOWORD(wParam);
wmEvent = HIWORD(wParam);
switch (wmId)
{
case WM_DESTROY:
case IDOK:
EndDialog(hDlg, NULL);
hProcDlg = NULL;
delete pswnd;
pswnd = NULL;
break;
case IDC_BUTTON1:
pswnd->RefreshProcess();
break;
case IDC_BUTTON2:
pswnd->AttachProcess();
break;
case IDC_BUTTON3:
pswnd->DetachProcess();
break;
case IDC_BUTTON5:
pswnd->CreateProfileForSelectedProcess();
break;
case IDC_BUTTON6:
pswnd->DeleteProfileForSelectedProcess();
break;
}
}
return TRUE;
case WM_NOTIFY:
{
LPNMHDR dr = (LPNMHDR)lParam;
switch (dr->code)
{
case LVN_ITEMCHANGED:
if (dr->idFrom == IDC_LIST1)
{
NMLISTVIEW *nmlv = (LPNMLISTVIEW)lParam;
if (nmlv->uNewState & LVIS_SELECTED)
pswnd->RefreshThread(nmlv->iItem);
}
break;
}
}
return TRUE;
default:
return FALSE;
}
}
LRESULT CALLBACK EditProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
switch (message)
{
case WM_CHAR: //Filter user input.
if (GetKeyState(VK_CONTROL) & 0x8000)
{
if (wParam == 1)
{
Edit_SetSel(hwndEdit, 0, -1);
SendMessage(hwndEdit, WM_COPY, 0, 0);
}
}
return 0;
case WM_LBUTTONUP:
if (hwndEdit)
SendMessage(hwndEdit, WM_COPY, 0, 0);
default:
{
return proc(hWnd, message, wParam, lParam);
}
}
}
LRESULT CALLBACK EditCmdProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
switch (message)
{
case WM_KEYDOWN:
if (wParam == VK_UP)
{
SetWindowText(hWnd, last_cmd);
SetFocus(hWnd);
return 0;
}
break;
case WM_CHAR:
if (wParam == VK_RETURN)
{
DWORD s = 0, pid = 0;
WCHAR str[32];
if (GetWindowTextLength(hWnd) == 0)
break;
GetWindowText(hWnd, last_cmd, CMD_SIZE);
//IthBreak();
if (GetWindowText(hwndProcessComboBox, str, 32))
pid = std::stoul(str);
ProcessCommand(last_cmd, pid);
Edit_SetSel(hWnd, 0, -1);
Edit_ReplaceSel(hWnd, &s);
SetFocus(hWnd);
return 0;
}
default:
break;
}
return CallWindowProc(proccmd, hWnd, message, wParam, lParam);
}
void CreateButtons(HWND hWnd)
{
hwndProcess = CreateWindow(L"Button", L"Process", WS_CHILD | WS_VISIBLE,
0, 0, 0, 0, hWnd, 0, hIns, NULL);
hwndOption = CreateWindow(L"Button", L"Option", WS_CHILD | WS_VISIBLE,
0, 0, 0, 0, hWnd, 0, hIns, NULL);
hwndClear = CreateWindow(L"Button", L"Clear", WS_CHILD | WS_VISIBLE,
0, 0, 0, 0, hWnd, 0, hIns, NULL);
hwndSave = CreateWindow(L"Button", L"Save", WS_CHILD | WS_VISIBLE,
0, 0, 0, 0, hWnd, 0, hIns, NULL);
hwndRemoveLink = CreateWindow(L"Button", L"Unlink", WS_CHILD | WS_VISIBLE,
0, 0, 0, 0, hWnd, 0, hIns, NULL);
hwndRemoveHook = CreateWindow(L"Button", L"Unhook", WS_CHILD | WS_VISIBLE,
0, 0, 0, 0, hWnd, 0, hIns, NULL);
hwndTop = CreateWindow(L"Button", L"Top", WS_CHILD | WS_VISIBLE | BS_PUSHLIKE | BS_CHECKBOX,
0, 0, 0, 0, hWnd, 0, hIns, NULL);
hwndProcessComboBox = CreateWindow(L"ComboBox", NULL,
WS_CHILD | WS_VISIBLE | CBS_DROPDOWNLIST |
CBS_SORT | WS_VSCROLL | WS_TABSTOP,
0, 0, 0, 0, hWnd, 0, hIns, NULL);
hwndCmd = CreateWindowEx(WS_EX_CLIENTEDGE, L"Edit", NULL,
WS_CHILD | WS_VISIBLE | ES_NOHIDESEL | ES_LEFT | ES_AUTOHSCROLL,
0, 0, 0, 0, hWnd, 0, hIns, NULL);
hwndEdit = CreateWindowEx(WS_EX_CLIENTEDGE, L"Edit", NULL,
WS_CHILD | WS_VISIBLE | ES_NOHIDESEL | WS_VSCROLL |
ES_LEFT | ES_MULTILINE | ES_AUTOVSCROLL,
0, 0, 0, 0, hWnd, 0, hIns, NULL);
}
void ClickButton(HWND hWnd, HWND h)
{
if (h == hwndProcess)
{
if (hProcDlg)
SetForegroundWindow(hProcDlg);
else
hProcDlg = CreateDialog(hIns, (LPWSTR)IDD_DIALOG2, 0, ProcessDlgProc);
}
else if (h == hwndOption)
{
if (hOptionDlg)
SetForegroundWindow(hOptionDlg);
else
hOptionDlg = CreateDialog(hIns, (LPWSTR)IDD_DIALOG4, 0, OptionDlgProc);
}
else if (h == hwndClear)
{
WCHAR pwcEntry[128] = {};
DWORD dwId = ComboBox_GetCurSel(hwndCombo);
int len = ComboBox_GetLBText(hwndCombo, dwId, pwcEntry);
dwId = std::stoul(pwcEntry, NULL, 16);
if (dwId == 0)
man->ClearCurrent();
else
man->RemoveSingleThread(dwId);
}
else if (h == hwndTop)
{
if (Button_GetCheck(h) == BST_CHECKED)
{
Button_SetCheck(h, BST_UNCHECKED);
SetWindowPos(hWnd, HWND_NOTOPMOST, 0, 0, 0, 0, SWP_NOSIZE | SWP_NOMOVE);
if (hProcDlg)
SetWindowPos(hProcDlg, HWND_NOTOPMOST, 0, 0, 0, 0, SWP_NOSIZE | SWP_NOMOVE);
if (hOptionDlg)
SetWindowPos(hOptionDlg, HWND_NOTOPMOST, 0, 0, 0, 0, SWP_NOSIZE | SWP_NOMOVE);
}
else
{
Button_SetCheck(h, BST_CHECKED);
SetWindowPos(hWnd, HWND_TOPMOST, 0, 0, 0, 0, SWP_NOSIZE | SWP_NOMOVE);
if (hProcDlg)
SetWindowPos(hProcDlg, HWND_TOPMOST, 0, 0, 0, 0, SWP_NOSIZE | SWP_NOMOVE);
if (hOptionDlg)
SetWindowPos(hOptionDlg, HWND_TOPMOST, 0, 0, 0, 0, SWP_NOSIZE | SWP_NOMOVE);
}
}
else if (h == hwndSave)
{
WCHAR str[32];
if (GetWindowText(hwndProcessComboBox, str, 32))
{
DWORD pid = std::stoul(str);
SaveProcessProfile(pid);
}
pfman->SaveProfiles();
}
else if (h == hwndRemoveLink)
{
WCHAR str[32];
if (GetWindowText(hwndCombo, str, 32))
{
DWORD from = std::stoul(str, NULL, 16);
if (from != 0)
Host_UnLink(from);
}
}
else if (h == hwndRemoveHook)
{
WCHAR str[32];
if (GetWindowText(hwndCombo, str, 32))
{
std::wstring entry(str);
std::size_t i;
DWORD threadNumber = std::stoul(entry, &i, 16);
entry = entry.substr(i + 1);
DWORD pid = std::stoul(entry, &i);
entry = entry.substr(i + 1);
DWORD addr = std::stoul(entry, NULL, 16);
if (threadNumber != 0)
Host_RemoveHook(pid, addr);
}
}
}
DWORD ThreadFilter(TextThread* thread, BYTE* out, DWORD len, DWORD new_line, PVOID data, bool space)
{
DWORD status = thread->Status();
if (global_filter && !new_line && thread->Number() != 0)
{
if (status & USING_UNICODE)
{
DWORD i, j;
len /= 2;
LPWSTR str = (LPWSTR)out;
for (i = 0, j = 0; i < len; i++)
{
WCHAR c = str[i];
if (!uni_filter->Find(c))
str[j++] = c;
}
memset(str + j, 0, (len - j) * 2);
len = j * 2;
}
else
{
DWORD i, j;
for (i = 0, j = 0; i < len; i++)
{
WORD c = out[i];
if (!IsDBCSLeadByte(c & 0xFF))
{
if (!mb_filter->Find(c))
out[j++] = c & 0xFF;
}
else if (i + 1 < len)
{
c = out[i + 1];
c <<= 8;
c |= out[i];
if (!mb_filter->Find(c))
{
out[j++] = c & 0xFF;
out[j++] = c >> 8;
}
i++;
}
}
memset(out + j, 0, len - j);
len = j;
}
}
return len;
}
DWORD ThreadOutput(TextThread* thread, BYTE* out, DWORD len, DWORD new_line, PVOID data, bool space)
{
if (len == 0)
return len;
DWORD status = thread->Status();
if (status & CURRENT_SELECT)
{
if (new_line)
{
if (thread->Number() == 0)
texts->AddText(L"\r\n", 2, true);
else
texts->AddText(L"\r\n\r\n", 4, true);
}
else if (status & USING_UNICODE)
{
texts->AddText((LPWSTR)out, len / 2, false);
}
else
{
int uni_len = MB_WC_count((char*)out, len);
LPWSTR str = new WCHAR[uni_len + 1];
MB_WC((char*)out, str, uni_len + 1);
str[uni_len] = L'\0';
texts->AddText(str, uni_len, false);
delete str;
}
}
return len;
}
bool GetHookParam(DWORD pid, DWORD hook_addr, HookParam& hp)
{
if (!pid)
return false;
ProcessRecord *pr = ::man->GetProcessRecord(pid);
if (!pr)
return false;
bool result = false;
WaitForSingleObject(pr->hookman_mutex, 0);
const Hook *hks = (Hook *)pr->hookman_map;
for (int i = 0; i < MAX_HOOK; i++)
{
if (hks[i].Address() == hook_addr)
{
hp = hks[i].hp;
result = true;
break;
}
}
ReleaseMutex(pr->hookman_mutex);
return result;
}
std::wstring GetEntryString(TextThread& thread)
{
CHAR entry[512];
thread.GetEntryString(entry, 512);
return toUnicodeString(entry);
}
std::wstring CreateEntryWithLink(TextThread& thread, std::wstring& entry)
{
std::wstring entryWithLink = entry;
if (thread.Link())
entryWithLink += L"->" + ToHexString(thread.LinkNumber());
if (thread.PID() == 0)
entryWithLink += L"ConsoleOutput";
HookParam hp = {};
if (GetHookParam(thread.PID(), thread.Addr(), hp))
entryWithLink += L" (" + GetCode(hp, thread.PID()) + L")";
return entryWithLink;
}
void AddToCombo(TextThread& thread, bool replace)
{
std::wstring entry = GetEntryString(thread);
std::wstring entryWithLink = CreateEntryWithLink(thread, entry);
int i = ComboBox_FindString(hwndCombo, -1, entry.c_str());
if (replace)
{
int sel = ComboBox_GetCurSel(hwndCombo);
if (i != CB_ERR)
ComboBox_DeleteString(hwndCombo, i);
ComboBox_AddString(hwndCombo, entryWithLink.c_str());
ComboBox_SetCurSel(hwndCombo, sel);
}
else
{
if (i == CB_ERR)
ComboBox_AddString(hwndCombo, entryWithLink.c_str());
// Why set current selection to 0 when the new thread is selected?
if (thread.Status() & CURRENT_SELECT)
ComboBox_SetCurSel(hwndCombo, 0);
}
}
void RemoveFromCombo(TextThread* thread)
{
CHAR entry[512];
thread->GetEntryString(entry, 512);
std::wstring unicodeEntry = toUnicodeString(entry);
if (thread->PID() == 0)
unicodeEntry += L"ConsoleOutput";
int i = ComboBox_FindString(hwndCombo, 0, unicodeEntry.c_str());
if (i != CB_ERR)
{
if (ComboBox_DeleteString(hwndCombo, i) == CB_ERR)
ConsoleOutput(ErrorDeleteCombo);
}
}
void ComboSelectCurrent(TextThread* thread)
{
ComboBox_SetCurSel(hwndCombo, thread->Number());
}
DWORD SetEditText(LPWSTR wc)
{
DWORD line;
Edit_SetText(hwndEdit, wc);
line = Edit_GetLineCount(hwndEdit);
SendMessage(hwndEdit, EM_LINESCROLL, 0, line);
return 0;
}
DWORD ThreadReset(TextThread* thread)
{
texts->ClearBuffer();
man->SetCurrent(thread);
thread->LockVector();
DWORD uni = thread->Status() & USING_UNICODE;
if (uni)
{
DWORD len = 0;
LPWSTR wc = (LPWSTR)thread->GetStore(&len);
len /= 2;
wc[len] = L'\0';
SetEditText(wc);
}
else
{
DWORD len = MB_WC_count((char*)thread->Storage(), thread->Used());
LPWSTR wc = new WCHAR[len + 1];
MB_WC((char*)thread->Storage(), wc, len + 1);
wc[len] = L'\0';
SetEditText(wc);
delete wc;
}
WCHAR buffer[16];
std::swprintf(buffer, L"%04X", thread->Number());
DWORD tmp = ComboBox_FindString(hwndCombo, 0, buffer);
if (tmp != CB_ERR)
ComboBox_SetCurSel(hwndCombo, tmp);
thread->UnlockVector();
return 0;
}
DWORD AddRemoveLink(TextThread* thread)
{
AddToCombo(*thread, true);
return 0;
}
bool IsUnicodeHook(const ProcessRecord& pr, DWORD hook);
void AddLinksToHookManager(const Profile* pf, size_t thread_index, const TextThread* thread);
DWORD ThreadCreate(TextThread* thread)
{
thread->RegisterOutputCallBack(ThreadOutput, 0);
thread->RegisterFilterCallBack(ThreadFilter, 0);
AddToCombo(*thread, false);
const auto& tp = thread->GetThreadParameter();
auto pr = man->GetProcessRecord(tp->pid);
if (pr == NULL)
return 0;
if (IsUnicodeHook(*pr, tp->hook))
thread->Status() |= USING_UNICODE;
auto pf = pfman->GetProfile(tp->pid);
if (!pf)
return 0;
const std::wstring& hook_name = GetHookNameByAddress(*pr, thread->GetThreadParameter()->hook);
auto thread_profile = pf->FindThread(thread->GetThreadParameter(), hook_name);
if (thread_profile != pf->Threads().end())
{
(*thread_profile)->HookManagerIndex() = thread->Number();
auto thread_index = thread_profile - pf->Threads().begin();
AddLinksToHookManager(pf, thread_index, thread);
if (pf->IsThreadSelected(thread_profile))
ThreadReset(thread);
}
return 0;
}
bool IsUnicodeHook(const ProcessRecord& pr, DWORD hook)
{
bool res = false;
WaitForSingleObject(pr.hookman_mutex, 0);
auto hooks = (const Hook*)pr.hookman_map;
for (DWORD i = 0; i < MAX_HOOK; i++)
{
if (hooks[i].Address() == hook)
{
res = hooks[i].Type() & USING_UNICODE;
break;
}
}
ReleaseMutex(pr.hookman_mutex);
return res;
}
void AddLinksToHookManager(const Profile* pf, size_t thread_index, const TextThread* thread)
{
for (auto lp = pf->Links().begin(); lp != pf->Links().end(); ++lp)
{
if ((*lp)->FromIndex() == thread_index)
{
WORD to_index = pf->Threads()[(*lp)->ToIndex()]->HookManagerIndex();
if (to_index != 0)
man->AddLink(thread->Number(), to_index);
}
if ((*lp)->ToIndex() == thread_index)
{
WORD from_index = pf->Threads()[(*lp)->FromIndex()]->HookManagerIndex();
if (from_index != 0)
man->AddLink(from_index, thread->Number());
}
}
}
DWORD ThreadRemove(TextThread* thread)
{
RemoveFromCombo(thread);
return 0;
}
DWORD RegisterProcessList(DWORD pid)
{
auto path = GetProcessPath(pid);
if (!path.empty())
{
WCHAR str[MAX_PATH];
std::swprintf(str, L"%04d:%s", pid, path.substr(path.rfind(L'\\') + 1).c_str());
ComboBox_AddString(hwndProcessComboBox, str);
if (ComboBox_GetCount(hwndProcessComboBox) == 1)
ComboBox_SetCurSel(hwndProcessComboBox, 0);
}
return 0;
}
DWORD RemoveProcessList(DWORD pid)
{
WCHAR str[MAX_PATH];
std::swprintf(str, L"%04d", pid);
DWORD i = ComboBox_FindString(hwndProcessComboBox, 0, str);
DWORD j = ComboBox_GetCurSel(hwndProcessComboBox);
if (i != CB_ERR)
{
DWORD k = ComboBox_DeleteString(hwndProcessComboBox, i);
if (i == j)
ComboBox_SetCurSel(hwndProcessComboBox, 0);
}
return 0;
}
DWORD RefreshProfileOnNewHook(DWORD pid)
{
return 0;
}
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
switch (message)
{
case WM_CREATE:
CreateButtons(hWnd);
// Add text to the window.
Edit_LimitText(hwndEdit, -1);
SendMessage(hwndEdit, WM_INPUTLANGCHANGEREQUEST, 0, 0x411);
proc = (WNDPROC)SetWindowLong(hwndEdit, GWL_WNDPROC, (LONG)EditProc);
proccmd = (WNDPROC)SetWindowLong(hwndCmd, GWL_WNDPROC, (LONG)EditCmdProc);
hwndCombo = CreateWindow(L"ComboBox", NULL,
WS_CHILD | WS_VISIBLE | CBS_DROPDOWNLIST |
CBS_SORT | WS_VSCROLL | WS_TABSTOP,
0, 0, 0, 0, hWnd, 0, hIns, NULL);
{
HDC hDC = GetDC(hWnd);
int nHeight = -MulDiv(12, GetDeviceCaps(hDC, LOGPIXELSY), 72);
ReleaseDC(hWnd, hDC);
HFONT hf = CreateFont(nHeight, 0, 0, 0, FW_LIGHT, FALSE, FALSE, FALSE, SHIFTJIS_CHARSET,
OUT_DEFAULT_PRECIS, CLIP_DEFAULT_PRECIS, CLEARTYPE_QUALITY, DEFAULT_PITCH | FF_DONTCARE,
L"MS Gothic");
hWhiteBrush = GetStockBrush(WHITE_BRUSH);
SendMessage(hwndCmd, WM_SETFONT, (WPARAM)hf, 0);
SendMessage(hwndEdit, WM_SETFONT, (WPARAM)hf, 0);
SendMessage(hwndCombo, WM_SETFONT, (WPARAM)hf, 0);
SendMessage(hwndProcessComboBox, WM_SETFONT, (WPARAM)hf, 0);
texts = new TextBuffer(hwndEdit);
man->RegisterThreadCreateCallback(ThreadCreate);
man->RegisterThreadRemoveCallback(ThreadRemove);
man->RegisterThreadResetCallback(ThreadReset);
TextThread* console = man->FindSingle(0);
console->RegisterOutputCallBack(ThreadOutput, NULL);
AddToCombo(*console, false);
man->RegisterProcessAttachCallback(RegisterProcessList);
man->RegisterProcessDetachCallback(RemoveProcessList);
man->RegisterProcessNewHookCallback(RefreshProfileOnNewHook);
man->RegisterAddRemoveLinkCallback(AddRemoveLink);
man->RegisterConsoleCallback(ConsoleOutput);
Host_Start();
{
static const WCHAR program_name[] = L"Interactive Text Hooker";
//static const WCHAR program_version[] = L"3.0";
static WCHAR version_info[256];
std::swprintf(version_info, L"%s %s (%s)", program_name, program_version, build_date);
man->AddConsoleOutput(version_info);
man->AddConsoleOutput(InitMessage);
}
if (background == 0)
man->AddConsoleOutput(BackgroundMsg);
}
return 0;
case WM_COMMAND:
{
DWORD wmId, wmEvent, dwId;
wmId = LOWORD(wParam);
wmEvent = HIWORD(wParam);
switch (wmEvent)
{
case EN_VSCROLL:
{
SCROLLBARINFO info = { sizeof(info) };
GetScrollBarInfo(hwndEdit, OBJID_VSCROLL, &info);
InvalidateRect(hwndEdit, 0, 1);
ValidateRect(hwndEdit, &info.rcScrollBar);
RedrawWindow(hwndEdit, 0, 0, RDW_ERASE);
}
break;
case CBN_SELENDOK:
{
if ((HWND)lParam == hwndProcessComboBox)
return 0;
dwId = ComboBox_GetCurSel(hwndCombo);
int len = ComboBox_GetLBTextLen(hwndCombo, dwId);
if (len > 0)
{
LPWSTR pwcEntry = new WCHAR[len + 1];
len = ComboBox_GetLBText(hwndCombo, dwId, pwcEntry);
DWORD num = std::stoul(pwcEntry, NULL, 16);
man->SelectCurrent(num);
delete[] pwcEntry;
}
}
return 0;
case BN_CLICKED:
ClickButton(hWnd, (HWND)lParam);
break;
default:
break;
}
}
break;
case WM_SETFOCUS:
SetFocus(hwndEdit);
return 0;
case WM_SIZE:
{
WORD width = LOWORD(lParam);
WORD height = HIWORD(lParam);
DWORD l = width / 7;
WORD h = HIWORD(GetDialogBaseUnits()); // height of the system font
h = h + (h / 2);
HDC hDC = GetDC(hWnd);
RECT rc;
GetClientRect(hWnd, &rc);
FillRect(hDC, &rc, hWhiteBrush);
ReleaseDC(hWnd, hDC);
MoveWindow(hwndProcess, 0, 0, l, h, TRUE);
MoveWindow(hwndOption, l * 1, 0, l, h, TRUE);
MoveWindow(hwndTop, l * 2, 0, l, h, TRUE);
MoveWindow(hwndClear, l * 3, 0, l, h, TRUE);
MoveWindow(hwndRemoveLink, l * 4, 0, l, h, TRUE);
MoveWindow(hwndRemoveHook, l * 5, 0, l, h, TRUE);
MoveWindow(hwndSave, l * 6, 0, width - 6 * l, h, TRUE);
l *= 2;
MoveWindow(hwndProcessComboBox, 0, h, l, 200, TRUE);
MoveWindow(hwndCmd, l, h, width - l, h, TRUE);
MoveWindow(hwndCombo, 0, h * 2, width, 200, TRUE);
h *= 3;
MoveWindow(hwndEdit, 0, h, width, height - h, TRUE);
}
return 0;
case WM_DESTROY:
man->RegisterThreadCreateCallback(0);
man->RegisterThreadRemoveCallback(0);
man->RegisterThreadResetCallback(0);
man->RegisterProcessAttachCallback(0);
man->RegisterProcessDetachCallback(0);
//delete texts;
SaveSettings();
PostQuitMessage(0);
return 0;
default:
return DefWindowProc(hWnd, message, wParam, lParam);
}
return 0;
}
DWORD WINAPI FlushThread(LPVOID lParam)
{
TextBuffer* t = (TextBuffer*)lParam;
while (t->Running())
{
t->Flush();
Sleep(10);
}
return 0;
}

View File

@ -1,20 +0,0 @@
/* Copyright (C) 2010-2012 kaosu (qiupf2000@gmail.com)
* This file is part of the Interactive Text Hooker.
* Interactive Text Hooker 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 <http://www.gnu.org/licenses/>.
*/
#pragma once
#include "ITH.h"

4
host/CLI/CMakeLists.txt Normal file
View File

@ -0,0 +1,4 @@
# The CLI isn't used by Textractor itself, but is here for other people that want to build projects on top of Textractor
add_executable(TextractorCLI main.cpp)
target_precompile_headers(TextractorCLI REUSE_FROM pch)
target_link_libraries(TextractorCLI host)

40
host/CLI/main.cpp Normal file
View File

@ -0,0 +1,40 @@
#include "../host.h"
#include "../hookcode.h"
#include <io.h>
#include <fcntl.h>
#include <iostream>
int main()
{
_setmode(_fileno(stdout), _O_U16TEXT);
_setmode(_fileno(stdin), _O_U16TEXT);
wprintf_s(L"Usage: {'attach'|'detach'|hookcode} -Pprocessid\n");
fflush(stdout);
Host::Start([](auto) {}, [](auto) {}, [](auto&) {}, [](auto&) {}, [](TextThread& thread, std::wstring& output)
{
wprintf_s(L"[%I64X:%I32X:%I64X:%I64X:%I64X:%s:%s] %s\n",
thread.handle,
thread.tp.processId,
thread.tp.addr,
thread.tp.ctx,
thread.tp.ctx2,
thread.name.c_str(),
HookCode::Generate(thread.hp, thread.tp.processId).c_str(),
output.c_str()
);
fflush(stdout);
return false;
});
wchar_t input[500] = {};
while (fgetws(input, 500, stdin))
{
wchar_t command[500] = {};
DWORD processId = 0;
if (swscanf(input, L"%500s -P%d", command, &processId) != 2) ExitProcess(0);
if (_wcsicmp(command, L"attach") == 0) Host::InjectProcess(processId);
else if (_wcsicmp(command, L"detach") == 0) Host::DetachProcess(processId);
else if (auto hp = HookCode::Parse(command)) Host::InsertHook(processId, hp.value());
else ExitProcess(0);
}
ExitProcess(0);
}

8
host/CMakeLists.txt Normal file
View File

@ -0,0 +1,8 @@
add_library(host
host.cpp
textthread.cpp
hookcode.cpp
)
target_precompile_headers(host REUSE_FROM pch)
add_subdirectory(CLI)

304
host/hookcode.cpp Normal file
View File

@ -0,0 +1,304 @@
#include "hookcode.h"
#include "module.h"
namespace
{
std::optional<HookParam> ParseRCode(std::wstring RCode)
{
std::wsmatch match;
HookParam hp = {};
hp.type |= DIRECT_READ;
// {S|Q|V|M}
switch (RCode[0])
{
case L'S':
break;
case L'Q':
hp.type |= USING_UNICODE;
break;
case L'V':
hp.type |= USING_UTF8;
break;
case L'M':
hp.type |= USING_UNICODE | HEX_DUMP;
break;
default:
return {};
}
RCode.erase(0, 1);
// [null_length<]
if (std::regex_search(RCode, match, std::wregex(L"^([0-9]+)<")))
{
hp.null_length = std::stoi(match[1]);
RCode.erase(0, match[0].length());
}
// [codepage#]
if (std::regex_search(RCode, match, std::wregex(L"^([0-9]+)#")))
{
hp.codepage = std::stoi(match[1]);
RCode.erase(0, match[0].length());
}
// @addr
if (!std::regex_match(RCode, match, std::wregex(L"@([[:xdigit:]]+)"))) return {};
hp.address = std::stoull(match[1], nullptr, 16);
return hp;
}
std::optional<HookParam> ParseHCode(std::wstring HCode)
{
std::wsmatch match;
HookParam hp = {};
// {A|B|W|H|S|Q|V|M}
switch (HCode[0])
{
case L'A':
hp.type |= BIG_ENDIAN;
hp.length_offset = 1;
break;
case L'B':
hp.length_offset = 1;
break;
case L'W':
hp.type |= USING_UNICODE;
hp.length_offset = 1;
break;
case L'H':
hp.type |= USING_UNICODE | HEX_DUMP;
hp.length_offset = 1;
break;
case L'S':
hp.type |= USING_STRING;
break;
case L'Q':
hp.type |= USING_STRING | USING_UNICODE;
break;
case L'V':
hp.type |= USING_STRING | USING_UTF8;
break;
case L'M':
hp.type |= USING_STRING | USING_UNICODE | HEX_DUMP;
break;
default:
return {};
}
HCode.erase(0, 1);
if (hp.type & USING_STRING)
{
if (HCode[0] == L'F')
{
hp.type |= FULL_STRING;
HCode.erase(0, 1);
}
// [null_length<]
if (std::regex_search(HCode, match, std::wregex(L"^([0-9]+)<")))
{
hp.null_length = std::stoi(match[1]);
HCode.erase(0, match[0].length());
}
}
// [N]
if (HCode[0] == L'N')
{
hp.type |= NO_CONTEXT;
HCode.erase(0, 1);
}
// [codepage#]
if (std::regex_search(HCode, match, std::wregex(L"^([0-9]+)#")))
{
hp.codepage = std::stoi(match[1]);
HCode.erase(0, match[0].length());
}
// [padding+]
if (std::regex_search(HCode, match, std::wregex(L"^([[:xdigit:]]+)\\+")))
{
hp.padding = std::stoull(match[1], nullptr, 16);
HCode.erase(0, match[0].length());
}
auto ConsumeHexInt = [&HCode]
{
size_t size = 0;
int value = 0;
try { value = std::stoi(HCode, &size, 16); } catch (std::invalid_argument) {}
HCode.erase(0, size);
return value;
};
// data_offset
hp.offset = ConsumeHexInt();
// [*deref_offset1]
if (HCode[0] == L'*')
{
hp.type |= DATA_INDIRECT;
HCode.erase(0, 1);
hp.index = ConsumeHexInt();
}
// [:split_offset[*deref_offset2]]
if (HCode[0] == L':')
{
hp.type |= USING_SPLIT;
HCode.erase(0, 1);
hp.split = ConsumeHexInt();
if (HCode[0] == L'*')
{
hp.type |= SPLIT_INDIRECT;
HCode.erase(0, 1);
hp.split_index = ConsumeHexInt();
}
}
// @addr[:module[:func]]
if (!std::regex_match(HCode, match, std::wregex(L"^@([[:xdigit:]]+)(:.+?)?(:.+)?"))) return {};
hp.address = std::stoull(match[1], nullptr, 16);
if (match[2].matched)
{
hp.type |= MODULE_OFFSET;
wcsncpy_s(hp.module, match[2].str().erase(0, 1).c_str(), MAX_MODULE_SIZE - 1);
}
if (match[3].matched)
{
hp.type |= FUNCTION_OFFSET;
std::wstring func = match[3];
strncpy_s(hp.function, std::string(func.begin(), func.end()).erase(0, 1).c_str(), MAX_MODULE_SIZE - 1);
}
// ITH has registers offset by 4 vs AGTH: need this to correct
if (hp.offset < 0) hp.offset -= 4;
if (hp.split < 0) hp.split -= 4;
return hp;
}
std::wstring HexString(int64_t num)
{
if (num < 0) return FormatString(L"-%I64X", -num);
return FormatString(L"%I64X", num);
}
std::wstring GenerateRCode(HookParam hp)
{
std::wstring RCode = L"R";
if (hp.type & USING_UNICODE)
{
if (hp.type & HEX_DUMP) RCode += L'M';
else RCode += L'Q';
if (hp.null_length != 0) RCode += std::to_wstring(hp.null_length) + L'<';
}
else
{
RCode += L'S';
if (hp.null_length != 0) RCode += std::to_wstring(hp.null_length) + L'<';
if (hp.codepage != 0) RCode += std::to_wstring(hp.codepage) + L'#';
}
RCode += L'@' + HexString(hp.address);
return RCode;
}
std::wstring GenerateHCode(HookParam hp, DWORD processId)
{
std::wstring HCode = L"H";
if (hp.type & USING_UNICODE)
{
if (hp.type & HEX_DUMP)
{
if (hp.type & USING_STRING) HCode += L'M';
else HCode += L'H';
}
else
{
if (hp.type & USING_STRING) HCode += L'Q';
else HCode += L'W';
}
}
else
{
if (hp.type & USING_STRING) HCode += L'S';
else if (hp.type & BIG_ENDIAN) HCode += L'A';
else HCode += L'B';
}
if (hp.type & FULL_STRING) HCode += L'F';
if (hp.null_length != 0) HCode += std::to_wstring(hp.null_length) + L'<';
if (hp.type & NO_CONTEXT) HCode += L'N';
if (hp.text_fun || hp.filter_fun || hp.hook_fun || hp.length_fun) HCode += L'X'; // no AGTH equivalent
if (hp.codepage != 0 && !(hp.type & USING_UNICODE)) HCode += std::to_wstring(hp.codepage) + L'#';
if (hp.padding) HCode += HexString(hp.padding) + L'+';
if (hp.offset < 0) hp.offset += 4;
if (hp.split < 0) hp.split += 4;
HCode += HexString(hp.offset);
if (hp.type & DATA_INDIRECT) HCode += L'*' + HexString(hp.index);
if (hp.type & USING_SPLIT) HCode += L':' + HexString(hp.split);
if (hp.type & SPLIT_INDIRECT) HCode += L'*' + HexString(hp.split_index);
// Attempt to make the address relative
if (processId && !(hp.type & MODULE_OFFSET))
if (AutoHandle<> process = OpenProcess(PROCESS_VM_READ | PROCESS_QUERY_INFORMATION, FALSE, processId))
if (MEMORY_BASIC_INFORMATION info = {}; VirtualQueryEx(process, (LPCVOID)hp.address, &info, sizeof(info)))
if (auto moduleName = GetModuleFilename(processId, (HMODULE)info.AllocationBase))
{
hp.type |= MODULE_OFFSET;
hp.address -= (uint64_t)info.AllocationBase;
wcsncpy_s(hp.module, moduleName->c_str() + moduleName->rfind(L'\\') + 1, MAX_MODULE_SIZE - 1);
}
HCode += L'@' + HexString(hp.address);
if (hp.type & MODULE_OFFSET) HCode += L':' + std::wstring(hp.module);
if (hp.type & FUNCTION_OFFSET) HCode += L':' + StringToWideString(hp.function);
return HCode;
}
}
namespace HookCode
{
std::optional<HookParam> Parse(std::wstring code)
{
if (code[0] == L'/') code.erase(0, 1);
code.erase(std::find(code.begin(), code.end(), L'/'), code.end()); // legacy/AGTH compatibility
Trim(code);
if (code[0] == L'R') return ParseRCode(code.erase(0, 1));
else if (code[0] == L'H') return ParseHCode(code.erase(0, 1));
return {};
}
std::wstring Generate(HookParam hp, DWORD processId)
{
return hp.type & DIRECT_READ ? GenerateRCode(hp) : GenerateHCode(hp, processId);
}
TEST(
assert(StringToWideString(u8"こんにちは") == L"こんにちは"),
assert(HexString(-12) == L"-C"),
assert(HexString(12) == L"C"),
assert(Parse(L"/HQN936#-c*C:C*1C@4AA:gdi.dll:GetTextOutA")),
assert(Parse(L"/HQN936#-c*C:C*1C@4AA:gdi.dll:GetTextOutA /KF")),
assert(Parse(L"HB4@0")),
assert(Parse(L"/RS65001#@44")),
assert(Parse(L"HQ@4")),
assert(!Parse(L"/RW@44")),
assert(!Parse(L"/HWG@33"))
);
}

9
host/hookcode.h Normal file
View File

@ -0,0 +1,9 @@
#pragma once
#include "types.h"
namespace HookCode
{
std::optional<HookParam> Parse(std::wstring code);
std::wstring Generate(HookParam hp, DWORD processId = 0);
}

259
host/host.cpp Normal file
View File

@ -0,0 +1,259 @@
#include "host.h"
#include "defs.h"
#include "module.h"
#include "hookcode.h"
#include "../texthook/texthook.h"
extern const wchar_t* ALREADY_INJECTED;
extern const wchar_t* NEED_32_BIT;
extern const wchar_t* NEED_64_BIT;
extern const wchar_t* INJECT_FAILED;
extern const wchar_t* CONSOLE;
extern const wchar_t* CLIPBOARD;
namespace
{
class ProcessRecord
{
public:
ProcessRecord(DWORD processId, HANDLE pipe) :
pipe(pipe),
mappedFile(OpenFileMappingW(FILE_MAP_READ, FALSE, (ITH_SECTION_ + std::to_wstring(processId)).c_str())),
view(*(const TextHook(*)[MAX_HOOK])MapViewOfFile(mappedFile, FILE_MAP_READ, 0, 0, HOOK_SECTION_SIZE / 2)), // jichi 1/16/2015: Changed to half to hook section size
viewMutex(ITH_HOOKMAN_MUTEX_ + std::to_wstring(processId))
{}
~ProcessRecord()
{
UnmapViewOfFile(view);
}
TextHook GetHook(uint64_t addr)
{
if (!view) return {};
std::scoped_lock lock(viewMutex);
for (auto hook : view) if (hook.address == addr) return hook;
return {};
}
template <typename T>
void Send(T data)
{
static_assert(sizeof(data) < PIPE_BUFFER_SIZE);
std::thread([=]
{
WriteFile(pipe, &data, sizeof(data), DUMMY, nullptr);
}).detach();
}
Host::HookEventHandler OnHookFound = [](HookParam hp, std::wstring text)
{
Host::AddConsoleOutput(HookCode::Generate(hp) + L": " + text);
};
private:
HANDLE pipe;
AutoHandle<> mappedFile;
const TextHook(&view)[MAX_HOOK];
WinMutex viewMutex;
};
size_t HashThreadParam(ThreadParam tp) { return std::hash<int64_t>()(tp.processId + tp.addr) + std::hash<int64_t>()(tp.ctx + tp.ctx2); }
Synchronized<std::unordered_map<ThreadParam, TextThread, Functor<HashThreadParam>>> textThreadsByParams;
Synchronized<std::unordered_map<DWORD, ProcessRecord>> processRecordsByIds;
Host::ProcessEventHandler OnConnect, OnDisconnect;
Host::ThreadEventHandler OnCreate, OnDestroy;
void RemoveThreads(std::function<bool(ThreadParam)> removeIf)
{
std::vector<TextThread*> threadsToRemove;
for (auto& [tp, thread] : textThreadsByParams.Acquire().contents) if (removeIf(tp)) threadsToRemove.push_back(&thread);
for (auto thread : threadsToRemove)
{
OnDestroy(*thread);
textThreadsByParams->erase(thread->tp);
}
}
void CreatePipe()
{
std::thread([]
{
struct PipeCloser { void operator()(HANDLE h) { DisconnectNamedPipe(h); CloseHandle(h); } };
AutoHandle<PipeCloser>
hookPipe = CreateNamedPipeW(HOOK_PIPE, PIPE_ACCESS_INBOUND, PIPE_TYPE_MESSAGE | PIPE_READMODE_MESSAGE, PIPE_UNLIMITED_INSTANCES, 0, PIPE_BUFFER_SIZE, MAXDWORD, &allAccess),
hostPipe = CreateNamedPipeW(HOST_PIPE, PIPE_ACCESS_OUTBOUND, PIPE_TYPE_MESSAGE | PIPE_READMODE_MESSAGE, PIPE_UNLIMITED_INSTANCES, PIPE_BUFFER_SIZE, 0, MAXDWORD, &allAccess);
static AutoHandle<> pipeAvailableEvent = CreateEventW(&allAccess, FALSE, FALSE, PIPE_AVAILABLE_EVENT);
SetEvent(pipeAvailableEvent);
ConnectNamedPipe(hookPipe, nullptr);
BYTE buffer[PIPE_BUFFER_SIZE] = {};
DWORD bytesRead, processId;
ReadFile(hookPipe, &processId, sizeof(processId), &bytesRead, nullptr);
processRecordsByIds->try_emplace(processId, processId, hostPipe);
OnConnect(processId);
CreatePipe();
while (ReadFile(hookPipe, buffer, PIPE_BUFFER_SIZE, &bytesRead, nullptr))
switch (*(HostNotificationType*)buffer)
{
case HOST_NOTIFICATION_FOUND_HOOK:
{
auto info = *(HookFoundNotif*)buffer;
auto OnHookFound = processRecordsByIds->at(processId).OnHookFound;
std::wstring wide = info.text;
if (wide.size() > STRING) OnHookFound(info.hp, std::move(info.text));
info.hp.type &= ~USING_UNICODE;
if (auto converted = StringToWideString((char*)info.text, info.hp.codepage))
if (converted->size() > STRING) OnHookFound(info.hp, std::move(converted.value()));
if (auto converted = StringToWideString((char*)info.text, info.hp.codepage = CP_UTF8))
if (converted->size() > STRING) OnHookFound(info.hp, std::move(converted.value()));
}
break;
case HOST_NOTIFICATION_RMVHOOK:
{
auto info = *(HookRemovedNotif*)buffer;
RemoveThreads([&](ThreadParam tp) { return tp.processId == processId && tp.addr == info.address; });
}
break;
case HOST_NOTIFICATION_TEXT:
{
auto info = *(ConsoleOutputNotif*)buffer;
Host::AddConsoleOutput(StringToWideString(info.message));
}
break;
default:
{
auto tp = *(ThreadParam*)buffer;
auto textThreadsByParams = ::textThreadsByParams.Acquire();
auto thread = textThreadsByParams->find(tp);
if (thread == textThreadsByParams->end())
{
try { thread = textThreadsByParams->try_emplace(tp, tp, processRecordsByIds->at(tp.processId).GetHook(tp.addr).hp).first; }
catch (std::out_of_range) { continue; } // probably garbage data in pipe, try again
OnCreate(thread->second);
}
thread->second.Push(buffer + sizeof(tp), bytesRead - sizeof(tp));
}
break;
}
RemoveThreads([&](ThreadParam tp) { return tp.processId == processId; });
OnDisconnect(processId);
processRecordsByIds->erase(processId);
}).detach();
}
}
namespace Host
{
void Start(ProcessEventHandler Connect, ProcessEventHandler Disconnect, ThreadEventHandler Create, ThreadEventHandler Destroy, TextThread::OutputCallback Output)
{
OnConnect = Connect;
OnDisconnect = Disconnect;
OnCreate = [Create](TextThread& thread) { Create(thread); thread.Start(); };
OnDestroy = [Destroy](TextThread& thread) { thread.Stop(); Destroy(thread); };
TextThread::Output = Output;
textThreadsByParams->try_emplace(console, console, HookParam{}, CONSOLE);
OnCreate(GetThread(console));
textThreadsByParams->try_emplace(clipboard, clipboard, HookParam{}, CLIPBOARD);
OnCreate(GetThread(clipboard));
CreatePipe();
static AutoHandle<> clipboardUpdate = CreateEventW(nullptr, FALSE, TRUE, NULL);
SetWindowsHookExW(WH_GETMESSAGE, [](int statusCode, WPARAM wParam, LPARAM lParam)
{
if (statusCode == HC_ACTION && wParam == PM_REMOVE && ((MSG*)lParam)->message == WM_CLIPBOARDUPDATE) SetEvent(clipboardUpdate);
return CallNextHookEx(NULL, statusCode, wParam, lParam);
}, NULL, GetCurrentThreadId());
std::thread([]
{
while (WaitForSingleObject(clipboardUpdate, INFINITE) == WAIT_OBJECT_0)
{
std::optional<std::wstring> clipboardText;
for (int retry = 0; !clipboardText && retry < 3; ++retry) // retry loop in case something else is using the clipboard
{
Sleep(10);
if (!IsClipboardFormatAvailable(CF_UNICODETEXT)) continue;
if (!OpenClipboard(NULL)) continue;
if (AutoHandle<Functor<GlobalUnlock>> clipboard = GetClipboardData(CF_UNICODETEXT)) clipboardText = (wchar_t*)GlobalLock(clipboard);
CloseClipboard();
}
if (clipboardText) GetThread(clipboard).AddSentence(std::move(clipboardText.value()));
}
throw;
}).detach();
}
void InjectProcess(DWORD processId)
{
std::thread([processId]
{
if (processId == GetCurrentProcessId()) return;
WinMutex(ITH_HOOKMAN_MUTEX_ + std::to_wstring(processId));
if (GetLastError() == ERROR_ALREADY_EXISTS) return AddConsoleOutput(ALREADY_INJECTED);
if (AutoHandle<> process = OpenProcess(PROCESS_ALL_ACCESS, FALSE, processId))
{
#ifdef _WIN64
BOOL invalidProcess = FALSE;
IsWow64Process(process, &invalidProcess);
if (invalidProcess) return AddConsoleOutput(NEED_32_BIT);
#endif
static std::wstring location = std::filesystem::path(GetModuleFilename().value()).replace_filename(ITH_DLL);
if (LPVOID remoteData = VirtualAllocEx(process, nullptr, (location.size() + 1) * sizeof(wchar_t), MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE))
{
WriteProcessMemory(process, remoteData, location.c_str(), (location.size() + 1) * sizeof(wchar_t), nullptr);
if (AutoHandle<> thread = CreateRemoteThread(process, nullptr, 0, (LPTHREAD_START_ROUTINE)LoadLibraryW, remoteData, 0, nullptr)) WaitForSingleObject(thread, INFINITE);
else if (GetLastError() == ERROR_ACCESS_DENIED) AddConsoleOutput(NEED_64_BIT); // https://stackoverflow.com/questions/16091141/createremotethread-access-denied
VirtualFreeEx(process, remoteData, 0, MEM_RELEASE);
return;
}
}
AddConsoleOutput(INJECT_FAILED);
}).detach();
}
void DetachProcess(DWORD processId)
{
processRecordsByIds->at(processId).Send(HOST_COMMAND_DETACH);
}
void InsertHook(DWORD processId, HookParam hp)
{
processRecordsByIds->at(processId).Send(InsertHookCmd(hp));
}
void RemoveHook(DWORD processId, uint64_t address)
{
processRecordsByIds->at(processId).Send(RemoveHookCmd(address));
}
void FindHooks(DWORD processId, SearchParam sp, HookEventHandler HookFound)
{
if (HookFound) processRecordsByIds->at(processId).OnHookFound = HookFound;
processRecordsByIds->at(processId).Send(FindHookCmd(sp));
}
TextThread& GetThread(ThreadParam tp)
{
return textThreadsByParams->at(tp);
}
TextThread* GetThread(int64_t handle)
{
for (auto& [tp, thread] : textThreadsByParams.Acquire().contents) if (thread.handle == handle) return &thread;
return nullptr;
}
void AddConsoleOutput(std::wstring text)
{
GetThread(console).AddSentence(std::move(text));
}
}

Some files were not shown because too many files have changed in this diff Show More