Added Windows Build/Install Support

linux_build.sh is now replaced by the build.py and install.py Python
scripts. with this comes cross platform build/install support
between Windows and Linux.

as of v4.x.2.x of the MRCI protocol, the TEXT type id is now
formatted in UTF8 instead of UTF16LE unicode. this change also
affected the tcp rev and several async commands so client code had
to be ajusted accordingly.
This commit is contained in:
Maurice ONeal 2020-08-09 17:22:31 -04:00
parent 0c575ed8c9
commit fb4d0bed87
22 changed files with 888 additions and 173 deletions

7
.gitignore vendored
View File

@ -28,6 +28,10 @@ ui_*.h
*.jsc *.jsc
Makefile* Makefile*
*build-* *build-*
/build
/app_dir
/release
/debug
# Qt unit tests # Qt unit tests
target_wrapper.* target_wrapper.*
@ -47,3 +51,6 @@ compile_commands.json
# QtCreator local machine specific files for imported projects # QtCreator local machine specific files for imported projects
*creator.user* *creator.user*
# VSCode
/.vscode

View File

@ -36,10 +36,28 @@ DEFINES += QT_DEPRECATED_WARNINGS
# You can also select to disable deprecated APIs only up to a certain version of Qt. # You can also select to disable deprecated APIs only up to a certain version of Qt.
#DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0 #DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0
win32 {
LIBS += -llibcrypto -llibssl
TARGET = cmdr TARGET = cmdr
DESTDIR_TARGET = build\\windows\\cmdr.exe
OBJECTS_DIR = build\\windows
MOC_DIR = build\\windows
RCC_DIR = build\\windows
DESTDIR = build\\windows
} else {
LIBS += -lcrypto -lssl LIBS += -lcrypto -lssl
TARGET = build/linux/cmdr
OBJECTS_DIR = build/linux
MOC_DIR = build/linux
RCC_DIR = build/linux
}
SOURCES += src/main.cpp \ SOURCES += src/main.cpp \
src/cmd_line.cpp \ src/cmd_line.cpp \
src/cmd_objs/host_doc.cpp \ src/cmd_objs/host_doc.cpp \

281
build.py Normal file
View File

@ -0,0 +1,281 @@
#!/usr/bin/python3
import os
import re
import subprocess
import shutil
import platform
import sys
def get_app_target(text):
return re.search(r'(APP_TARGET) +(\"(.*?)\")', text).group(3)
def get_app_ver(text):
return re.search(r'(APP_VERSION) +(\"(.*?)\")', text).group(3)
def get_app_name(text):
return re.search(r'(APP_NAME) +(\"(.*?)\")', text).group(3)
def get_qt_path():
try:
return str(subprocess.check_output(["qtpaths", "--binaries-dir"]), 'utf-8').strip()
except:
print("A direct call to 'qtpaths' has failed so automatic retrieval of the QT bin folder is not possible.")
return input("Please enter the QT bin path (leave blank to cancel the build): ")
def get_qt_from_cli():
for arg in sys.argv:
if arg == "-qt_dir":
index = sys.argv.index(arg)
try:
return sys.argv[index + 1]
except:
return ""
return ""
def get_ver_header():
current_dir = os.path.dirname(__file__)
if current_dir == "":
return "src" + os.sep + "common.h"
else:
return current_dir + os.sep + "src" + os.sep + "common.h"
def get_nearest_subdir(path, sub_name):
dir_list = os.listdir(path)
ret = ""
for entry in dir_list:
if sub_name in entry:
ret = entry
break
return ret
def get_maker(qt_path):
ret = ""
if platform.system() == "Linux":
ret = "make"
elif platform.system() == "Windows":
path = os.path.abspath(qt_path + "\\..")
name = os.path.basename(path)
if "mingw" in name:
tools_path = os.path.abspath(qt_path + "\\..\\..\\..\\Tools")
mingw_ver = name[5:7]
mingw_tool_subdir = get_nearest_subdir(tools_path, "mingw" + mingw_ver)
mingw_tool_path = tools_path + "\\" + mingw_tool_subdir + "\\bin"
if not os.environ['PATH'].endswith(";"):
os.environ['PATH'] = os.environ['PATH'] + ";"
os.environ['PATH'] = os.environ['PATH'] + mingw_tool_path
ret = "mingw32-make"
elif "msvc" in name:
print("Warning: this script will assume you already ran the VsDevCmd.bat or vsvars32.bat script files")
print(" for Microsoft Visual Studio. Either way, a call to 'nmake' should be recognizable as ")
print(" a shell command otherwise this script will fail.\n")
ans = input("If that is the case enter 'y' to continue or any other key to cancel the build: ")
if ans == 'y' or ans == 'Y':
ret = "nmake"
else:
exit()
else:
print("The system platform is unknown. Output from platform.system() = " + platform.system())
return ret
def cd():
current_dir = os.path.dirname(__file__)
if current_dir != "":
os.chdir(current_dir)
def verbose_copy(src, dst):
print("cpy: " + src + " --> " + dst)
if os.path.isdir(src):
files = os.listdir(src)
for file in files:
tree_src = src + os.path.sep + file
tree_dst = dst + os.path.sep + file
if os.path.isdir(tree_src):
if not os.path.exists(tree_dst):
os.makedirs(tree_dst)
verbose_copy(tree_src, tree_dst)
else:
shutil.copyfile(src, dst)
def linux_build_app_dir(app_ver, app_name, app_target, qt_bin):
if not os.path.exists("app_dir/linux/platforms"):
os.makedirs("app_dir/linux/platforms")
if not os.path.exists("app_dir/linux/xcbglintegrations"):
os.makedirs("app_dir/linux/xcbglintegrations")
if not os.path.exists("app_dir/linux/lib"):
os.makedirs("app_dir/linux/lib")
if not os.path.exists("app_dir/icons"):
os.makedirs("app_dir/icons")
verbose_copy(qt_bin + "/../plugins/platforms/libqxcb.so", "app_dir/linux/platforms/libqxcb.so")
verbose_copy(qt_bin + "/../plugins/xcbglintegrations/libqxcb-egl-integration.so", "app_dir/linux/xcbglintegrations/libqxcb-egl-integration.so")
verbose_copy(qt_bin + "/../plugins/xcbglintegrations/libqxcb-glx-integration.so", "app_dir/linux/xcbglintegrations/libqxcb-glx-integration.so")
verbose_copy("build/linux/" + app_target, "app_dir/linux/" + app_target)
verbose_copy("icons", "app_dir/icons")
shutil.copyfile("build/linux/" + app_target, "/tmp/" + app_target)
# copying the executable file from the build folder to
# temp bypasses any -noexe retrictions a linux file
# system may have. there is a chance temp is also
# restricted in this way but that kind of config is
# rare. ldd will not run correctly with -noexe
# enabled.
lines = str(subprocess.check_output(["ldd", "/tmp/" + app_target]), 'utf-8').split("\n")
os.remove("/tmp/" + app_target)
for line in lines:
if " => " in line:
if ("libQt" in line) or ("libicu" in line) or ("libssl" in line) or ("libcrypto" in line) or ("libGL.so" in line) or ("libpcre16.so" in line) or ("libpcre.so" in line):
if " (0x0" in line:
start_index = line.index("> ") + 2
end_index = line.index(" (0x0")
src_file = line[start_index:end_index]
file_name = os.path.basename(src_file)
verbose_copy(src_file, "app_dir/linux/lib/" + file_name)
if "/usr/lib/x86_64-linux-gnu/qt5/bin" == qt_bin:
verbose_copy(qt_bin + "/../../libQt5DBus.so.5", "app_dir/linux/lib/libQt5DBus.so.5")
verbose_copy(qt_bin + "/../../libQt5XcbQpa.so.5", "app_dir/linux/lib/libQt5XcbQpa.so.5")
else:
verbose_copy(qt_bin + "/../lib/libQt5DBus.so.5", "app_dir/linux/lib/libQt5DBus.so.5")
verbose_copy(qt_bin + "/../lib/libQt5XcbQpa.so.5", "app_dir/linux/lib/libQt5XcbQpa.so.5")
verbose_copy("templates/linux_run_script.sh", "app_dir/linux/" + app_target + ".sh")
verbose_copy("templates/linux_uninstall.sh", "app_dir/linux/uninstall.sh")
verbose_copy("templates/linux_icon.desktop", "app_dir/linux/" + app_target + ".desktop")
complete(app_ver, app_target)
def windows_build_app_dir(app_ver, app_name, app_target, qt_bin):
if os.path.exists("release"):
os.removedirs("release")
if os.path.exists("debug"):
os.removedirs("debug")
if not os.path.exists("app_dir\\windows"):
os.makedirs("app_dir\\windows")
if not os.path.exists("app_dir\\icons"):
os.makedirs("app_dir\\icons")
verbose_copy("icons", "app_dir\\icons")
verbose_copy("build\\windows\\" + app_target + ".exe", "app_dir\\windows\\" + app_target + ".exe")
verbose_copy("templates\\windows_uninstall.bat", "app_dir\\windows\\uninstall.bat")
verbose_copy("templates\\win_icon.vbs", "app_dir\\windows\\icon.vbs")
os.chdir("app_dir\\windows\\")
result = subprocess.run([qt_bin + "\\" + "windeployqt", app_target + ".exe"])
cd()
if result.returncode == 0:
complete(app_ver, app_target)
def complete(app_ver, app_target):
if os.path.exists("Makefile"):
os.remove("Makefile")
if os.path.exists("Makefile.Debug"):
os.remove("Makefile.Debug")
if os.path.exists("Makefile.Release"):
os.remove("Makefile.Release")
if os.path.exists("object_script." + app_target + ".Debug"):
os.remove("object_script." + app_target + ".Debug")
if os.path.exists("object_script." + app_target + ".Release"):
os.remove("object_script." + app_target + ".Release")
print("Build complete for version: " + app_ver)
print("You can now run the install.py script to install onto this machine or create an installer.")
def main():
with open(get_ver_header()) as file:
text = file.read()
app_target = get_app_target(text)
app_ver = get_app_ver(text)
app_name = get_app_name(text)
qt_bin = get_qt_from_cli()
if qt_bin == "":
qt_bin = get_qt_path()
maker = get_maker(qt_bin)
if qt_bin != "":
print("app_target = " + app_target)
print("app_version = " + app_ver)
print("app_name = " + app_name)
print("qt_bin = " + qt_bin)
print("maker = " + maker + "\n")
if maker == "":
print("Could not find a valid maker/compiler on this platform, unable to continue.")
else:
cd()
result = subprocess.run([qt_bin + os.sep + "qmake", "-config", "release"])
if result.returncode == 0:
result = subprocess.run([maker])
if result.returncode == 0:
if not os.path.exists("app_dir"):
os.makedirs("app_dir")
with open("app_dir" + os.sep + "info.txt", "w") as info_file:
info_file.write(app_target + "\n")
info_file.write(app_ver + "\n")
info_file.write(app_name + "\n")
if platform.system() == "Linux":
linux_build_app_dir(app_ver, app_name, app_target, qt_bin)
elif platform.system() == "Windows":
windows_build_app_dir(app_ver, app_name, app_target, qt_bin)
else:
print("The platform you are running in is not compatible with the app_dir build out procedure.")
print(" output from platform.system() = " + platform.system())
if __name__ == "__main__":
main()

View File

@ -44,18 +44,21 @@ notes:
### Client Header (This Application) ### ### Client Header (This Application) ###
``` ```
[tag][appName][padding] [tag][appName][mod_instructions][padding]
tag - 4bytes - 0x4D, 0x52, 0x43, 0x49 (MRCI) tag - 4bytes - 0x4D, 0x52, 0x43, 0x49 (MRCI)
appName - 134bytes - UTF16LE string (padded with 0x00) appName - 32bytes - UTF8 string (padded with 0x00)
padding - 272bytes - padding of 0x00 bytes reserved for future expansion modInst - 128bytes - UTF8 string (padded with 0x00)
padding - 128bytes - string of (0x00)
``` ```
notes: notes:
* The **tag** is just a fixed ascii string "MRCI" that indicates to the host that the client is indeed attempting to use the MRCI protocol. * **tag** is just a fixed ascii string "MRCI" that indicates to the host that the client is indeed attempting to use the MRCI protocol.
* The **appName** is the name of the client application that is connected to the host. It can also contain the client's app version if needed because it doesn't follow any particular standard. This string is accessable to all modules so the commands themselves can be made aware of what app the user is currently using. * **appName** is the name of the client application that is connected to the host. It can also contain the client's app version if needed because it doesn't follow any particular standard. This string is accessable to all modules so the commands themselves can be made aware of what app the user is currently using.
* **modInst** is an additional set of command lines that can be passed onto to all module processes when they are intialized. This can be used by certain clients that want to intruct certain modules that might be installed in the host to do certain actions during intialization. This remains constant for as long as the session is active and cannot be changed at any point.
### Host Header ### ### Host Header ###
@ -85,31 +88,34 @@ notes:
Async commands are 'virtual commands' that this application can encounter at any time while connected to the host. More information about this can be found in the [Async.md](Async.md) document. This application does act on some of the data carried by the async commands but not all of them. Async commands are 'virtual commands' that this application can encounter at any time while connected to the host. More information about this can be found in the [Async.md](Async.md) document. This application does act on some of the data carried by the async commands but not all of them.
### Development Setup ### ### Build Setup ###
Linux Required Packages: For Linux you need the following packages to successfully build/install:
``` ```
qtbase5-dev qtbase5-dev
libssl-dev libssl-dev
gcc gcc
make make
makeself python3
``` ```
### Build From Source (Linux) ### For Windows support you need to have the following applications installed:
```
OpenSSL
Qt5.12 or newer
Python3
```
Linux_build.sh is a custom script designed to build this project from the source code using qmake, make and makeself. You can pass 2 optional arguments: ### Build ###
1. The path to the QT bin folder in case you want to compile with a QT install not defined in PATH. To build this project from source you just need to run the build.py and then the install.py python scripts. While running the build the script, it will try to find the Qt API installed in your machine according to the PATH env variable. If not found, it will ask you to input where it can find the Qt bin folder where the qmake executable exists or you can bypass all of this by passing the -qt_dir option on it's command line.
2. Path of the output makeself file (usually has a .run extension). If not given, the outfile will be named cmdr-x.x.run in the source code folder.
Build: while running the install script, it will ask you to input 1 of 3 options:
```
cd /path/to/source/code ***local machine*** - This option will install the built application onto the local machine without creating an installer.
sh ./linux_build.sh
``` ***create installer*** - This option creates an installer that can be distributed to other machines to installation. The resulting installer is just a regular .py script file that the target machine can run if it has Python3 insalled. Only Python3 needs to be installed and an internet connection is not required.
Install:
``` ***exit*** - Cancel the installation.
chmod +x ./cmdr-x.x.run
./cmdr-x.x.run -local or -installer can be passed as command line options for install.py to explicitly select one of the above options without pausing for user input.
```

View File

@ -220,7 +220,7 @@ to_client: [type_id(25)][cmd_id(28)][branch_id(0)][size_of_payload][payload(CH_M
``` ```
```ASYNC_RM_CH_MEMBER (29)``` ```ASYNC_RM_CH_MEMBER (29)```
This async command carries a combination of the channel id and the 32byte user id of the channel member that has left or kicked from the channel member list. All sessions that are logged in as a member of the channel forwards the data to the clients as a [BYTES](type_ids.md) frame. This async command carries a combination of the channel id and the 32byte user id of the channel member that has left or kicked from the channel member list. All sessions that are logged in as a member of the channel forwards the data to the clients as a [BYTES](type_ids.md) frame. This async aommand also sent when a user declines an invite to a channel.
``` ```
from_module: [8bytes(chId)][32bytes(userId)] from_module: [8bytes(chId)][32bytes(userId)]
to_client: [type_id(14)][cmd_id(29)][branch_id(0)][size_of_payload][payload(64bit_ch_id + 256bit_user_id)] to_client: [type_id(14)][cmd_id(29)][branch_id(0)][size_of_payload][payload(64bit_ch_id + 256bit_user_id)]

View File

@ -42,7 +42,7 @@ enum TypeID : quint8
```TEXT``` ```TEXT```
This is text that can be displayed directly to the user, passed as command line arguments to be processed or used to carry text data within other data types. This is text that can be displayed directly to the user, passed as command line arguments to be processed or used to carry text data within other data types.
format: ```[UTF-16LE_string] (no BOM)``` format: ```[UTF-8_string] (no BOM)```
```GEN_FILE``` ```GEN_FILE```
This is a file transfer type id that can be used to transfer any file type (music, photos, documents, etc...). It operates in its own protocol of sorts. The 1st GEN_FILE frame received by the host or client is TEXT parameters similar to what you see in terminal command lines with at least one of the arguments listed below. The next set of GEN_FILE frames received by the host or client is then the binary data that needs to be written to an open file or streamed until the limit defined in -len is meet. This is a file transfer type id that can be used to transfer any file type (music, photos, documents, etc...). It operates in its own protocol of sorts. The 1st GEN_FILE frame received by the host or client is TEXT parameters similar to what you see in terminal command lines with at least one of the arguments listed below. The next set of GEN_FILE frames received by the host or client is then the binary data that needs to be written to an open file or streamed until the limit defined in -len is meet.
@ -77,7 +77,7 @@ This id can be treated exactly like TEXT except this tells the client that the c
This is similar to PRIV_TEXT expect it is not asking for private information. It is simply prompting for non-sensitive information from the user. This is similar to PRIV_TEXT expect it is not asking for private information. It is simply prompting for non-sensitive information from the user.
```BIG_TEXT``` ```BIG_TEXT```
Also formatted exactly like TEXT but this indicates to the client that this is a large body of text that is recommended to be word wrapped when displaying to the user. It can contain line breaks so clients are also recommended to honor those line breaks. Also formatted exactly like TEXT but this indicates to the client that this is a large body of text that is recommended to be word wrapped when displaying to the user. It can contain line breaks so clients are recommended to honor those line breaks.
```IDLE``` ```IDLE```
All commands started during the session returns this type id when it has finished it's task. It carries a 16bit unsigned integer indicating the result of the task that the command was running. All commands started during the session returns this type id when it has finished it's task. It carries a 16bit unsigned integer indicating the result of the task that the command was running.
@ -97,13 +97,13 @@ enum RetCode : quint16
notes: notes:
1. the custom return code can be additional data added to the end of the 16bit 1. the custom return code can be additional data added to the end of the 16bit
integer that can carry additional data about the result of the task. it can integer that can carry additional data about the result of the task. it can
be any format that the module itself decides it can should be. nothing is be any format that the module itself decides it to be. nothing is stopping
stopping modules from defining return codes beyond the value of 7 but it is modules from defining return codes beyond the value of 7 but it is advised
advised not to because this enum might be expanded in the future. not to because this enum might be expanded in the future.
``` ```
```TERM_CMD``` ```TERM_CMD```
This type id doesn't carry any actual data. It is used to tell the host to stop/terminate the command id and branch id that was used to send it. It does not actually terminate the module's process within the host, it only simply tells it to stop what it is currently doing. This will also terminate any commands in a prompt/more input state. This type id doesn't carry any actual data. It is used to tell the host to stop/terminate the command id and branch id that was used to send it. It does not actually terminate the module's process within the host, it will only tell it to stop what it is currently doing. This will also terminate any commands in a prompt state.
```KILL_CMD``` ```KILL_CMD```
This works similarly to TERM_CMD except it will also terminate the module process. The module process will have 3 seconds to shutdown gracefully before it is force killed by the host session. This works similarly to TERM_CMD except it will also terminate the module process. The module process will have 3 seconds to shutdown gracefully before it is force killed by the host session.
@ -143,12 +143,12 @@ This is a data structure that carries information about a file system object (fi
2. bytes[1-8] - creation time in msec since Epoch UTC (64bit little endian uint) 2. bytes[1-8] - creation time in msec since Epoch UTC (64bit little endian uint)
3. bytes[9-16] - modification time in msec since Epoch UTC (64bit little endian uint) 3. bytes[9-16] - modification time in msec since Epoch UTC (64bit little endian uint)
4. bytes[17-24] - file size (64bit little endian uint) 4. bytes[17-24] - file size (64bit little endian uint)
5. bytes[25-n] - file name (UTF16-LE string, 16bit terminated) 5. bytes[25-n] - file name (UTF8 string, NULL terminated)
6. bytes[n-n] - symmlink target if it is a symmlink (UTF16-LE string, 16bit terminated) 6. bytes[n-n] - symmlink target if it is a symmlink (UTF8 string, NULL terminated)
notes: notes:
1. 16bit terminated UTF-16LE strings are basically 1. NULL terminated UTF-8 strings are basically tailed
terminated by 2 bytes of 0x00. by a single 0x00 byte to indicate the end-of-string.
2. the symmlink target is empty if not a symmlink but 2. the symmlink target is empty if not a symmlink but
the terminator should still be present. the terminator should still be present.
@ -171,9 +171,9 @@ This carry some user account and session information about a peer client connect
format: format:
1. bytes[0-27] 28bytes - session id (224bit hash) 1. bytes[0-27] 28bytes - session id (224bit hash)
2. bytes[28-59] 32bytes - user id (256bit hash) 2. bytes[28-59] 32bytes - user id (256bit hash)
3. bytes[60-107] 48bytes - user name (TEXT - padded with 0x00) 3. bytes[60-83] 24bytes - user name (TEXT - padded with 0x00)
4. bytes[108-241] 134bytes - app name (TEXT - padded with 0x00) 4. bytes[84-115] 32bytes - app name (TEXT - padded with 0x00)
5. bytes[242-305] 64bytes - disp name (TEXT - padded with 0x00) 5. bytes[116-139] 24bytes - disp name (TEXT - padded with 0x00)
notes: notes:
1. the session id is unique to the peer's session connection only. it 1. the session id is unique to the peer's session connection only. it
@ -183,23 +183,23 @@ This carry some user account and session information about a peer client connect
even when the user name changes and across all clients logged into even when the user name changes and across all clients logged into
the same account. the same account.
3. the display name is the preffered display name of the peer. clients 3. the display name is the preferred display name of the peer. clients
are encouraged to use this rather than the user name when displaying are encouraged to use this rather than the user name when displaying
peer info to the user. if empty, it's ok to just fall back to the user peer info to the end user. if empty, then it is ok to fall back to
name. displaying the user name.
``` ```
```PING_PEERS``` ```PING_PEERS```
This is formatted extactly as PEER_INFO except it is used by the ASYNC_LIMITED_CAST [async](async.md) command to tell all peer sessions that receive it to send a PEER_INFO frame about the local session to their own clients and return PEER_INFO frames about themselves to the local session. This is formatted extactly as PEER_INFO except it is used by the ASYNC_LIMITED_CAST [async](async.md) command to tell all peer sessions that receive it to forward the same frame to their own clients and return PEER_INFO frames about themselves to the session that sent the PING_PEERS.
```MY_INFO``` ```MY_INFO```
This contains all of the information found in ```PEER_INFO``` for the local session but also includes the following: This contains all of the information found in ```PEER_INFO``` for the local session but also includes the following:
``` ```
format: format:
1. bytes[306-433] 128bytes - email (TEXT - padded with 0x00) 1. bytes[140-203] 64bytes - email (TEXT - padded with 0x00)
2. bytes[434-437] 4bytes - host rank (32bit unsigned int) 2. bytes[204-207] 4bytes - host rank (32bit unsigned int)
3. bytes[438] 1byte - is email confirmed? (0x00 false, 0x01 true) 3. bytes[208] 1byte - is email confirmed? (0x00 false, 0x01 true)
``` ```
```NEW_CMD``` ```NEW_CMD```
@ -209,11 +209,11 @@ This contains information about a new command that was added to the current sess
format: format:
1. bytes[0-1] 2bytes - 16bit LE unsigned int (command id) 1. bytes[0-1] 2bytes - 16bit LE unsigned int (command id)
2. bytes[2] 1byte - 8bit LE unsigned int (genfile type) 2. bytes[2] 1byte - 8bit LE unsigned int (genfile type)
3. bytes[3-130] 128bytes - command name (TEXT - padded with 0x00) 3. bytes[3-66] 64bytes - command name (TEXT - padded with 0x00)
4. bytes[131-258] 128bytes - library name (TEXT - padded with 0x00) 4. bytes[67-130] 64bytes - library name (TEXT - padded with 0x00)
5. bytes[259-n] variable - short text (16bit null terminated) 5. bytes[131-n] variable - short text (null terminated)
6. bytes[n-n] variable - io text (16bit null terminated) 6. bytes[n-n] variable - io text (null terminated)
7. bytes[n-n] variable - long text (16bit null terminated) 7. bytes[n-n] variable - long text (null terminated)
notes: notes:
1. the genfile type is numerical value of 2, 3 or 0. a value of 2 1. the genfile type is numerical value of 2, 3 or 0. a value of 2
@ -288,13 +288,13 @@ This contains public information about a channel member.
2. bytes[8-39] 32bytes - user id (256bit hash) 2. bytes[8-39] 32bytes - user id (256bit hash)
3. bytes[40] 1byte - is invite? (0x00=false, 0x01=true) 3. bytes[40] 1byte - is invite? (0x00=false, 0x01=true)
4. bytes[41] 1byte - member's channel privilege level (8bit unsigned int) 4. bytes[41] 1byte - member's channel privilege level (8bit unsigned int)
5. bytes[42-n] variable - user name (TEXT - 16bit null terminated) 5. bytes[42-n] variable - user name (TEXT - null terminated)
6. bytes[n-n] variable - display name (TEXT - 16bit null terminated) 6. bytes[n-n] variable - display name (TEXT - null terminated)
7. bytes[n-n] variable - channel name (TEXT - 16bit null terminated) 7. bytes[n-n] variable - channel name (TEXT - null terminated)
notes: notes:
1. a 16bit null terminated TEXT formatted string ended with 2 bytes of 1. a null terminated TEXT formatted string ends with a 0x00 to
(0x00) to indicate the end of the string data. indicate the end of the string.
2. the member's privilege level can be any of the values discribed in 2. the member's privilege level can be any of the values discribed in
section [4.3](host_features.md). section [4.3](host_features.md).

BIN
icons/win_icon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

372
install.py Normal file
View File

@ -0,0 +1,372 @@
#!/usr/bin/python3
import os
import subprocess
import shutil
import platform
import sys
import zipfile
import binascii
import tempfile
def cd():
current_dir = os.path.dirname(__file__)
if current_dir != "":
os.chdir(current_dir)
def get_default_install_dir(app_target, app_name):
if platform.system() == "Linux":
return "/opt/" + app_target
else:
return os.environ['ProgramFiles'] + "\\" + app_name
def get_default_installer_path(app_ver, app_name):
return os.path.expanduser("~") + os.sep + app_name + "-" + app_ver + ".py"
def get_install_dir(app_target, app_name):
path = get_default_install_dir(app_target, app_name)
print("The default install directory is: " + path)
while(True):
ans = input("Do you want to change it? (y/n): ")
if ans == "y" or ans == "Y":
path = input("Enter a new install directory (leave blank to go back to the default): ")
path = os.path.normpath(path)
break
elif ans == "n" or ans == "N":
break
if path == "":
return get_default_install_dir(app_target, app_name)
else:
return path
def get_installer_path(app_ver, app_name):
path = get_default_installer_path(app_ver, app_name)
print("The built .py installer will placed here: " + path)
while(True):
ans = input("Do you want to change the path? (y/n): ")
if ans == "y" or ans == "Y":
path = input("Enter a new path (leave blank to go back to the default): ")
path = os.path.normpath(path)
break
elif ans == "n" or ans == "N":
break
if path == "":
return get_default_installer_path(app_ver, app_name)
else:
return path
def make_install_dir(path):
try:
if not os.path.exists(path):
os.makedirs(path)
return True
except:
print("Failed to create the install directory, please make sure you are runnning this script with admin rights.")
return False
def replace_bin(binary, old_bin, new_bin, offs):
while(True):
try:
index = binary.index(old_bin, offs)
binary = binary[:index] + new_bin + binary[index + len(old_bin):]
except ValueError:
break
return binary
def bin_sub_copy_file(src, dst, old_bin, new_bin, offs):
binary = bytearray()
with open(src, "rb") as rd_file:
binary = rd_file.read()
binary = replace_bin(binary, old_bin, new_bin, offs)
with open(dst, "wb") as wr_file:
wr_file.write(binary)
def text_sub_copy_file(src, dst, old_text, new_text, offs):
bin_sub_copy_file(src, dst, old_text.encode("utf-8"), new_text.encode("utf-8"), offs)
def text_template_deploy(src, dst, install_dir, app_name, app_target):
print("dep: " + dst)
text_sub_copy_file(src, dst, "$install_dir", install_dir, 0)
text_sub_copy_file(dst, dst, "$app_name", app_name, 0)
text_sub_copy_file(dst, dst, "$app_target", app_target, 0)
def verbose_copy(src, dst):
print("cpy: " + src + " --> " + dst)
if os.path.isdir(src):
files = os.listdir(src)
if not os.path.exists(dst):
os.makedirs(dst)
for file in files:
tree_src = src + os.path.sep + file
tree_dst = dst + os.path.sep + file
if os.path.isdir(tree_src):
if not os.path.exists(tree_dst):
os.makedirs(tree_dst)
verbose_copy(tree_src, tree_dst)
else:
shutil.copyfile(src, dst)
def verbose_create_symmlink(src, dst):
print("lnk: " + src + " --> " + dst)
if os.path.exists(dst):
os.remove(dst)
os.symlink(src, dst)
def local_install(app_target, app_name):
if platform.system() == "Linux":
if not os.path.exists("app_dir/linux"):
print("An app_dir for the Linux platform could not be found.")
else:
install_dir = get_install_dir(app_target, app_name)
if os.path.exists(install_dir + "/uninstall.sh"):
subprocess.run([install_dir + "/uninstall.sh"])
if make_install_dir(install_dir):
if not os.path.exists("/var/opt/" + app_target):
os.makedirs("/var/opt/" + app_target)
text_template_deploy("app_dir/linux/" + app_target + ".sh", install_dir + "/" + app_target + ".sh", install_dir, app_name, app_target)
text_template_deploy("app_dir/linux/uninstall.sh", install_dir + "/uninstall.sh", install_dir, app_name, app_target)
text_template_deploy("app_dir/linux/" + app_target + ".desktop", "/usr/share/applications/" + app_target + ".desktop", install_dir, app_name, app_target)
for image_res in ["8x8", "16x16", "22x22", "24x24", "32x32", "36x36", "42x42", "48x48", "64x64", "192x192", "256x256", "512x512"]:
src = "app_dir/icons/" + image_res + ".png"
dst = "/usr/share/icons/hicolor/" + image_res
if os.path.exists(dst):
verbose_copy(src, dst + "/apps/" + app_target + ".png")
subprocess.run(["chmod", "644", dst + "/apps/" + app_target + ".png"])
verbose_copy("app_dir/icons/scalable.svg", "/usr/share/icons/hicolor/scalable/apps/" + app_target + ".svg")
verbose_copy("app_dir/linux/" + app_target, install_dir + "/" + app_target)
verbose_copy("app_dir/linux/lib", install_dir + "/lib")
verbose_copy("app_dir/linux/platforms", install_dir + "/platforms")
verbose_copy("app_dir/linux/xcbglintegrations", install_dir + "/xcbglintegrations")
verbose_create_symmlink(install_dir + "/" + app_target + ".sh", "/usr/bin/" + app_target)
subprocess.run(["chmod", "644", "/usr/share/icons/hicolor/scalable/apps/" + app_target + ".svg"])
subprocess.run(["chmod", "755", install_dir + "/" + app_target + ".sh"])
subprocess.run(["chmod", "755", install_dir + "/" + app_target])
subprocess.run(["chmod", "755", install_dir + "/uninstall.sh"])
print("Installation finished. If you ever need to uninstall this application, run this command with root rights:")
print(" sh " + install_dir + "/uninstall.sh\n")
elif platform.system() == "Windows":
if not os.path.exists("app_dir\\windows"):
print("An app_dir for the Windows platform could not be found.")
else:
install_dir = get_install_dir(app_target, app_name)
if os.path.exists(install_dir + "\\uninstall.bat"):
subprocess.run([install_dir + "\\uninstall.bat"])
if os.path.exists(install_dir):
# this block is here to make sure the install_dir is deleted
# if/when the uninstall.bat fails to do so. in my test machine,
# the .bat script will delete install_dir if run directly but
# not when called through subprocess.run() for some reason.
shutil.rmtree(install_dir)
if make_install_dir(install_dir):
verbose_copy("app_dir\\windows", install_dir)
verbose_copy("app_dir\\icons\\win_icon.ico", install_dir + "\\icon.ico")
text_template_deploy("app_dir\\windows\\uninstall.bat", install_dir + "\\uninstall.bat", install_dir, app_name, app_target)
text_template_deploy("app_dir\\windows\\icon.vbs", install_dir + "\\icon.vbs", install_dir, app_name, app_target)
text_sub_copy_file(install_dir + "\\icon.vbs", install_dir + "\\icon.vbs", "%SystemDrive%", os.environ['SystemDrive'], 0)
os.system("\"" + install_dir + "\\icon.vbs\"")
print("Installation finished. If you ever need to uninstall this application, run this batch file with admin rights:")
print(" " + install_dir + "\\uninstall.bat\n")
else:
print("The platform you are running in is not compatible.")
print(" output from platform.system() = " + platform.system())
def dir_tree(path):
ret = []
if os.path.isdir(path):
for entry in os.listdir(path):
full_path = os.path.join(path, entry)
if os.path.isdir(full_path):
for sub_dir_file in dir_tree(full_path):
ret.append(sub_dir_file)
else:
ret.append(full_path)
return ret
def to_hex(data):
return str(binascii.hexlify(data))[2:-1]
def from_hex(text_line):
return binascii.unhexlify(text_line)
def make_install(app_ver, app_name):
path = get_installer_path(app_ver, app_name)
with zipfile.ZipFile("app_dir.zip", "w", compression=zipfile.ZIP_DEFLATED) as zip_file:
print("Compressing app_dir --")
for file in dir_tree("app_dir"):
print("adding file: " + file)
zip_file.write(file)
text_sub_copy_file(__file__, path, "main(is_sfx=False)", "main(is_sfx=True)\n\n\n", 7700)
with open(path, "a") as dst_file, open("app_dir.zip", "rb") as src_file:
print("Packing the compressed app_dir into the sfx script file --")
dst_file.write("# APP_DIR\n")
stat = os.stat("app_dir.zip")
while(True):
buffer = src_file.read(4000000)
if len(buffer) != 0:
dst_file.write("# " + to_hex(buffer) + "\n")
print(str(src_file.tell()) + "/" + str(stat.st_size))
if len(buffer) < 4000000:
break
os.remove("app_dir.zip")
print("Finished.")
def sfx():
abs_sfx_path = os.path.abspath(__file__)
mark_found = False
os.chdir(tempfile.gettempdir())
with open(abs_sfx_path) as packed_file, open("app_dir.zip", "wb") as zip_file:
stat = os.stat(abs_sfx_path)
print("Unpacking the app_dir compressed file from the sfx script.")
while(True):
line = packed_file.readline()
if not line:
break
elif mark_found:
zip_file.write(from_hex(line[2:-1]))
print(str(packed_file.tell()) + "/" + str(stat.st_size))
else:
if line == "# APP_DIR\n":
mark_found = True
print("Done.")
if not mark_found:
print("The app_dir mark was not found, unable to continue.")
else:
with zipfile.ZipFile("app_dir.zip", "r", compression=zipfile.ZIP_DEFLATED) as zip_file:
print("De-compressing app_dir --")
zip_file.extractall()
print("Preparing for installation.")
os.remove("app_dir.zip")
with open("app_dir" + os.sep + "info.txt") as info_file:
info = info_file.read().split("\n")
local_install(info[0], info[2])
shutil.rmtree("app_dir")
def main(is_sfx):
cd()
app_target = ""
app_ver = ""
app_name = ""
if not is_sfx:
with open("app_dir" + os.sep + "info.txt") as info_file:
info = info_file.read().split("\n")
app_target = info[0]
app_ver = info[1]
app_name = info[2]
if is_sfx:
sfx()
elif "-local" in sys.argv:
local_install(app_target, app_name)
elif "-installer" in sys.argv:
make_install(app_ver, app_name)
else:
print("Do you want to install onto this machine or create an installer?")
print("[1] local machine")
print("[2] create installer")
print("[3] exit")
while(True):
opt = input("select an option: ")
if opt == "1":
local_install(app_target, app_name)
break
elif opt == "2":
make_install(app_ver, app_name)
break
elif opt == "3":
break
if __name__ == "__main__":
main(is_sfx=False)

View File

@ -164,7 +164,7 @@ void CmdLine::toHost(const QString &cmdName, const QString &args)
{ {
if (flags & HOST_HOOK) if (flags & HOST_HOOK)
{ {
emit dataToHookedHost(toTEXT(args), TEXT); emit dataToHookedHost(args.toUtf8(), TEXT);
} }
else if (!cmdName.isEmpty()) else if (!cmdName.isEmpty())
{ {
@ -178,11 +178,11 @@ void CmdLine::toHost(const QString &cmdName, const QString &args)
{ {
emit setHostCmdId(cmdId); emit setHostCmdId(cmdId);
emit setGenfileType(Shared::genfileTypes->value(cmdId)); emit setGenfileType(Shared::genfileTypes->value(cmdId));
emit dataToGenFile(toTEXT(args)); emit dataToGenFile(args.toUtf8());
} }
else else
{ {
emit dataToHost(cmdId, toTEXT(args), TEXT); emit dataToHost(cmdId, args.toUtf8(), TEXT);
} }
} }
} }
@ -225,7 +225,7 @@ void CmdLine::procCmdLine(const QString &line)
if (flags & GEN_HOOK) if (flags & GEN_HOOK)
{ {
emit dataToGenFile(toTEXT(argsLine)); emit dataToGenFile(argsLine.toUtf8());
} }
else if (Shared::clientCmds->contains(cmdName)) else if (Shared::clientCmds->contains(cmdName))
{ {

View File

@ -18,13 +18,27 @@
HostDoc::HostDoc(const QByteArray &import, QObject *parent) : Command(parent) HostDoc::HostDoc(const QByteArray &import, QObject *parent) : Command(parent)
{ {
// ```NEW_CMD```
// This contains information about a new command that was added to the current session.
// ```
// format:
// 1. bytes[0-1] 2bytes - 16bit LE unsigned int (command id)
// 2. bytes[2] 1byte - 8bit LE unsigned int (genfile type)
// 3. bytes[3-66] 64bytes - command name (TEXT - padded with 0x00)
// 4. bytes[67-130] 64bytes - library name (TEXT - padded with 0x00)
// 5. bytes[131-n] variable - short text (null terminated)
// 6. bytes[n-n] variable - io text (null terminated)
// 7. bytes[n-n] variable - long text (null terminated)
valid = false; valid = false;
if (import.size() >= 259) if (import.size() >= 131) // within safe import size
{ {
cmdId = static_cast<quint16>(rdInt(import.mid(0, 2))); cmdId = static_cast<quint16>(rdInt(import.mid(0, 2))); // 1.
auto cmdName = QString(import.mid(3, 64)).toLower(); // 3.
QString cmdName = fromTEXT(import.mid(3, 128)).toLower();
QString num; QString num;
for (int i = 1; Shared::clientCmds->contains(cmdName + num); ++i) for (int i = 1; Shared::clientCmds->contains(cmdName + num); ++i)
@ -34,18 +48,18 @@ HostDoc::HostDoc(const QByteArray &import, QObject *parent) : Command(parent)
setObjectName(cmdName + num); setObjectName(cmdName + num);
if ((import[2] == GEN_UPLOAD) || (import[2] == GEN_DOWNLOAD)) if ((import[2] == GEN_UPLOAD) || (import[2] == GEN_DOWNLOAD)) // 2.
{ {
Shared::genfileCmds->insert(cmdId, objectName()); Shared::genfileCmds->insert(cmdId, objectName());
Shared::genfileTypes->insert(cmdId, static_cast<quint8>(import[2])); Shared::genfileTypes->insert(cmdId, static_cast<quint8>(import[2]));
} }
quint32 offs = 259; quint32 offs = 131; // 5.
libTxt = fromTEXT(import.mid(131, 128)); libTxt = QString(import.mid(67, 64)); // 4.
shortTxt = readNullTermText(import, &offs); shortTxt = readNullTermText(import, &offs);
ioTxt = readNullTermText(import, &offs); ioTxt = readNullTermText(import, &offs); // 6.
longTxt = readNullTermText(import, &offs); longTxt = readNullTermText(import, &offs); // 7.
valid = true; valid = true;
Shared::hostCmds->insert(cmdId, objectName()); Shared::hostCmds->insert(cmdId, objectName());
@ -55,29 +69,23 @@ HostDoc::HostDoc(const QByteArray &import, QObject *parent) : Command(parent)
QString HostDoc::readNullTermText(const QByteArray &data, quint32 *offs) QString HostDoc::readNullTermText(const QByteArray &data, quint32 *offs)
{ {
static const QByteArray null16(2, 0x00);
QString ret; QString ret;
quint32 len = static_cast<quint32>(data.size());
for (; *offs < len; *offs += 2) auto len = static_cast<quint32>(data.size());
{
if ((*offs + 2) < len)
{
QByteArray chr = QByteArray::fromRawData(data.data() + *offs, 2);
if (chr == null16) for (; *offs < len; *offs += 1)
{
if (data[*offs] == 0x00)
{ {
break; break;
} }
else else
{ {
ret.append(fromTEXT(chr)); ret.append(data[*offs]);
}
} }
} }
*offs += 2; *offs += 1;
return ret; return ret;
} }

View File

@ -45,9 +45,9 @@ void About::dispInfo(Command *cmdObj)
QTextStream txtOut(&txt); QTextStream txtOut(&txt);
wordWrap("i/o: ", txtOut, cmdObj->ioText(), Shared::mainWidget); wordWrap("i/o: ", txtOut, cmdObj->ioText(), Shared::mainWidget);
txtOut << "" << endl; txtOut << "" << Qt::endl;
wordWrap("library: ", txtOut, cmdObj->libText(), Shared::mainWidget); wordWrap("library: ", txtOut, cmdObj->libText(), Shared::mainWidget);
txtOut << "" << endl; txtOut << "" << Qt::endl;
wordWrap("usage: ", txtOut, cmdObj->longText(), Shared::mainWidget); wordWrap("usage: ", txtOut, cmdObj->longText(), Shared::mainWidget);
cacheTxt(TEXT, txt); cacheTxt(TEXT, txt);
@ -115,12 +115,12 @@ void About::dataIn(const QString &argsLine)
QString txt; QString txt;
QTextStream txtOut(&txt); QTextStream txtOut(&txt);
txtOut << libText() << endl << endl; txtOut << libText() << Qt::endl << Qt::endl;
txtOut << "Based on QT " << QT_VERSION_STR << " " << 8 * QT_POINTER_SIZE << "bit" << endl << endl; txtOut << "Based on QT " << QT_VERSION_STR << " " << 8 * QT_POINTER_SIZE << "bit" << Qt::endl << Qt::endl;
txtOut << "The program is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE" << endl; txtOut << "The program is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE" << Qt::endl;
txtOut << "WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE." << endl << endl; txtOut << "WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE." << Qt::endl << Qt::endl;
txtOut << "run: 'ls_cmds' to see all available commands." << endl << endl; txtOut << "run: 'ls_cmds' to see all available commands." << Qt::endl << Qt::endl;
txtOut << "for more detailed information about a command run: 'about <command>'" << endl << endl; txtOut << "for more detailed information about a command run: 'about <command>'" << Qt::endl << Qt::endl;
cacheTxt(TEXT, txt); cacheTxt(TEXT, txt);
} }
@ -145,8 +145,8 @@ void ListCmds::ls(QHash<QString, Command *> *cmdObjs, QTextStream &txtOut, const
cmdNames.sort(Qt::CaseInsensitive); cmdNames.sort(Qt::CaseInsensitive);
txtOut << endl; txtOut << Qt::endl;
txtOut << title << endl << endl; txtOut << title << Qt::endl << Qt::endl;
for (int i = 0; i < cmdNames.size(); ++i) for (int i = 0; i < cmdNames.size(); ++i)
{ {

View File

@ -34,20 +34,20 @@ void Status::dataIn(const QString &argsLine)
QString txt; QString txt;
QTextStream txtOut(&txt); QTextStream txtOut(&txt);
txtOut << "--Local data" << endl << endl; txtOut << "--Local data" << Qt::endl << Qt::endl;
txtOut << " Client version : " << QCoreApplication::applicationVersion() << endl; txtOut << " Client version : " << QCoreApplication::applicationVersion() << Qt::endl;
txtOut << " Connected? : " << boolText(*Shared::connectedToHost) << endl; txtOut << " Connected? : " << boolText(*Shared::connectedToHost) << Qt::endl;
txtOut << " Address : " << *Shared::hostAddress << endl; txtOut << " Address : " << *Shared::hostAddress << Qt::endl;
txtOut << " Port : " << *Shared::hostPort << endl; txtOut << " Port : " << *Shared::hostPort << Qt::endl;
if (*Shared::connectedToHost) if (*Shared::connectedToHost)
{ {
txtOut << "" << endl; txtOut << "" << Qt::endl;
txtOut << "--Session data" << endl << endl; txtOut << "--Session data" << Qt::endl << Qt::endl;
txtOut << " Client address : " << Shared::socket->localAddress().toString() << endl; txtOut << " Client address : " << Shared::socket->localAddress().toString() << Qt::endl;
txtOut << " Host address : " << Shared::socket->peerAddress().toString() << endl; txtOut << " Host address : " << Shared::socket->peerAddress().toString() << Qt::endl;
txtOut << " Session id : " << Shared::sessionId->toHex() << endl; txtOut << " Session id : " << Shared::sessionId->toHex() << Qt::endl;
txtOut << " Host version : " << verText() << endl; txtOut << " Host version : " << verText() << Qt::endl;
txtOut << " GEN_FILE commands : "; txtOut << " GEN_FILE commands : ";
QStringList genCmds = Shared::genfileCmds->values(); QStringList genCmds = Shared::genfileCmds->values();
@ -56,13 +56,13 @@ void Status::dataIn(const QString &argsLine)
{ {
genCmds.sort(Qt::CaseInsensitive); genCmds.sort(Qt::CaseInsensitive);
txtOut << "" << endl; txtOut << "" << Qt::endl;
} }
for (int i = 0; i < genCmds.size(); ++i) for (int i = 0; i < genCmds.size(); ++i)
{ {
if (i != 0) txtOut << " " << genCmds[i] << endl; if (i != 0) txtOut << " " << genCmds[i] << Qt::endl;
else txtOut << genCmds[i] << endl; else txtOut << genCmds[i] << Qt::endl;
} }
} }

View File

@ -138,25 +138,9 @@ QByteArray wrFrame(quint16 cmdId, const QByteArray &data, uchar dType)
return typeBa + cmdBa + branBa + sizeBa + data; return typeBa + cmdBa + branBa + sizeBa + data;
} }
QByteArray toTEXT(const QString &txt) QByteArray toFixedTEXT(const QString &txt, int len)
{ {
QByteArray ret = QTextCodec::codecForName(TXT_CODEC)->fromUnicode(txt); return txt.toUtf8().leftJustified(len, 0, true);
return ret.mid(2);
}
QByteArray fixedToTEXT(const QString &txt, int len)
{
return toTEXT(txt).leftJustified(len, 0, true);
}
QString fromTEXT(const QByteArray &txt)
{
QByteArray ba = txt;
ba.replace(QByteArray(2, 0x00), QByteArray());
return QTextCodec::codecForName(TXT_CODEC)->toUnicode(ba);
} }
QByteArray wrInt(quint64 num, int numOfBits) QByteArray wrInt(quint64 num, int numOfBits)
@ -211,10 +195,11 @@ QStringList parseArgs(const QByteArray &data, int maxArgs)
{ {
QStringList ret; QStringList ret;
QString arg; QString arg;
QString line = fromTEXT(data);
bool inDQuotes = false; auto line = QString(data);
bool inSQuotes = false; auto inDQuotes = false;
bool escaped = false; auto inSQuotes = false;
auto escaped = false;
for (int i = 0; i < line.size(); ++i) for (int i = 0; i < line.size(); ++i)
{ {
@ -280,7 +265,7 @@ QStringList parseArgs(const QByteArray &data, int maxArgs)
QStringList parseArgs(const QString &line) QStringList parseArgs(const QString &line)
{ {
return parseArgs(toTEXT(line), -1); return parseArgs(line.toUtf8(), -1);
} }
void wordWrap(const QString &label, QTextStream &txtOut, const QString &txtIn, QWidget *measureWid) void wordWrap(const QString &label, QTextStream &txtOut, const QString &txtIn, QWidget *measureWid)
@ -313,8 +298,8 @@ void wordWrap(const QString &label, QTextStream &txtOut, const QString &txtIn, Q
line.chop(1); line.chop(1);
if (labelWr) txtOut << label << line << endl; if (labelWr) txtOut << label << line << Qt::endl;
else txtOut << indent << line << endl; else txtOut << indent << line << Qt::endl;
labelWr = false; labelWr = false;
} }
@ -325,8 +310,8 @@ void wordWrap(const QString &label, QTextStream &txtOut, const QString &txtIn, Q
// the case, the line is added to the return and then // the case, the line is added to the return and then
// cleared. // cleared.
if (labelWr) txtOut << label << line << endl; if (labelWr) txtOut << label << line << Qt::endl;
else txtOut << indent << line << endl; else txtOut << indent << line << Qt::endl;
line.clear(); line.clear();

View File

@ -71,12 +71,11 @@
#define DEFAULT_MAX_LINES 1000 #define DEFAULT_MAX_LINES 1000
#define RDBUFF 16777215 #define RDBUFF 16777215
#define DEFAULT_PORT 35516 #define DEFAULT_PORT 35516
#define TXT_CODEC "UTF-16LE"
#define BOOKMARK_FOLDER "bookmarks" #define BOOKMARK_FOLDER "bookmarks"
#define CONFIG_FILENAME "config_v3.json" #define CONFIG_FILENAME "config_v3.json"
#define APP_NAME "Cmdr" #define APP_NAME "Cmdr"
#define APP_TARGET "cmdr" #define APP_TARGET "cmdr"
#define APP_VERSION "3.3" #define APP_VERSION "3.4"
enum TypeID : quint8 enum TypeID : quint8
{ {
@ -168,11 +167,9 @@ void wordWrap(const QString &label, QTextStream &txtOut, const QString &t
bool argExists(const QString &key, const QStringList &args); bool argExists(const QString &key, const QStringList &args);
QByteArray wrInt(quint64 num, int numOfBits); QByteArray wrInt(quint64 num, int numOfBits);
QByteArray wrFrame(quint16 cmdId, const QByteArray &data, uchar dType); QByteArray wrFrame(quint16 cmdId, const QByteArray &data, uchar dType);
QByteArray toTEXT(const QString &txt); QByteArray toFixedTEXT(const QString &txt, int len);
QByteArray fixedToTEXT(const QString &txt, int len);
QStringList parseArgs(const QByteArray &data, int maxArgs); QStringList parseArgs(const QByteArray &data, int maxArgs);
QStringList parseArgs(const QString &line); QStringList parseArgs(const QString &line);
QString fromTEXT(const QByteArray &txt);
QString appDataDir(); QString appDataDir();
QString getParam(const QString &key, const QStringList &args); QString getParam(const QString &key, const QStringList &args);
QString extractCmdName(const QByteArray &data); QString extractCmdName(const QByteArray &data);

View File

@ -269,7 +269,7 @@ QByteArray Genfile::autoFill(const QByteArray &data)
args.append("-len"); args.append("-len");
args.append(len); args.append(len);
return toTEXT(args.join(' ')); return args.join(' ').toUtf8();
} }
else else
{ {
@ -372,7 +372,7 @@ void Genfile::dataIn(const QByteArray &data)
} }
else if (flags & CONFIRM_NEEDED) else if (flags & CONFIRM_NEEDED)
{ {
QString ans = fromTEXT(data); auto ans = QString(data);
if (QRegExp("y", Qt::CaseInsensitive).exactMatch(ans)) if (QRegExp("y", Qt::CaseInsensitive).exactMatch(ans))
{ {

View File

@ -83,7 +83,7 @@ void Session::sockerr(QAbstractSocket::SocketError err)
} }
else else
{ {
if (sslErrors().isEmpty()) if (sslHandshakeErrors().isEmpty())
{ {
// a non-empty sslErrors() can assume the errors were already displayed // a non-empty sslErrors() can assume the errors were already displayed
// by the sslErrs() slot so this conditional block is here to prevent // by the sslErrs() slot so this conditional block is here to prevent
@ -128,11 +128,12 @@ void Session::sslErrs(const QList<QSslError> &errors)
void Session::isConnected() void Session::isConnected()
{ {
// client header format: [4bytes(tag)][134bytes(appName)][272bytes(padding)] // client header format: [4bytes(tag)][32bytes(appName)][128bytes(modInst)][128bytes(padding)]
// tag = 0x4D, 0x52, 0x43, 0x49 (MRCI) // tag = 0x4D, 0x52, 0x43, 0x49 (MRCI)
// appName = UTF16LE string (padded with 0x00) // appName = UTF8 string (padded with 0x00)
// padding = just a string of 0x00 (reserved for future expansion) // modInst = UTF8 string (padded with 0x00)
// padding = 128 bytes of (0x00)
cacheTxt(TEXT, "Connected.\n"); cacheTxt(TEXT, "Connected.\n");
@ -141,19 +142,11 @@ void Session::isConnected()
QByteArray header; QByteArray header;
header.append(SERVER_HEADER_TAG); header.append(SERVER_HEADER_TAG);
header.append(fixedToTEXT(appName, 134)); header.append(toFixedTEXT(appName, 32));
header.append(QByteArray(272, 0)); header.append(QByteArray(256, 0));
if (header.size() == CLIENT_HEADER_LEN)
{
write(header); write(header);
} }
else
{
cacheTxt(ERR, "\nerr: client bug! - header len not equal to " + QString::number(CLIENT_HEADER_LEN) + " bytes.\n");
disconnectFromHost();
}
}
void Session::handShakeDone() void Session::handShakeDone()
{ {
@ -162,13 +155,13 @@ void Session::handShakeDone()
QString txt; QString txt;
QTextStream txtOut(&txt); QTextStream txtOut(&txt);
txtOut << "SSL Handshake sucessful." << endl txtOut << "SSL Handshake sucessful." << Qt::endl
<< "cipher details:" << endl << "cipher details:" << Qt::endl
<< " #cipher - " << cipher.name() << endl << " #cipher - " << cipher.name() << Qt::endl
<< " #protocol - " << cipher.protocolString() << endl << " #protocol - " << cipher.protocolString() << Qt::endl
<< " #bit_depth - " << cipher.usedBits() << endl << " #bit_depth - " << cipher.usedBits() << Qt::endl
<< " #key_exchange - " << cipher.keyExchangeMethod() << endl << " #key_exchange - " << cipher.keyExchangeMethod() << Qt::endl
<< " #authentication - " << cipher.authenticationMethod() << endl; << " #authentication - " << cipher.authenticationMethod() << Qt::endl;
cacheTxt(TEXT, txt); cacheTxt(TEXT, txt);
} }
@ -276,21 +269,21 @@ void Session::procAsync(const QByteArray &data)
{ {
if (dType == TEXT) if (dType == TEXT)
{ {
cacheTxt(dType, "\ncast_text: " + fromTEXT(data) + "\n"); cacheTxt(dType, "\ncast_text: " + QString(data) + "\n");
} }
else if (dType == BIG_TEXT) else if (dType == BIG_TEXT)
{ {
QString txt; QString txt;
QTextStream stream(&txt); QTextStream stream(&txt);
wordWrap("cast_text: ", stream, fromTEXT(data), Shared::mainWidget); wordWrap("cast_text: ", stream, QString(data), Shared::mainWidget);
cacheTxt(TEXT, "\n"); cacheTxt(TEXT, "\n");
cacheTxt(TEXT, txt); cacheTxt(TEXT, txt);
} }
} }
else if (cmdId == ASYNC_RDY) else if (cmdId == ASYNC_RDY)
{ {
cacheTxt(dType, fromTEXT(data)); cacheTxt(dType, QString(data));
hook = 0; hook = 0;
} }
@ -386,7 +379,7 @@ void Session::dataFromHost(const QByteArray &data)
{ {
if ((dType == TEXT) || (dType == PRIV_TEXT) || (dType == BIG_TEXT) || (dType == ERR) || (dType == PROMPT_TEXT)) if ((dType == TEXT) || (dType == PRIV_TEXT) || (dType == BIG_TEXT) || (dType == ERR) || (dType == PROMPT_TEXT))
{ {
cacheTxt(dType, fromTEXT(data)); cacheTxt(dType, QString(data));
} }
else if (dType == PROG) else if (dType == PROG)
{ {
@ -466,7 +459,7 @@ void Session::dataIn()
cacheTxt(TEXT, "Detected host version: " + verText() + "\n"); cacheTxt(TEXT, "Detected host version: " + verText() + "\n");
if (*Shared::tcpRev == 1) if (*Shared::tcpRev == 2)
{ {
*Shared::sessionId = servHeader.mid(9, 28); *Shared::sessionId = servHeader.mid(9, 28);
*Shared::connectedToHost = true; *Shared::connectedToHost = true;
@ -478,6 +471,7 @@ void Session::dataIn()
if (reply == 2) if (reply == 2)
{ {
cacheTxt(TEXT, "Starting SSL handshake.\n"); cacheTxt(TEXT, "Starting SSL handshake.\n");
startClientEncryption(); startClientEncryption();
} }
else else

View File

@ -34,6 +34,7 @@
#include <QSslCipher> #include <QSslCipher>
#include <QSslCertificate> #include <QSslCertificate>
#include <QSocketNotifier> #include <QSocketNotifier>
#include <QSslConfiguration>
#include <QTimer> #include <QTimer>
#include "cmd_objs/command.h" #include "cmd_objs/command.h"
@ -41,7 +42,6 @@
#include "cmd_objs/host_doc.h" #include "cmd_objs/host_doc.h"
#define SERVER_HEADER_TAG "MRCI" #define SERVER_HEADER_TAG "MRCI"
#define CLIENT_HEADER_LEN 410
#define SERVER_HEADER_LEN 37 #define SERVER_HEADER_LEN 37
#define FRAME_HEADER_LEN 8 #define FRAME_HEADER_LEN 8

View File

@ -0,0 +1,9 @@
[Desktop Entry]
Type=Application
Exec=/usr/bin/$app_target
Name=$app_name
GenericName=Terminal emulator for MRCI host.
Icon=$app_target
StartupWMClass=$app_target
Terminal=false
Categories=Network;Terminal;MRCI;

View File

@ -0,0 +1,5 @@
#!/bin/sh
export QTDIR=$install_dir
export QT_PLUGIN_PATH=$install_dir
export LD_LIBRARY_PATH="$install_dir/lib:\$LD_LIBRARY_PATH"
$install_dir/$app_target $1 $2 $3

View File

@ -0,0 +1,21 @@
#!/bin/sh
rm -v /usr/bin/$app_target
rm -rv $install_dir
rm -v /usr/share/icons/hicolor/8x8/apps/$app_target.png
rm -v /usr/share/icons/hicolor/16x16/apps/$app_target.png
rm -v /usr/share/icons/hicolor/22x22/apps/$app_target.png
rm -v /usr/share/icons/hicolor/24x24/apps/$app_target.png
rm -v /usr/share/icons/hicolor/32x32/apps/$app_target.png
rm -v /usr/share/icons/hicolor/36x36/apps/$app_target.png
rm -v /usr/share/icons/hicolor/42x42/apps/$app_target.png
rm -v /usr/share/icons/hicolor/48x48/apps/$app_target.png
rm -v /usr/share/icons/hicolor/64x64/apps/$app_target.png
rm -v /usr/share/icons/hicolor/72x72/apps/$app_target.png
rm -v /usr/share/icons/hicolor/96x96/apps/$app_target.png
rm -v /usr/share/icons/hicolor/128x128/apps/$app_target.png
rm -v /usr/share/icons/hicolor/192x192/apps/$app_target.png
rm -v /usr/share/icons/hicolor/256x256/apps/$app_target.png
rm -v /usr/share/icons/hicolor/512x512/apps/$app_target.png
rm -v /usr/share/icons/hicolor/scalable/apps/$app_target.svg
rm -v /usr/share/applications/$app_target.desktop
echo "Uninstallation Complete"

6
templates/win_icon.vbs Normal file
View File

@ -0,0 +1,6 @@
set WshShell = CreateObject("Wscript.shell")
set oMyShortcut = WshShell.CreateShortcut("%SystemDrive%\ProgramData\Microsoft\Windows\Start Menu\Programs\$app_name.lnk")
oMyShortcut.IconLocation = "$install_dir\icon.ico"
OMyShortcut.TargetPath = "$install_dir\$app_target.exe"
oMyShortCut.Save

View File

@ -0,0 +1,6 @@
$app_target -stop
schtasks /delete /f /tn $app_name
del /f "%windir%\$app_target.exe"
del /f "%SystemDrive%\ProgramData\Microsoft\Windows\Start Menu\Programs\$app_name.lnk"
rd /q /s "$install_dir"
echo "Uninstallation Complete"