Compare commits

..

No commits in common. "main" and "0.1.5+2.0" have entirely different histories.

43 changed files with 931 additions and 2539 deletions

3
.gitattributes vendored Normal file
View File

@ -0,0 +1,3 @@
*.png filter=lfs diff=lfs merge=lfs -text
*.gif filter=lfs diff=lfs merge=lfs -text
*.ico filter=lfs diff=lfs merge=lfs -text

View File

@ -1,6 +0,0 @@
{
"[rust]": {
"editor.defaultFormatter": "rust-lang.rust-analyzer",
"editor.formatOnSave": true
}
}

775
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -12,22 +12,19 @@ homedir = "0.3.4"
native-tls = "0.2.14" native-tls = "0.2.14"
clap = { version = "4.5.36", features = ["derive"] } clap = { version = "4.5.36", features = ["derive"] }
serde = { version = "1.0.219", features = ["serde_derive"] } serde = { version = "1.0.219", features = ["serde_derive"] }
gtk4 = { version = "0.9.6", optional = true } gtk4 = { version = "0.9.6", features = [ "v4_10" ] }
chrono = "0.4.40" chrono = "0.4.40"
serde_default = "0.2.0" serde_default = "0.2.0"
socks = "0.3.4" socks = "0.3.4"
libnotify = { version = "1.0.3", optional = true } libnotify = { version = "1.0.3", optional = true }
notify-rust = { version = "4.11.7", optional = true }
gdk-pixbuf = { version = "0.3.0", optional = true } # DO NOT UPDATE gdk-pixbuf = { version = "0.3.0", optional = true } # DO NOT UPDATE
winapi = { version = "0.3.9", optional = true, features = ["wincon", "winuser"] } winapi = { version = "0.3.9", optional = true, features = ["wincon", "winuser"] }
tungstenite = "0.27.0" tungstenite = "0.27.0"
[build-dependencies]
winresource = { version = "0.1.20", optional = true }
[features] [features]
default = ["gtk"] default = []
gtk = ["dep:gtk4"]
libnotify = ["dep:libnotify", "dep:gdk-pixbuf"] libnotify = ["dep:libnotify", "dep:gdk-pixbuf"]
notify-rust = ["dep:notify-rust"] winapi = ["dep:winapi"]
winapi = ["dep:winapi", "dep:winresource"]
[build-dependencies]
winresource = "0.1.20"

View File

@ -1,2 +0,0 @@
[target.x86_64-pc-windows-gnu]
image = "mglolenstine/gtk4-cross:rust-gtk-latest"

View File

@ -1,42 +1,15 @@
.PHONY: clean install uninstall build_linux build_windows build_all .PHONY: clean install uninstall
TARGETS = \
i686-unknown-linux-gnu \
i686-unknown-linux-musl \
x86_64-unknown-linux-none \
x86_64-unknown-linux-gnu \
x86_64-unknown-linux-musl \
aarch64-unknown-linux-gnu \
aarch64-unknown-linux-musl
install: target/release/bRAC install: target/release/bRAC
mkdir -p ~/.local
mkdir -p ~/.local/bin
mkdir -p ~/.local/share
cp $< ~/.local/bin/bRAC cp $< ~/.local/bin/bRAC
chmod +x ~/.local/bin/bRAC chmod +x ~/.local/bin/bRAC
mkdir ~/.local/share/bRAC -p mkdir ~/.local/share/bRAC -p
cp misc/bRAC.png ~/.local/share/bRAC/icon.png cp misc/bRAC.png ~/.local/share/bRAC/icon.png
./misc/create-desktop.sh > ~/.local/share/applications/ru.themixray.bRAC.desktop cp misc/bRAC.desktop ~/.local/share/applications/ru.themixray.bRAC.desktop
uninstall: uninstall:
rm -rf ~/.config/bRAC ~/.local/share/bRAC rm -rf ~/.config/bRAC ~/.local/share/bRAC
rm -f ~/.local/share/applications/ru.themixray.bRAC.desktop rm -f ~/.local/share/applications/ru.themixray.bRAC.desktop
target/release/bRAC: target/release/bRAC:
cargo build -r cargo build -r
build_all: build_linux build_windows
build_linux:
mkdir -p build
mkdir -p build/linux
for target in $(TARGETS); do \
cargo build -r --target $$target; \
cp target/$$target/bRAC build/linux/$$target-bRAC; \
done
build_windows:
echo "Windows build is in development!!!"
clean: clean:
cargo clean cargo clean
rm -rf build

View File

@ -11,15 +11,15 @@ better RAC client
- gtk4 modern GUI - gtk4 modern GUI
- RACv1.99.x and RACv2.0 compatible - RACv1.99.x and RACv2.0 compatible
- WRAC compatible ([docs](docs/wrac.md)) - WRAC compatible (can be enabled in the settings)
- chat commands (type /help) - chat commands (type /help)
- uses tor proxy as default (wracs://meex.lol:11234) - no ip and date visible for anyone
- no ip and date visible for anyone (almost) - uses TOR proxy server by default (meex.lol:11234)
- coloring usernames by their clients (CRAB, clRAC, Mefidroniy, etc.) - coloring usernames by their clients (CRAB, clRAC, Mefidroniy, etc)
- many command-line options (see --help) - many command-line options (--help)
- rich configuration (--config-path to get file path) - rich configuration (--config-path to get file path and --configure to edit)
- RACS/WRACS compatible (ex: wracs://meex.lol) - RACS compatible (--enable-ssl or in --configure enable SSL)
- reading messages chunked (less traffic usage) - chunked reading messages
![screenshot](misc/image.png) ![screenshot](misc/image.png)
@ -39,8 +39,6 @@ better RAC client
NO SOLUTION NO SOLUTION
Read [compiling docs](docs/compiling.md) to build it manually.
### download binary ### download binary
go to [releases](https://github.com/MeexReay/bRAC/releases/latest) and download file you need. its simple. go to [releases](https://github.com/MeexReay/bRAC/releases/latest) and download file you need. its simple.
@ -61,8 +59,6 @@ cargo build -r # build release (target/release/bRAC)
cargo run -r # build and run cargo run -r # build and run
``` ```
Read more about that on the [compiling docs](docs/compiling.md).
### nix package ### nix package
If you have Nix package manager installed, you can use: If you have Nix package manager installed, you can use:
@ -86,21 +82,16 @@ messages starting with a slash are sent to chat only if the `--disable-commands`
## docs ## docs
- [Compiling](docs/compiling.md) - [Message formats](docs/message_formats.md)
- [User agents](docs/user_agents.md)
- [Using as crate](docs/crate.md)
- [Authenticated mode](docs/auth_mode.md) - [Authenticated mode](docs/auth_mode.md)
- [WRAC protocol (v2.0)](docs/wrac.md) - [Cross compile](docs/cross_compile.md)
- [About RAC URL](docs/url.md)
- [FAQ](docs/faq.md) - [FAQ](docs/faq.md)
## see also ## see also
- [Racinfo - webpage with info about RAC](https://racinfo.kostyazero.com/) - [RAC-Hub - all about RAC protocol](https://the-stratosphere-solutions.github.io/RAC-Hub/)
- [RAC-Hub - all about RAC protocol](https://meexreay.github.io/RAC-Hub/)
- [RAC protocol (v2.0)](https://gitea.bedohswe.eu.org/pixtaded/crab#rac-protocol) - [RAC protocol (v2.0)](https://gitea.bedohswe.eu.org/pixtaded/crab#rac-protocol)
- [CRAB - client & server for RAC](https://gitea.bedohswe.eu.org/pixtaded/crab) - [CRAB - client & server for RAC](https://gitea.bedohswe.eu.org/pixtaded/crab)
- [Mefidroniy - TUI client for RAC](https://github.com/OctoBanon-Main/mefedroniy-client) - [Mefidroniy - client for RAC](https://github.com/OctoBanon-Main/mefedroniy-client)
- [cRACk - client for RAC kettles](https://github.com/pansangg/cRACk)
- [AlmatyD - server for RACv1.0](https://gitea.bedohswe.eu.org/bedohswe/almatyd) - [AlmatyD - server for RACv1.0](https://gitea.bedohswe.eu.org/bedohswe/almatyd)
- [RAC protocol (v1.0)](https://bedohswe.eu.org/text/rac/protocol.md.html) - [RAC protocol (v1.0)](https://bedohswe.eu.org/text/rac/protocol.md.html)

View File

@ -1,12 +1,16 @@
use std::io; use {
std::{
env,
io,
},
winresource::WindowsResource,
};
fn main() -> io::Result<()> { fn main() -> io::Result<()> {
#[cfg(feature = "winapi")]
{
use {std::env, winresource::WindowsResource};
if env::var_os("CARGO_CFG_WINDOWS").is_some() { if env::var_os("CARGO_CFG_WINDOWS").is_some() {
WindowsResource::new().set_icon("misc/icon.ico").compile()?; WindowsResource::new()
} .set_icon("misc/icon.ico")
.compile()?;
} }
Ok(()) Ok(())
} }

View File

@ -1,85 +0,0 @@
# How to compile it
## Windows
1. Install [rustup](https://rustup.rs/)
2. Install [MSVC](https://visualstudio.microsoft.com/visual-cpp-build-tools/) and run `rustup default stable-msvc`
3. Extract [GTK4 from gvsbuild](https://github.com/wingtk/gvsbuild/releases/latest) to `C:\gtk`
4. Update environment variables:
- Go to Start, search for 'Advanced system settings' (or click on Properties of My Computer in the Explorer, then you'll find 'Advanced system settings')
- Click 'Environment Variables...'
- Add `C:\gtk\lib\pkgconfig` to the PKG_CONFIG_PATH variable (or create one if doesnt exist)
- Add `C:\gtk\bin` to the PATH variable (or create one if doesnt exist)
- Add `C:\gtk\lib` to the Lib variable (or create one if doesnt exist)
- Apply and close the window (maybe restart PC)
5. Open the repository directory in console (download it from github or with `git clone https://github.com/MeexReay/bRAC.git`)
6. Run `cargo build -r -F winapi,notify-rust`
7. Done! Your finished binary is in the `target/release` folder.
## Linux / MacOS
1. Install `rust`, `openssl-dev`, `gtk4-dev` with your package manager
2. Open the repository directory in console (download it from github or with `git clone https://github.com/MeexReay/bRAC.git`)
3. Run `cargo build -r`
4. Done! Your finished binary is in the `target/release` folder.
# Troubleshooting
## Windows
### Black frame around the window
Black frame appears on connecting to the server or when bRAC just freezes. Be patient.
## MacOS
### Notifications dont work
There are two solutions:
- Switch to `libnotify`:
Add the feature `libnotify` to cargo: `cargo build -r -F libnotify`
- Switch to `notify-rust`:
Add the feature `notify-rust` to cargo: `cargo build -r -F notify-rust`
## Linux
### Notifications dont work
There are two solutions:
- Switch to `libnotify`:
Just add the new feature to cargo: `cargo build -r -F libnotify` \
Libnotify sucks in many situations, but it always work
- Make a desktop file:
Enter the repository folder and run: `./misc/create-desktop.sh` \
You'll get a desktop file contents, just edit paths here and write it to a new file in the `~/.local/share/applications` or `/usr/share/applications`\
All of these, with adding icons and other, makes this command: `make install` (using `gnumake` package) \
But make sure, that you have `.local/bin` in the `PATH` variable, otherwise it won't work. \
Now, if you'll run with the desktop file, GNotifications will work perfectly.
# Cross-compiling
## From Linux to Windows
```bash
./misc/build.sh
```
## From NixOS to Windows
```bash
nix-shell -p rustup gcc cargo-cross zip unzip curl
rustup toolchain install stable
./misc/build.sh
```
## From Windows to Linux
That's your problem

View File

@ -1,65 +0,0 @@
# Using as crate
This article describes how to use the client as rust crate
## Installation
To use exact version:
```toml
[dependencies.bRAC]
git = "https://github.com/MeexReay/bRAC"
tag = "0.1.2+2.0"
default-features = false
```
To use with latest changes:
```toml
[dependencies.bRAC]
git = "https://github.com/MeexReay/bRAC"
default-features = false
```
`default-features = false` here removes GTK4 gui from installation.
## Usage
As the code structure was changed like about gazillion times,
you need to explore it yourself, if you are using an old version.
Here is example of usage on commit [80e7b8c](https://github.com/MeexReay/bRAC/commit/80e7b8c50642f9b76be06980305ed03253858d0c)
```rust
use bRAC::proto::*;
use std::error::Error;
fn main() -> Result<(), Box<dyn Error>> {
let mut conn = connect("wracs://meex.lol", None)?;
// read docs/url.md
// this keep-alive way with only one connection
// works only for WRAC, for a regular RAC,
// you have to connect to the server on each request
send_message(&mut conn, "<dude> hi RAC-loving kikes!")?;
register_user(&mut conn, "dude", "password")?;
send_message_auth(&mut conn, "dude", "password", "my auth message")?;
send_message_spoof_auth(&mut conn, "<dude> this message totally fucks auth system")?;
let (mut all_messages, last_size) = read_messages(&mut conn, 10, 0, false)?.unwrap(); // limits with 10 messages
/* imagine that new messages were written here */
let (mut new_messages, last_size) = read_messages(&mut conn, 10, last_size, true)?.unwrap(); // chunked reading!
all_messages.append(&mut new_messages);
println!("all_messages: {all_messages:?}. last_size: {last_size}");
Ok(())
}
```
## See more
- [rac-rs - A Rust client library for RAC protocol. (with async support)](https://github.com/kostya-zero/rac-rs)

15
docs/cross_compile.md Normal file
View File

@ -0,0 +1,15 @@
# Cross-compile on Linux to Windows
## Install dev packages
on Nix:
```bash
nix-shell -p pkgsCross.mingwW64.stdenv.cc pkgsCross.mingwW64.windows.pthreads pkgsCross.mingwW64.gtk4
```
## Build
```bash
build build/windows-x86_64
```

47
docs/message_formats.md Normal file
View File

@ -0,0 +1,47 @@
# message formats
## types
### bRAC
this client
```yml
format: "리㹰<{name}> {text}"
regex: "\uB9AC\u3E70<(.*?)> (.*)"
color: "green"
```
### CRAB
[CRAB - client & server for RAC written in java](https://gitea.bedohswe.eu.org/pixtaded/crab)
```yml
format: "═══<{name}> {text}"
regex: "\u2550\u2550\u2550<(.*?)> (.*)"
color: "light red"
```
### Mefedroniy
[Mefidroniy - client for RAC written in rust](https://github.com/OctoBanon-Main/mefedroniy-client)
```yml
format: "°ʘ<{name}> {text}"
regex: "\u00B0\u0298<(.*?)> (.*)"
color: "light magenta"
```
### clRAC
official client
```yml
format: "<{name}> {text}"
regex: "<(.*?)> (.*)"
color: "cyan"
```
## developer notes
in auth-mode, there is must to be `> ` after name (`{name}> {text}`)

View File

@ -1,16 +0,0 @@
# How does RAC URL work?
RAC URL is used in sRAC and bRAC as the default way of specifying host, running a RAC or WRAC server.
Format of RAC URL:
```
<protocol>://<address>[:<port>]
```
Protocol can be one of these:
| | **SSL** | **No SSL** |
| :--: | :--: | :--: |
| **WebSocket** | wracs:// | wrac:// |
| **No Websocket** | racs:// | rac:// |

View File

@ -1,20 +0,0 @@
# user agents
User agents in RAC is the way how to get know from what client the message was sent. It works by just checking the message text throught regex.
## clients
Here are listed the most common clients, and their name colors in the chat.
| Client | Format | Regex | Color |
| :----: | :----: | :----: | :----: |
| [bRAC](https://github.com/MeexReay/bRAC) | 리㹰<{name}> {text} | `\uB9AC\u3E70<(.*?)> (.*)` | green
| [CRAB](https://gitea.bedohswe.eu.org/pixtaded/crab) | ═══<{name}> {text} | `\u2550\u2550\u2550<(.*?)> (.*)` | light red
| [Mefidroniy](https://github.com/OctoBanon-Main/mefedroniy-client) | °ʘ<{name}> {text} | `\u00B0\u0298<(.*?)> (.*)` | light magenta
| [cRACk](https://github.com/pansangg/cRACk) | ⁂<{name}> {text} | `\u2042<(.*?)> (.*)` | gold
| [Snowdrop](https://github.com/Forbirdden/Snowdrop) | ඞ<{name}> {text} | `\u0D9E<(.*?)> (.*)` | light green
| clRAC | <{name}> {text} | `<(.*?)> (.*)` | cyan
## developer notes
in auth-mode, there is must to be `> ` after name (`{name}> {text}`)

View File

@ -1,84 +0,0 @@
# WRACv2.0 Protocol
Uses websocket for connections, and sends binary data only (works in packet-way manner)
Totally inherits all packets of RACv2, except of reading messages
## Sending messages
Client sends:
- Byte `0x01`
- Message text
## Sending authorized messages
Client sends:
- Byte `0x02`
- Username
- `\n`
- Password
- `\n`
- Message
Server sends:
- nothing if message was sent successfully
- `0x01` if the user does not exists
- `0x02` if the password is incorrect
## Registration users
Client sends:
- Byte `0x03`
- Username
- `\n`
- Password
Server sends:
- nothing if user was registered successfully
- `0x01` if the username is already taken
## Reading messages
### Getting message length
Client sends:
- Byte `0x00`
Server sends:
- Size of all messages in ASCII (data_size)
### Normal reading
This packet is independent from getting message length packet.
Client sends:
- Byte `0x00`
- Byte `0x01`
Server sends:
- All messages
### Chunked reading
This packet is independent from getting message length packet.
Client sends:
- Byte `0x00`
- Byte `0x02`
- Size of messages you have in ASCII (last_size)
Server sends:
- All new messages
*for example: if you want to read last N bytes, last_size = data_size - N*

View File

@ -1,23 +0,0 @@
use bRAC::proto::*;
use std::error::Error;
fn main() -> Result<(), Box<dyn Error>> {
let mut conn = connect("rac://meex.lol", None)?;
send_message(&mut conn, "<dude> hi RAC-loving kikes!")?;
register_user(&mut conn, "dude", "password")?;
send_message_auth(&mut conn, "dude", "password", "my auth message")?;
send_message_spoof_auth(&mut conn, "<dude> this message totally fucks auth system")?;
let (mut all_messages, last_size) = read_messages(&mut conn, 10, 0, false)?.unwrap(); // limits with 10 messages
/* imagine that new messages were written here */
let (mut new_messages, last_size) = read_messages(&mut conn, 10, last_size, true)?.unwrap(); // chunked reading!
all_messages.append(&mut new_messages);
println!("all_messages: {all_messages:?}. last_size: {last_size}");
Ok(())
}

12
misc/bRAC.desktop Normal file
View File

@ -0,0 +1,12 @@
[Desktop Entry]
Name=bRAC
Version=0.1.4
Type=Application
Comment=better RAC client
Icon=~/.local/share/bRAC/icon.png
Exec=~/.local/bin/bRAC
Categories=Network;
StartupNotify=true
DBusActivatable=true
Terminal=false
X-GNOME-UsesNotifications=true

Binary file not shown.

Before

Width:  |  Height:  |  Size: 30 KiB

After

Width:  |  Height:  |  Size: 130 B

View File

@ -1,46 +0,0 @@
#!/bin/bash
echo "Run this script only from repository root!"
echo "This script depends on:"
echo " - fact that you are on linux x86_64!"
echo " - zip, unzip, curl. install it with your distro's package manager"
echo " - cross crate. to install it, run this: cargo install cross --git https://github.com/cross-rs/cross"
echo " - docker, so you should run something like this on your distro: sudo systemctl start docker"
read -p "Press enter if you really want to do rm -rf build/"
rm -rf build
mkdir build
build_linux() {
mkdir build/linux-x86_64
mkdir build/linux-x86_64/misc
cargo build -r
cp target/release/bRAC build/linux-x86_64/misc/bRAC-gnotif
cp misc/user-install.sh build/linux-x86_64/install.sh
cp misc/bRAC.png build/linux-x86_64/misc
cp misc/create-desktop.sh build/linux-x86_64/misc
cargo build -r -F libnotify
cp target/release/bRAC build/linux-x86_64
cp README.md build/linux-x86_64
cp LICENSE build/linux-x86_64
zip -r build/bRAC-linux-x86_64.zip build/linux-x86_64
}
build_windows() {
chmod +x misc/mslink.sh
curl -L https://github.com/wingtk/gvsbuild/releases/download/2025.5.0/GTK4_Gvsbuild_2025.5.0_x64.zip -o build/gvsbuild.zip # TODO: make this link auto-update
unzip build/gvsbuild.zip "bin/*" -d build/windows-x86_64
rm build/gvsbuild.zip
cross build --target x86_64-pc-windows-gnu -F notify-rust,winapi -r
cp target/x86_64-pc-windows-gnu/release/bRAC.exe build/windows-x86_64/bin
echo "@echo off" > build/windows-x86_64/start.bat
echo "set \"PATH=%CD%\bin;%PATH%\"" >> build/windows-x86_64/start.bat
echo "start \"\" /B \"bin\bRAC.exe\"" >> build/windows-x86_64/start.bat
./misc/mslink.sh -l "%COMSPEC% /C start start.bat" -o build/windows-x86_64/bRAC.lnk # TODO: fix this lnk
cp README.md build/windows-x86_64
curl https://raw.githubusercontent.com/wingtk/gvsbuild/refs/heads/main/COPYING -o build/windows-x86_64/LICENSE
zip -r build/bRAC-windows-x86_64.zip build/windows-x86_64
}
build_linux
build_windows

View File

@ -1,15 +0,0 @@
#!/bin/bash
version=$(grep -m1 '^version' Cargo.toml | sed -E 's/version *= *"(.*)"/\1/')
echo "[Desktop Entry]"
echo "Name=bRAC"
echo "Version=$version"
echo "Type=Application"
echo "Comment=better RAC client"
echo "Icon=$HOME/.local/share/bRAC/icon.png"
echo "Exec=$HOME/.local/bin/bRAC"
echo "Categories=Network;"
echo "StartupNotify=true"
echo "Terminal=false"
echo "X-GNOME-UsesNotifications=true"

Binary file not shown.

Before

Width:  |  Height:  |  Size: 66 KiB

After

Width:  |  Height:  |  Size: 130 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 61 KiB

After

Width:  |  Height:  |  Size: 130 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

After

Width:  |  Height:  |  Size: 130 B

View File

@ -1,243 +0,0 @@
#!/bin/bash
# mslink: Allow to create Windows Shortcut without the need of Windows
#
# Copyright (C) 2019 Mikaël Le Bohec
#
# This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License along with this program. If not, see http://www.gnu.org/licenses/.
#############################################################################################
# mslink.sh v1.3
#############################################################################################
# Ce script permet de créer un Raccourci Windows (Fichier .LNK)
# Script créé en se basant sur la doc
# http://msdn.microsoft.com/en-us/library/dd871305.aspx
#############################################################################################
OPTIONS=$(getopt -q -n ${0} -o hpl:o:n:w:a:i: -l help,lnk-target:,output-file:,name:,working-dir:,arguments:,icon:,printer-link -- "$@")
eval set -- ${OPTIONS}
IS_PRINTER_LNK=0
while true; do
case "$1" in
-h|--help) HELP=1 ;;
-p|--printer-link) IS_PRINTER_LNK=1 ;;
-l|--lnk-target) LNK_TARGET="$2" ; shift ;;
-o|--output-file) OUTPUT_FILE="$2" ; shift ;;
-n|--name) param_HasName="$2" ; shift ;;
-w|--working-dir) param_HasWorkingDir="$2" ; shift ;;
-a|--arguments) param_HasArguments="$2" ; shift ;;
-i|--icon) param_HasIconLocation="$2" ; shift ;;
--) shift ; break ;;
*) echo "Option inconnue : $1" ; exit 1 ;;
esac
shift
done
if [ $# -ne 0 ]; then
echo "Option(s) inconnue(s) : $@"
exit 1
fi
[ ${#LNK_TARGET} -eq 0 ] || [ ${#OUTPUT_FILE} -eq 0 ] && echo "
Usage :
${0} -l cible_du_fichier_lnk [-n description] [-w working_dir] [-a cmd_args] [-i icon_path] -o mon_fichier.lnk [-p]
Options :
-l, --lnk-target Précise la cible du raccourci
-o, --output-file Enregistre le raccourci dans un fichier
-n, --name Spécifie une description au raccourci
-w, --working-dir Spécifie le répertoire de lancement de la commande
-a, --arguments Spécifie les arguments de la commande lancée
-i, --icon Spécifie le chemin de l'icône
-p, --printer-link Génère un raccourci de type imprimante réseau
" && exit 1
#############################################################################################
# Fonctions
#############################################################################################
function ascii2hex() {
echo $(echo -n ${1} | hexdump -v -e '/1 " x%02x"'|sed s/\ /\\\\/g)
}
function gen_LinkFlags() {
echo '\x'$(printf '%02x' "$((HasLinkTargetIDList + HasName + HasWorkingDir + HasArguments + HasIconLocation))")${LinkFlags_2_3_4}
}
function gen_Data_string() {
ITEM_SIZE=$(printf '%04x' $((${#1})))
echo '\x'${ITEM_SIZE:2:2}'\x'${ITEM_SIZE:0:2}$(ascii2hex ${1})
}
function gen_IDLIST() {
ITEM_SIZE=$(printf '%04x' $((${#1}/4+2)))
echo '\x'${ITEM_SIZE:2:2}'\x'${ITEM_SIZE:0:2}${1}
}
function convert_CLSID_to_DATA() {
echo -n ${1:6:2}${1:4:2}${1:2:2}${1:0:2}${1:11:2}${1:9:2}${1:16:2}${1:14:2}${1:19:4}${1:24:12}|sed s/"\([A-Fa-f0-9][A-Fa-f0-9]\)"/\\\\x\\1/g
}
#############################################################################################
# Variables issues de la documentation officielle de Microsoft
#############################################################################################
HasLinkTargetIDList=0x01
HasName=0x04
HasWorkingDir=0x10
HasArguments=0x20
HasIconLocation=0x40
HeaderSize='\x4c\x00\x00\x00' # HeaderSize
LinkCLSID=$(convert_CLSID_to_DATA "00021401-0000-0000-c000-000000000046") # LinkCLSID
LinkFlags_2_3_4='\x01\x00\x00' # ForceNoLinkInfo
LinkFlags=""
FileAttributes_Directory='\x10\x00\x00\x00' # FILE_ATTRIBUTE_DIRECTORY
FileAttributes_File='\x20\x00\x00\x00' # FILE_ATTRIBUTE_ARCHIVE
CreationTime='\x00\x00\x00\x00\x00\x00\x00\x00'
AccessTime='\x00\x00\x00\x00\x00\x00\x00\x00'
WriteTime='\x00\x00\x00\x00\x00\x00\x00\x00'
FileSize='\x00\x00\x00\x00'
IconIndex='\x00\x00\x00\x00'
ShowCommand='\x01\x00\x00\x00' # SW_SHOWNORMAL
Hotkey='\x00\x00' # No Hotkey
Reserved='\x00\x00' # Valeur non modifiable
Reserved2='\x00\x00\x00\x00' # Valeur non modifiable
Reserved3='\x00\x00\x00\x00' # Valeur non modifiable
TerminalID='\x00\x00' # Valeur non modifiable
CLSID_Computer="20d04fe0-3aea-1069-a2d8-08002b30309d" # Poste de travail
CLSID_Network="208d2c60-3aea-1069-a2d7-08002b30309d" # Favoris réseau
#############################################################################################
# Constantes trouvées à partir de l'analyse de fichiers lnk
#############################################################################################
PREFIX_LOCAL_ROOT='\x2f' # Disque local
PREFIX_FOLDER='\x31\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' # Dossier de fichiers
PREFIX_FILE='\x32\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' # Fichier
PREFIX_NETWORK_ROOT='\xc3\x01\x81' # Racine de serveur de fichiers réseau
PREFIX_NETWORK_PRINTER='\xc3\x02\xc1' # Imprimante réseau
END_OF_STRING='\x00'
#############################################################################################
if [ ! -z "${param_HasName}" ]; then
STRING_DATA=${STRING_DATA}$(gen_Data_string ${param_HasName})
else
HasName=0x00
fi
if [ ! -z "${param_HasWorkingDir}" ]; then
STRING_DATA=${STRING_DATA}$(gen_Data_string ${param_HasWorkingDir})
else
HasWorkingDir=0x00
fi
if [ ! -z "${param_HasArguments}" ]; then
STRING_DATA=${STRING_DATA}$(gen_Data_string ${param_HasArguments})
else
HasArguments=0x00
fi
if [ ! -z "${param_HasIconLocation}" ]; then
STRING_DATA=${STRING_DATA}$(gen_Data_string ${param_HasIconLocation})
else
HasIconLocation=0x00
fi
LinkFlags=$(gen_LinkFlags)
# On retire l'anti-slash final s'il y en a un
LNK_TARGET=${LNK_TARGET%\\}
# On sépare le chemin racine du lien de la cible finale
# On distingue aussi si le lien est de type local ou réseau
# On définie la valeur Item_Data suivant le cas d'un lien réseau ou local
IS_ROOT_LNK=0
IS_NETWORK_LNK=0
if [[ ${LNK_TARGET} == \\\\* ]]; then
IS_NETWORK_LNK=1
PREFIX_ROOT=${PREFIX_NETWORK_ROOT}
Item_Data='\x1f\x58'$(convert_CLSID_to_DATA ${CLSID_Network})
TARGET_ROOT=${LNK_TARGET%\\*}
if [[ ${LNK_TARGET} == \\\\*\\* ]]; then
TARGET_LEAF=${LNK_TARGET##*\\}
fi
if [ ${TARGET_ROOT} == \\ ]; then
TARGET_ROOT=${LNK_TARGET}
fi
else
PREFIX_ROOT=${PREFIX_LOCAL_ROOT}
Item_Data='\x1f\x50'$(convert_CLSID_to_DATA ${CLSID_Computer})
TARGET_ROOT=${LNK_TARGET%%\\*}
if [[ ${LNK_TARGET} == *\\* ]]; then
TARGET_LEAF=${LNK_TARGET#*\\}
fi
[[ ! ${TARGET_ROOT} == *\\ ]] && TARGET_ROOT=${TARGET_ROOT}'\'
fi
if [ ${IS_PRINTER_LNK} -eq 1 ]; then
PREFIX_ROOT=${PREFIX_NETWORK_PRINTER}
TARGET_ROOT=${LNK_TARGET}
IS_ROOT_LNK=1
fi
[ ${#TARGET_LEAF} -eq 0 ] && IS_ROOT_LNK=1
#############################################################################################
# On sélectionne le préfixe qui sera utilisé pour afficher l'icône du raccourci
if [[ ${TARGET_LEAF} == *.??? ]]; then
PREFIX_OF_TARGET=${PREFIX_FILE}
TYPE_TARGET="fichier"
FileAttributes=${FileAttributes_File}
else
PREFIX_OF_TARGET=${PREFIX_FOLDER}
TYPE_TARGET="dossier"
FileAttributes=${FileAttributes_Directory}
fi
# On convertit les valeurs des cibles en binaire
TARGET_ROOT=$(ascii2hex "${TARGET_ROOT}")
TARGET_ROOT=${TARGET_ROOT}$(for i in `seq 1 21`;do echo -n '\x00';done) # Nécessaire à partir de Vista et supérieur sinon le lien est considéré comme vide (je n'ai trouvé nul part d'informations à ce sujet)
TARGET_LEAF=$(ascii2hex "${TARGET_LEAF}")
# On crée l'IDLIST qui représente le cœur du fichier LNK
if [ ${IS_ROOT_LNK} -eq 1 ];then
IDLIST_ITEMS=$(gen_IDLIST ${Item_Data})$(gen_IDLIST ${PREFIX_ROOT}${TARGET_ROOT}${END_OF_STRING})
else
IDLIST_ITEMS=$(gen_IDLIST ${Item_Data})$(gen_IDLIST ${PREFIX_ROOT}${TARGET_ROOT}${END_OF_STRING})$(gen_IDLIST ${PREFIX_OF_TARGET}${TARGET_LEAF}${END_OF_STRING})
fi
IDLIST=$(gen_IDLIST ${IDLIST_ITEMS})
#############################################################################################
if [ ${IS_NETWORK_LNK} -eq 1 ]; then
TYPE_LNK="réseau"
if [ ${IS_PRINTER_LNK} -eq 1 ]; then
TYPE_TARGET="imprimante"
fi
else
TYPE_LNK="local"
fi
echo "Création d'un raccourci de type \""${TYPE_TARGET}" "${TYPE_LNK}"\" avec pour cible "${LNK_TARGET} ${param_HasArguments} 1>&2
echo -ne ${HeaderSize}${LinkCLSID}${LinkFlags}${FileAttributes}${CreationTime}${AccessTime}${WriteTime}${FileSize}${IconIndex}${ShowCommand}${Hotkey}${Reserved}${Reserved2}${Reserved3}${IDLIST}${TerminalID}${STRING_DATA} > "${OUTPUT_FILE}"

View File

@ -1,7 +1,5 @@
#!/bin/bash #!/bin/bash
echo "this script is deprecated, fix it yourself if you wanna to"; exit
if [[ $EUID -ne 0 ]]; then if [[ $EUID -ne 0 ]]; then
echo "This script must be run as root" echo "This script must be run as root"
exit 1 exit 1

View File

@ -1,7 +1,5 @@
#!/bin/bash #!/bin/bash
echo "this script is deprecated, fix it yourself if you wanna to"; exit
if [[ $EUID -ne 0 ]]; then if [[ $EUID -ne 0 ]]; then
echo "This script must be run as root" echo "This script must be run as root"
exit 1 exit 1

View File

@ -1,12 +1,7 @@
#!/bin/bash #!/bin/bash
mkdir -p ~/.local cp bRAC ~/.local/bin/bRAC
mkdir -p ~/.local/bin
mkdir -p ~/.local/share
mkdir -p ~/.local/share/bRAC
cp misc/bRAC-gnotif ~/.local/bin/bRAC
chmod +x ~/.local/bin/bRAC chmod +x ~/.local/bin/bRAC
mkdir ~/.local/share/bRAC -p
cp misc/bRAC.png ~/.local/share/bRAC/icon.png cp misc/bRAC.png ~/.local/share/bRAC/icon.png
./misc/create-desktop.sh > ~/.local/share/applications/ru.themixray.bRAC.desktop cp misc/bRAC.desktop ~/.local/share/applications/ru.themixray.bRAC.desktop

View File

@ -1,88 +1,74 @@
use clap::Parser; use std::str::FromStr;
use serde_default::DefaultFromSerde;
use serde_yml;
use std::{fs, path::PathBuf}; use std::{fs, path::PathBuf};
use serde_yml;
use serde_default::DefaultFromSerde;
use clap::Parser;
const MESSAGE_FORMAT: &str = "\u{B9AC}\u{3E70}<{name}> {text}"; const MESSAGE_FORMAT: &str = "\u{B9AC}\u{3E70}<{name}> {text}";
fn default_true() -> bool { fn default_true() -> bool { true }
true pub fn default_max_messages() -> usize { 200 }
} pub fn default_update_time() -> usize { 50 }
pub fn default_max_messages() -> usize { pub fn default_host() -> String { "meex.lol:11234".to_string() }
200 pub fn default_message_format() -> String { MESSAGE_FORMAT.to_string() }
}
pub fn default_update_time() -> usize {
100
}
pub fn default_oof_update_time() -> usize {
10000
}
pub fn default_konata_size() -> usize {
100
}
pub fn default_host() -> String {
"wracs://meex.lol:11234".to_string()
}
pub fn default_message_format() -> String {
MESSAGE_FORMAT.to_string()
}
#[derive(serde::Serialize, serde::Deserialize, DefaultFromSerde, Clone)] #[derive(serde::Serialize, serde::Deserialize, DefaultFromSerde, Clone)]
pub struct Config { pub struct Config {
#[serde(default = "default_host")] #[serde(default = "default_host")] pub host: String,
pub host: String, #[serde(default)] pub name: Option<String>,
#[serde(default)] #[serde(default = "default_message_format")] pub message_format: String,
pub name: Option<String>, #[serde(default = "default_update_time")] pub update_time: usize,
#[serde(default = "default_message_format")] #[serde(default = "default_max_messages")] pub max_messages: usize,
pub message_format: String, #[serde(default = "default_true")] pub hide_my_ip: bool,
#[serde(default = "default_update_time")] #[serde(default)] pub show_other_ip: bool,
pub update_time: usize, #[serde(default)] pub auth_enabled: bool,
#[serde(default = "default_oof_update_time")] #[serde(default)] pub ssl_enabled: bool,
pub oof_update_time: usize, #[serde(default = "default_true")] pub chunked_enabled: bool,
#[serde(default = "default_max_messages")] #[serde(default = "default_true")] pub formatting_enabled: bool,
pub max_messages: usize, #[serde(default = "default_true")] pub commands_enabled: bool,
#[serde(default = "default_konata_size")] #[serde(default)] pub wrac_enabled: bool,
pub konata_size: usize, #[serde(default)] pub proxy: Option<String>,
#[serde(default)] #[serde(default = "default_true")] pub notifications_enabled: bool,
pub remove_gui_shit: bool, }
#[serde(default = "default_true")]
pub hide_my_ip: bool, pub fn get_config_path() -> PathBuf {
#[serde(default)] let mut config_dir = PathBuf::from_str(".").unwrap();
pub show_other_ip: bool,
#[serde(default = "default_true")] #[cfg(not(target_os = "windows"))]
pub chunked_enabled: bool, if let Some(dir) = {
#[serde(default = "default_true")] let home_dir = {
pub formatting_enabled: bool, use homedir::my_home;
#[serde(default = "default_true")] my_home().ok().flatten()
pub commands_enabled: bool, };
#[serde(default)]
pub proxy: Option<String>, #[cfg(target_os = "linux")]
#[serde(default = "default_true")] let config_dir = {
pub notifications_enabled: bool, let home_dir = home_dir.map(|o| o.join("bRAC"));
#[serde(default)] home_dir.map(|o| o.join(".config"))
pub debug_logs: bool, };
#[cfg(target_os = "macos")]
let config_dir = {
let home_dir = home_dir.map(|o| o.join("bRAC"));
home_dir.map(|o| o.join(".config"))
};
config_dir
} {
config_dir = dir;
} }
#[cfg(target_os = "windows")] #[cfg(target_os = "windows")]
pub fn get_config_path() -> PathBuf { if let Some(dir) = {
use std::env; use std::env;
use std::str::FromStr;
env::var("APPDATA") env::var("APPDATA")
.ok() .ok()
.and_then(|o| Some(PathBuf::from_str(&o).ok()?.join("bRAC"))) .and_then(|o| Some(PathBuf::from_str(&o).ok()?.join("bRAC")))
.unwrap_or("bRAC/config.yml".into()) } {
config_dir = dir;
} }
#[cfg(any(target_os = "macos", target_os = "linux"))] config_dir.join("config.yml")
pub fn get_config_path() -> PathBuf {
use homedir::my_home;
my_home()
.ok()
.flatten()
.map(|o| o.join(".config"))
.map(|o| o.join("bRAC"))
.unwrap_or("bRAC".into())
.join("config.yml")
} }
pub fn load_config(path: PathBuf) -> Config { pub fn load_config(path: PathBuf) -> Config {
@ -119,89 +105,39 @@ pub struct Args {
#[arg(short='s', long, value_name="MESSAGE")] #[arg(short='s', long, value_name="MESSAGE")]
pub send_message: Option<String>, pub send_message: Option<String>,
#[arg(short = 'H', long)] #[arg(short='H', long)] pub host: Option<String>,
pub host: Option<String>, #[arg(short='n', long)] pub name: Option<String>,
#[arg(short = 'n', long)] #[arg(long)] pub message_format: Option<String>,
pub name: Option<String>, #[arg(long)] pub update_time: Option<usize>,
#[arg(long)] #[arg(long)] pub max_messages: Option<usize>,
pub message_format: Option<String>, #[arg(long)] pub hide_my_ip: Option<bool>,
#[arg(long)] #[arg(long)] pub show_other_ip: Option<bool>,
pub update_time: Option<usize>, #[arg(long)] pub auth_enabled:Option <bool>,
#[arg(long)] #[arg(long)] pub ssl_enabled: Option<bool>,
pub oof_update_time: Option<usize>, #[arg(long)] pub chunked_enabled: Option<bool>,
#[arg(long)] #[arg(long)] pub formatting_enabled: Option<bool>,
pub max_messages: Option<usize>, #[arg(long)] pub commands_enabled: Option<bool>,
#[arg(long)] #[arg(long)] pub notifications_enabled: Option<bool>,
pub konata_size: Option<usize>, #[arg(long)] pub wrac_enabled: Option<bool>,
#[arg(long)] #[arg(long)] pub proxy: Option<String>,
pub hide_my_ip: Option<bool>,
#[arg(long)]
pub show_other_ip: Option<bool>,
#[arg(long)]
pub remove_gui_shit: Option<bool>,
#[arg(long)]
pub chunked_enabled: Option<bool>,
#[arg(long)]
pub formatting_enabled: Option<bool>,
#[arg(long)]
pub commands_enabled: Option<bool>,
#[arg(long)]
pub notifications_enabled: Option<bool>,
#[arg(long)]
pub proxy: Option<String>,
#[arg(long)]
pub debug_logs: bool,
} }
impl Args { impl Args {
pub fn patch_config(&self, config: &mut Config) { pub fn patch_config(&self, config: &mut Config) {
if let Some(v) = self.host.clone() { if let Some(v) = self.host.clone() { config.host = v }
config.host = v if let Some(v) = self.name.clone() { config.name = Some(v) }
} if let Some(v) = self.proxy.clone() { config.proxy = Some(v) }
if let Some(v) = self.name.clone() { if let Some(v) = self.message_format.clone() { config.message_format = v }
config.name = Some(v) if let Some(v) = self.update_time { config.update_time = v }
} if let Some(v) = self.max_messages { config.max_messages = v }
if let Some(v) = self.proxy.clone() { if let Some(v) = self.hide_my_ip { config.hide_my_ip = v }
config.proxy = Some(v) if let Some(v) = self.show_other_ip { config.show_other_ip = v }
} if let Some(v) = self.auth_enabled { config.auth_enabled = v }
if let Some(v) = self.message_format.clone() { if let Some(v) = self.ssl_enabled { config.ssl_enabled = v }
config.message_format = v if let Some(v) = self.chunked_enabled { config.chunked_enabled = v }
} if let Some(v) = self.formatting_enabled { config.formatting_enabled = v }
if let Some(v) = self.update_time { if let Some(v) = self.commands_enabled { config.commands_enabled = v }
config.update_time = v if let Some(v) = self.notifications_enabled { config.notifications_enabled = v }
} if let Some(v) = self.wrac_enabled { config.wrac_enabled = v }
if let Some(v) = self.oof_update_time {
config.oof_update_time = v
}
if let Some(v) = self.max_messages {
config.max_messages = v
}
if let Some(v) = self.konata_size {
config.konata_size = v
}
if let Some(v) = self.hide_my_ip {
config.hide_my_ip = v
}
if let Some(v) = self.show_other_ip {
config.show_other_ip = v
}
if let Some(v) = self.remove_gui_shit {
config.remove_gui_shit = v
}
if let Some(v) = self.chunked_enabled {
config.chunked_enabled = v
}
if let Some(v) = self.formatting_enabled {
config.formatting_enabled = v
}
if let Some(v) = self.commands_enabled {
config.commands_enabled = v
}
if let Some(v) = self.notifications_enabled {
config.notifications_enabled = v
}
if self.debug_logs {
config.debug_logs = true
}
} }
} }

View File

@ -1,8 +1,4 @@
use std::sync::{ use std::sync::{atomic::{AtomicUsize, Ordering}, mpsc::Sender, Arc, RwLock};
atomic::{AtomicBool, AtomicUsize, Ordering},
mpsc::Sender,
Arc, RwLock,
};
use rand::random; use rand::random;
@ -11,11 +7,10 @@ use super::config::Config;
pub struct Context { pub struct Context {
pub registered: RwLock<Option<String>>, pub registered: RwLock<Option<String>>,
pub config: RwLock<Config>, pub config: RwLock<Config>,
pub sender: RwLock<Option<Arc<Sender<(Vec<String>, bool)>>>>, pub sender: RwLock<Option<Arc<Sender<(String, bool)>>>>,
pub messages: RwLock<Vec<String>>, pub messages: RwLock<Vec<String>>,
pub packet_size: AtomicUsize, pub packet_size: AtomicUsize,
pub name: RwLock<String>, pub name: RwLock<String>
pub is_focused: AtomicBool,
} }
impl Context { impl Context {
@ -26,13 +21,7 @@ impl Context {
sender: RwLock::new(None), sender: RwLock::new(None),
messages: RwLock::new(Vec::new()), messages: RwLock::new(Vec::new()),
packet_size: AtomicUsize::default(), packet_size: AtomicUsize::default(),
name: RwLock::new( name: RwLock::new(config.name.clone().unwrap_or_else(|| format!("Anon#{:X}", random::<u16>()))),
config
.name
.clone()
.unwrap_or_else(|| format!("Anon#{:X}", random::<u16>())),
),
is_focused: AtomicBool::new(true),
} }
} }
@ -42,10 +31,7 @@ impl Context {
pub fn set_config(&self, config: &Config) { pub fn set_config(&self, config: &Config) {
*self.config.write().unwrap() = config.clone(); *self.config.write().unwrap() = config.clone();
*self.name.write().unwrap() = config *self.name.write().unwrap() = config.name.clone().unwrap_or_else(|| format!("Anon#{:X}", random::<u16>()));
.name
.clone()
.unwrap_or_else(|| format!("Anon#{:X}", random::<u16>()));
*self.registered.write().unwrap() = None; *self.registered.write().unwrap() = None;
*self.messages.write().unwrap() = Vec::new(); *self.messages.write().unwrap() = Vec::new();
self.packet_size.store(0, Ordering::SeqCst); self.packet_size.store(0, Ordering::SeqCst);
@ -63,12 +49,7 @@ impl Context {
self.messages.read().unwrap().clone() self.messages.read().unwrap().clone()
} }
pub fn put_messages_packet( pub fn put_messages_packet(&self, max_length: usize, messages: Vec<String>, packet_size: usize) {
&self,
max_length: usize,
messages: Vec<String>,
packet_size: usize,
) {
self.packet_size.store(packet_size, Ordering::SeqCst); self.packet_size.store(packet_size, Ordering::SeqCst);
let mut messages = messages; let mut messages = messages;
if messages.len() > max_length { if messages.len() > max_length {
@ -77,12 +58,7 @@ impl Context {
*self.messages.write().unwrap() = messages; *self.messages.write().unwrap() = messages;
} }
pub fn add_messages_packet( pub fn add_messages_packet(&self, max_length: usize, messages: Vec<String>, packet_size: usize) {
&self,
max_length: usize,
messages: Vec<String>,
packet_size: usize,
) {
self.packet_size.store(packet_size, Ordering::SeqCst); self.packet_size.store(packet_size, Ordering::SeqCst);
self.add_message(max_length, messages); self.add_message(max_length, messages);
} }
@ -100,7 +76,9 @@ macro_rules! connect_rac {
($ctx:ident) => { ($ctx:ident) => {
&mut connect( &mut connect(
&$ctx.config(|o| o.host.clone()), &$ctx.config(|o| o.host.clone()),
$ctx.config(|o| o.ssl_enabled),
$ctx.config(|o| o.proxy.clone()), $ctx.config(|o| o.proxy.clone()),
$ctx.config(|o| o.wrac_enabled)
)? )?
}; };
} }

File diff suppressed because it is too large Load Diff

Binary file not shown.

Before

Width:  |  Height:  |  Size: 30 KiB

After

Width:  |  Height:  |  Size: 130 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 24 KiB

After

Width:  |  Height:  |  Size: 130 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

After

Width:  |  Height:  |  Size: 130 B

View File

@ -1,26 +1,19 @@
use std::{ use std::{
error::Error, error::Error, sync::Arc, thread, time::{Duration, SystemTime, UNIX_EPOCH}
sync::Arc,
time::{SystemTime, UNIX_EPOCH},
}; };
use crate::connect_rac; use crate::connect_rac;
use super::proto::{ use super::proto::{connect, read_messages, send_message, send_message_spoof_auth, register_user, send_message_auth};
connect, read_messages, register_user, send_message, send_message_auth,
};
use gui::{add_chat_message, clear_chat_messages};
use lazy_static::lazy_static; use lazy_static::lazy_static;
use regex::Regex; use regex::Regex;
use ctx::Context; use ctx::Context;
#[cfg(feature = "gtk")]
pub mod gui;
#[cfg(feature = "gtk")]
pub use gui::run_main_loop; pub use gui::run_main_loop;
#[cfg(feature = "gtk")]
use gui::{add_chat_messages, clear_chat_messages};
const HELP_MESSAGE: &str = "Help message: const HELP_MESSAGE: &str = "Help message:
/help - show help message /help - show help message
@ -38,22 +31,21 @@ lazy_static! {
pub static ref IP_REGEX: Regex = Regex::new(r"\{(.*?)\} (.*)").unwrap(); pub static ref IP_REGEX: Regex = Regex::new(r"\{(.*?)\} (.*)").unwrap();
pub static ref COLORED_USERNAMES: Vec<(Regex, String)> = vec![ pub static ref COLORED_USERNAMES: Vec<(Regex, String)> = vec![
(Regex::new(r"\u{B9AC}\u{3E70}<(.*?)> (.*)").unwrap(), "#70fa7a".to_string()), // bRAC (Regex::new(r"\u{B9AC}\u{3E70}<(.*?)> (.*)").unwrap(), "green".to_string()), // bRAC
(Regex::new(r"\u{2550}\u{2550}\u{2550}<(.*?)> (.*)").unwrap(), "#fa7070".to_string()), // CRAB (Regex::new(r"\u{2550}\u{2550}\u{2550}<(.*?)> (.*)").unwrap(), "red".to_string()), // CRAB
(Regex::new(r"\u{00B0}\u{0298}<(.*?)> (.*)").unwrap(), "#da70fa".to_string()), // Mefidroniy (Regex::new(r"\u{00B0}\u{0298}<(.*?)> (.*)").unwrap(), "magenta".to_string()), // Mefidroniy
(Regex::new(r"\u{2042}<(.*?)> (.*)").unwrap(), "#f8b91b".to_string()), // cRACk (Regex::new(r"<(.*?)> (.*)").unwrap(), "cyan".to_string()), // clRAC
(Regex::new(r"\u{0D9E}<(.*?)> (.*)").unwrap(), "#aeff00".to_string()), // Snowdrop
(Regex::new(r"<(.*?)> (.*)").unwrap(), "#70fadc".to_string()), // clRAC
]; ];
pub static ref SERVER_LIST: Vec<String> = vec![ pub static ref SERVER_LIST: Vec<String> = vec![
"wracs://meex.lol:11234".to_string(),
"rac://meex.lol".to_string(), "rac://meex.lol".to_string(),
"wracs://meex.lol".to_string(), "rac://meex.lol:11234".to_string(),
"rac://91.192.22.20".to_string() "rac://91.192.22.20".to_string()
]; ];
} }
pub mod gui;
pub mod config; pub mod config;
pub mod ctx; pub mod ctx;
@ -63,32 +55,27 @@ pub fn sanitize_text(input: &str) -> String {
cleaned_text.into_owned() cleaned_text.into_owned()
} }
#[cfg(feature = "gtk")]
pub fn add_message(ctx: Arc<Context>, message: &str) -> Result<(), Box<dyn Error>> { pub fn add_message(ctx: Arc<Context>, message: &str) -> Result<(), Box<dyn Error>> {
for i in message.split("\n").map(|o| o.to_string()) { for i in message.split("\n")
.map(|o| o.to_string()) {
print_message(ctx.clone(), i)?; print_message(ctx.clone(), i)?;
} }
Ok(()) Ok(())
} }
#[cfg(feature = "gtk")]
pub fn on_command(ctx: Arc<Context>, command: &str) -> Result<(), Box<dyn Error>> { pub fn on_command(ctx: Arc<Context>, command: &str) -> Result<(), Box<dyn Error>> {
let command = command.trim_start_matches("/"); let command = command.trim_start_matches("/");
let (command, args) = command.split_once(" ").unwrap_or((&command, "")); let (command, args) = command.split_once(" ").unwrap_or((&command, ""));
let args = args.split(" ").collect::<Vec<&str>>(); let args = args.split(" ").collect::<Vec<&str>>();
if command == "clear" { if command == "clear" {
let Some(times) = args.get(0) else { let Some(times) = args.get(0) else { return Ok(()) };
return Ok(());
};
let times = times.parse()?; let times = times.parse()?;
for _ in 0..times { for _ in 0..times {
send_message(connect_rac!(ctx), "\r")?; send_message(connect_rac!(ctx), "\r")?;
} }
} else if command == "spam" { } else if command == "spam" {
let Some(times) = args.get(0) else { let Some(times) = args.get(0) else { return Ok(()) };
return Ok(());
};
let times = times.parse()?; let times = times.parse()?;
let msg = args[1..].join(" "); let msg = args[1..].join(" ");
for _ in 0..times { for _ in 0..times {
@ -99,31 +86,28 @@ pub fn on_command(ctx: Arc<Context>, command: &str) -> Result<(), Box<dyn Error>
} else if command == "register" { } else if command == "register" {
let Some(pass) = args.get(0) else { let Some(pass) = args.get(0) else {
add_message(ctx.clone(), "please provide password as the first argument")?; add_message(ctx.clone(), "please provide password as the first argument")?;
return Ok(()); return Ok(())
}; };
match register_user(connect_rac!(ctx), &ctx.name(), pass) { match register_user(connect_rac!(ctx), &ctx.name(), pass, !ctx.config(|o| o.ssl_enabled)) {
Ok(true) => { Ok(true) => {
add_message(ctx.clone(), "you was registered successfully bro")?; add_message(ctx.clone(), "you was registered successfully bro")?;
*ctx.registered.write().unwrap() = Some(pass.to_string()); *ctx.registered.write().unwrap() = Some(pass.to_string());
} },
Ok(false) => add_message(ctx.clone(), "user with this account already exists bruh")?, Ok(false) => add_message(ctx.clone(), "user with this account already exists bruh")?,
Err(e) => add_message(ctx.clone(), &format!("ERROR while registrationing: {}", e))?, Err(e) => add_message(ctx.clone(), &format!("ERROR while registrationing: {}", e))?
}; };
} else if command == "login" { } else if command == "login" {
let Some(pass) = args.get(0) else { let Some(pass) = args.get(0) else {
add_message(ctx.clone(), "please provide password as the first argument")?; add_message(ctx.clone(), "please provide password as the first argument")?;
return Ok(()); return Ok(())
}; };
add_message(ctx.clone(), "ye bro you was logged in")?; add_message(ctx.clone(), "ye bro you was logged in")?;
*ctx.registered.write().unwrap() = Some(pass.to_string()); *ctx.registered.write().unwrap() = Some(pass.to_string());
} else if command == "ping" { } else if command == "ping" {
let mut before = ctx.packet_size(); let mut before = ctx.packet_size();
let message = format!( let message = format!("Checking ping... {:X}", SystemTime::now().duration_since(UNIX_EPOCH)?.as_millis());
"Checking ping... {:X}",
SystemTime::now().duration_since(UNIX_EPOCH)?.as_millis()
);
send_message(connect_rac!(ctx), &message)?; send_message(connect_rac!(ctx), &message)?;
@ -134,10 +118,9 @@ pub fn on_command(ctx: Arc<Context>, command: &str) -> Result<(), Box<dyn Error>
connect_rac!(ctx), connect_rac!(ctx),
ctx.config(|o| o.max_messages), ctx.config(|o| o.max_messages),
before, before,
ctx.config(|o| o.chunked_enabled), !ctx.config(|o| o.ssl_enabled),
) ctx.config(|o| o.chunked_enabled)
.ok() ).ok().flatten();
.flatten();
if let Some((data, size)) = data { if let Some((data, size)) = data {
if let Some(last) = data.iter().rev().find(|o| o.contains(&message)) { if let Some(last) = data.iter().rev().find(|o| o.contains(&message)) {
@ -152,10 +135,7 @@ pub fn on_command(ctx: Arc<Context>, command: &str) -> Result<(), Box<dyn Error>
} }
} }
add_message( add_message(ctx.clone(), &format!("Ping = {}ms", start.elapsed().unwrap().as_millis()))?;
ctx.clone(),
&format!("Ping = {}ms", start.elapsed().unwrap().as_millis()),
)?;
} else { } else {
add_message(ctx.clone(), "Unknown command bruh")?; add_message(ctx.clone(), "Unknown command bruh")?;
} }
@ -164,8 +144,7 @@ pub fn on_command(ctx: Arc<Context>, command: &str) -> Result<(), Box<dyn Error>
} }
pub fn prepare_message(ctx: Arc<Context>, message: &str) -> String { pub fn prepare_message(ctx: Arc<Context>, message: &str) -> String {
format!( format!("{}{}{}",
"{}{}{}",
if ctx.config(|o| o.hide_my_ip) { if ctx.config(|o| o.hide_my_ip) {
"\r\x07" "\r\x07"
} else { } else {
@ -173,8 +152,14 @@ pub fn prepare_message(ctx: Arc<Context>, message: &str) -> String {
}, },
message, message,
if !ctx.config(|o| o.hide_my_ip) { if !ctx.config(|o| o.hide_my_ip) {
if message.chars().count() < 54 { let spaces = if ctx.config(|o| o.auth_enabled) {
" ".repeat(54 - message.chars().count()) 39
} else {
54
};
if message.chars().count() < spaces {
" ".repeat(spaces-message.chars().count())
} else { } else {
String::new() String::new()
} }
@ -184,14 +169,12 @@ pub fn prepare_message(ctx: Arc<Context>, message: &str) -> String {
) )
} }
#[cfg(feature = "gtk")]
pub fn print_message(ctx: Arc<Context>, message: String) -> Result<(), Box<dyn Error>> { pub fn print_message(ctx: Arc<Context>, message: String) -> Result<(), Box<dyn Error>> {
ctx.add_message(ctx.config(|o| o.max_messages), vec![message.clone()]); ctx.add_message(ctx.config(|o| o.max_messages), vec![message.clone()]);
add_chat_messages(ctx.clone(), vec![message]); add_chat_message(ctx.clone(), message);
Ok(()) Ok(())
} }
#[cfg(feature = "gtk")]
pub fn recv_tick(ctx: Arc<Context>) -> Result<(), Box<dyn Error>> { pub fn recv_tick(ctx: Arc<Context>) -> Result<(), Box<dyn Error>> {
let last_size = ctx.packet_size(); let last_size = ctx.packet_size();
@ -199,36 +182,47 @@ pub fn recv_tick(ctx: Arc<Context>) -> Result<(), Box<dyn Error>> {
connect_rac!(ctx), connect_rac!(ctx),
ctx.config(|o| o.max_messages), ctx.config(|o| o.max_messages),
ctx.packet_size(), ctx.packet_size(),
ctx.config(|o| o.chunked_enabled), !ctx.config(|o| o.ssl_enabled),
ctx.config(|o| o.chunked_enabled)
) { ) {
Ok(Some((messages, size))) => { Ok(Some((messages, size))) => {
if ctx.config(|o| o.chunked_enabled) { if ctx.config(|o| o.chunked_enabled) {
ctx.add_messages_packet(ctx.config(|o| o.max_messages), messages.clone(), size); ctx.add_messages_packet(ctx.config(|o| o.max_messages), messages.clone(), size);
if last_size == 0 { if last_size == 0 {
clear_chat_messages(ctx.clone(), messages); if messages.len() >= 1 {
clear_chat_messages(ctx.clone(), messages[0].clone());
if messages.len() >= 2 {
for msg in &messages[1..] {
add_chat_message(ctx.clone(), msg.clone());
}
}
}
} else { } else {
add_chat_messages(ctx.clone(), messages); for msg in messages {
add_chat_message(ctx.clone(), msg.clone());
}
} }
} else { } else {
ctx.put_messages_packet(ctx.config(|o| o.max_messages), messages.clone(), size); ctx.put_messages_packet(ctx.config(|o| o.max_messages), messages.clone(), size);
clear_chat_messages(ctx.clone(), messages); if messages.len() >= 1 {
clear_chat_messages(ctx.clone(), messages[0].clone());
if messages.len() >= 2 {
for msg in &messages[1..] {
add_chat_message(ctx.clone(), msg.clone());
} }
} }
}
}
},
Err(e) => { Err(e) => {
if ctx.config(|o| o.debug_logs) { println!("Read messages error: {}", e.to_string())
add_chat_messages(
ctx.clone(),
vec![format!("Read messages error: {}", e.to_string())],
);
}
} }
_ => {} _ => {}
} }
thread::sleep(Duration::from_millis(ctx.config(|o| o.update_time) as u64));
Ok(()) Ok(())
} }
#[cfg(feature = "gtk")]
pub fn on_send_message(ctx: Arc<Context>, message: &str) -> Result<(), Box<dyn Error>> { pub fn on_send_message(ctx: Arc<Context>, message: &str) -> Result<(), Box<dyn Error>> {
if message.starts_with("/") && ctx.config(|o| o.commands_enabled) { if message.starts_with("/") && ctx.config(|o| o.commands_enabled) {
on_command(ctx.clone(), &message)?; on_command(ctx.clone(), &message)?;
@ -237,11 +231,13 @@ pub fn on_send_message(ctx: Arc<Context>, message: &str) -> Result<(), Box<dyn E
ctx.clone(), ctx.clone(),
&ctx.config(|o| o.message_format.clone()) &ctx.config(|o| o.message_format.clone())
.replace("{name}", &ctx.name()) .replace("{name}", &ctx.name())
.replace("{text}", &message), .replace("{text}", &message)
); );
if let Some(password) = ctx.registered.read().unwrap().clone() { if let Some(password) = ctx.registered.read().unwrap().clone() {
send_message_auth(connect_rac!(ctx), &ctx.name(), &password, &message)?; send_message_auth(connect_rac!(ctx), &ctx.name(), &password, &message, !ctx.config(|o| o.ssl_enabled))?;
} else if ctx.config(|o| o.auth_enabled) {
send_message_spoof_auth(connect_rac!(ctx), &message, !ctx.config(|o| o.ssl_enabled))?;
} else { } else {
send_message(connect_rac!(ctx), &message)?; send_message(connect_rac!(ctx), &message)?;
} }
@ -252,16 +248,16 @@ pub fn on_send_message(ctx: Arc<Context>, message: &str) -> Result<(), Box<dyn E
pub fn sanitize_message(message: String) -> Option<String> { pub fn sanitize_message(message: String) -> Option<String> {
let message = sanitize_text(&message); let message = sanitize_text(&message);
let message = message.trim().to_string(); let message = message.trim().to_string();
Some(message) Some(message)
} }
/// message -> (date, ip, text, (name, color)) /// message -> (date, ip, text, (name, color))
pub fn parse_message( pub fn parse_message(message: String) -> Option<(String, Option<String>, String, Option<(String, String)>)> {
message: String,
) -> Option<(String, Option<String>, String, Option<(String, String)>)> {
if message.is_empty() { if message.is_empty() {
return None; return None
} }
let date = DATE_REGEX.captures(&message)?; let date = DATE_REGEX.captures(&message)?;
@ -278,10 +274,7 @@ pub fn parse_message(
.to_string(); .to_string();
let (ip, message) = if let Some(message) = IP_REGEX.captures(&message) { let (ip, message) = if let Some(message) = IP_REGEX.captures(&message) {
( (Some(message.get(1)?.as_str().to_string()), message.get(2)?.as_str().to_string())
Some(message.get(1)?.as_str().to_string()),
message.get(2)?.as_str().to_string(),
)
} else { } else {
(None, message) (None, message)
}; };
@ -298,11 +291,7 @@ pub fn parse_message(
pub fn find_username_color(message: &str) -> Option<(String, String, String)> { pub fn find_username_color(message: &str) -> Option<(String, String, String)> {
for (re, color) in COLORED_USERNAMES.iter() { for (re, color) in COLORED_USERNAMES.iter() {
if let Some(captures) = re.captures(message) { if let Some(captures) = re.captures(message) {
return Some(( return Some((captures[1].to_string(), captures[2].to_string(), color.clone()))
captures[1].to_string(),
captures[2].to_string(),
color.clone(),
));
} }
} }
None None

View File

@ -1,6 +1,3 @@
.message-content { color:rgb(255, 255, 255); }
/* Now made with GTK Pango Markup */
/* .message-content { color:rgb(255, 255, 255); }
.message-date { color:rgb(146, 146, 146); } .message-date { color:rgb(146, 146, 146); }
.message-ip { color:rgb(73, 73, 73); } */ .message-ip { color:rgb(73, 73, 73); }

View File

@ -1,6 +1,3 @@
.message-content { color:rgb(0, 0, 0); }
/* Now made with GTK Pango Markup */
/* .message-content { color:rgb(0, 0, 0); }
.message-date { color:rgb(41, 41, 41); } .message-date { color:rgb(41, 41, 41); }
.message-ip { color:rgb(88, 88, 88); } */ .message-ip { color:rgb(88, 88, 88); }

View File

@ -1,3 +1,5 @@
.send-button, .send-text { border-radius: 0; } .send-button, .send-text { border-radius: 0; }
.calendar { .calendar {
transform: scale(0.6); transform: scale(0.6);
@ -14,11 +16,9 @@
font-weight: bold; font-weight: bold;
} }
/* Now made with GTK Pango Markup */ .message-name { font-weight: bold; }
/* .message-name { font-weight: bold; }
.message-name-green { color: #70fa7a; } .message-name-green { color: #70fa7a; }
.message-name-red { color: #fa7070; } .message-name-red { color: #fa7070; }
.message-name-magenta { color: #da70fa; } .message-name-magenta { color: #da70fa; }
.message-name-cyan { color: #70fadc; } */ .message-name-cyan { color: #70fadc; }

View File

@ -1,17 +1,12 @@
use std::sync::Arc; use std::sync::Arc;
use bRAC::chat::{
config::{get_config_path, load_config, Args},
ctx::Context,
};
use bRAC::proto::{connect, read_messages, send_message}; use bRAC::proto::{connect, read_messages, send_message};
use bRAC::chat::{config::{get_config_path, load_config, Args}, ctx::Context, run_main_loop};
use clap::Parser; use clap::Parser;
fn main() { fn main() {
#[cfg(feature = "winapi")] #[cfg(feature = "winapi")]
unsafe { unsafe { winapi::um::wincon::FreeConsole() };
winapi::um::wincon::FreeConsole()
};
let args = Args::parse(); let args = Args::parse();
@ -24,39 +19,37 @@ fn main() {
let mut config = load_config(config_path); let mut config = load_config(config_path);
args.patch_config(&mut config);
if args.read_messages { if args.read_messages {
let mut stream = let mut stream = connect(&config.host, config.ssl_enabled, config.proxy.clone(), config.wrac_enabled).expect("Error reading message");
connect(&config.host, config.proxy.clone()).expect("Error reading message");
print!( print!("{}", read_messages(
"{}", &mut stream,
read_messages(&mut stream, config.max_messages, 0, false) config.max_messages,
.ok() 0,
.flatten() !config.ssl_enabled,
.expect("Error reading messages") false
.0 )
.join("\n") .ok().flatten()
.expect("Error reading messages").0.join("\n")
); );
} }
if let Some(message) = &args.send_message { if let Some(message) = &args.send_message {
let mut stream = let mut stream = connect(&config.host, config.ssl_enabled, config.proxy.clone(), config.wrac_enabled).expect("Error sending message");
connect(&config.host, config.proxy.clone()).expect("Error sending message");
send_message(&mut stream, message).expect("Error sending message"); send_message(
&mut stream,
message
).expect("Error sending message");
} }
if args.send_message.is_some() || args.read_messages { if args.send_message.is_some() || args.read_messages {
return; return;
} }
#[cfg(feature = "gtk")] args.patch_config(&mut config);
{
let ctx = Arc::new(Context::new(&config)); let ctx = Arc::new(Context::new(&config));
use bRAC::chat::run_main_loop;
run_main_loop(ctx.clone()); run_main_loop(ctx.clone());
} }
}

View File

@ -1,14 +1,8 @@
use std::{ use std::{error::Error, fmt::Debug, io::{Read, Write}, net::{TcpStream, ToSocketAddrs}, time::Duration};
error::Error,
fmt::Debug,
io::{Read, Write},
net::{TcpStream, ToSocketAddrs},
time::Duration,
};
use native_tls::{TlsConnector, TlsStream}; use native_tls::{TlsConnector, TlsStream};
use socks::Socks5Stream; use socks::Socks5Stream;
use tungstenite::{client::client_with_config, protocol::WebSocketConfig, WebSocket}; use tungstenite::WebSocket;
pub mod rac; pub mod rac;
pub mod wrac; pub mod wrac;
@ -19,44 +13,28 @@ pub trait Stream: Read + Write + Unpin + Send + Sync + Debug {
} }
impl Stream for TcpStream { impl Stream for TcpStream {
fn set_read_timeout(&self, timeout: Duration) { fn set_read_timeout(&self, timeout: Duration) { let _ = TcpStream::set_read_timeout(&self, Some(timeout)); }
let _ = TcpStream::set_read_timeout(&self, Some(timeout)); fn set_write_timeout(&self, timeout: Duration) { let _ = TcpStream::set_write_timeout(&self, Some(timeout)); }
}
fn set_write_timeout(&self, timeout: Duration) {
let _ = TcpStream::set_write_timeout(&self, Some(timeout));
}
} }
impl Stream for Socks5Stream { impl Stream for Socks5Stream {
fn set_read_timeout(&self, timeout: Duration) { fn set_read_timeout(&self, timeout: Duration) { let _ = TcpStream::set_read_timeout(self.get_ref(), Some(timeout)); }
let _ = TcpStream::set_read_timeout(self.get_ref(), Some(timeout)); fn set_write_timeout(&self, timeout: Duration) { let _ = TcpStream::set_write_timeout(self.get_ref(), Some(timeout)); }
}
fn set_write_timeout(&self, timeout: Duration) {
let _ = TcpStream::set_write_timeout(self.get_ref(), Some(timeout));
}
} }
impl<T: Stream> Stream for TlsStream<T> { impl<T: Stream> Stream for TlsStream<T> {
fn set_read_timeout(&self, timeout: Duration) { fn set_read_timeout(&self, timeout: Duration) { self.get_ref().set_read_timeout(timeout); }
self.get_ref().set_read_timeout(timeout); fn set_write_timeout(&self, timeout: Duration) { self.get_ref().set_write_timeout(timeout); }
}
fn set_write_timeout(&self, timeout: Duration) {
self.get_ref().set_write_timeout(timeout);
}
} }
impl Stream for TlsStream<Box<dyn Stream>> { impl Stream for TlsStream<Box<dyn Stream>> {
fn set_read_timeout(&self, timeout: Duration) { fn set_read_timeout(&self, timeout: Duration) { self.get_ref().set_read_timeout(timeout); }
self.get_ref().set_read_timeout(timeout); fn set_write_timeout(&self, timeout: Duration) { self.get_ref().set_write_timeout(timeout); }
}
fn set_write_timeout(&self, timeout: Duration) {
self.get_ref().set_write_timeout(timeout);
}
} }
pub enum RacStream { pub enum RacStream {
WRAC(WebSocket<Box<dyn Stream>>), WRAC(WebSocket<Box<dyn Stream>>),
RAC(Box<dyn Stream>), RAC(Box<dyn Stream>)
} }
/// `socks5://user:pass@127.0.0.1:12345/path -> ("127.0.0.1:12345", ("user", "pass"))` \ /// `socks5://user:pass@127.0.0.1:12345/path -> ("127.0.0.1:12345", ("user", "pass"))` \
@ -86,42 +64,46 @@ pub fn parse_rac_url(url: &str) -> Option<(String, bool, bool)> {
let (scheme, url) = url.split_once("://").unwrap_or(("rac", url)); let (scheme, url) = url.split_once("://").unwrap_or(("rac", url));
let (host, _) = url.split_once("/").unwrap_or((url, "")); let (host, _) = url.split_once("/").unwrap_or((url, ""));
match scheme.to_lowercase().as_str() { match scheme.to_lowercase().as_str() {
"rac" => Some(( "rac" => {
Some((
if host.contains(":") { if host.contains(":") {
host.to_string() host.to_string()
} else { } else {
format!("{host}:42666") format!("{host}:42666")
}, },
false, false, false
false, ))
)), },
"racs" => Some(( "racs" => {
Some((
if host.contains(":") { if host.contains(":") {
host.to_string() host.to_string()
} else { } else {
format!("{host}:42667") format!("{host}:42667")
}, },
true, true, false
false, ))
)), },
"wrac" => Some(( "wrac" => {
Some((
if host.contains(":") { if host.contains(":") {
host.to_string() host.to_string()
} else { } else {
format!("{host}:52666") format!("{host}:52666")
}, },
false, false, true
true, ))
)), },
"wracs" => Some(( "wracs" => {
Some((
if host.contains(":") { if host.contains(":") {
host.to_string() host.to_string()
} else { } else {
format!("{host}:52667") format!("{host}:52667")
}, },
true, true, true
true, ))
)), },
_ => None, _ => None,
} }
} }
@ -132,19 +114,14 @@ pub fn parse_rac_url(url: &str) -> Option<(String, bool, bool)> {
/// ssl - wrap with ssl client, write false if you dont know what it is /// ssl - wrap with ssl client, write false if you dont know what it is
/// proxy - socks5 proxy (host, (user, pass)) /// proxy - socks5 proxy (host, (user, pass))
/// wrac - to use wrac protocol /// wrac - to use wrac protocol
pub fn connect(host: &str, proxy: Option<String>) -> Result<RacStream, Box<dyn Error>> { pub fn connect(host: &str, ssl: bool, proxy: Option<String>, wrac: bool) -> Result<RacStream, Box<dyn Error>> {
let (host, ssl, wrac) = let (host, ssl_, wrac_) = parse_rac_url(host).ok_or::<Box<dyn Error>>("url parse error".into())?;
parse_rac_url(host).ok_or::<Box<dyn Error>>("url parse error".into())?; let (ssl, wrac) = (ssl_ || ssl, wrac_ || wrac);
let stream: Box<dyn Stream> = if let Some(proxy) = proxy { let stream: Box<dyn Stream> = if let Some(proxy) = proxy {
if let Some((proxy, auth)) = parse_socks5_url(&proxy) { if let Some((proxy, auth)) = parse_socks5_url(&proxy) {
if let Some((user, pass)) = auth { if let Some((user, pass)) = auth {
Box::new(Socks5Stream::connect_with_password( Box::new(Socks5Stream::connect_with_password(&proxy, host.as_str(), &user, &pass)?)
&proxy,
host.as_str(),
&user,
&pass,
)?)
} else { } else {
Box::new(Socks5Stream::connect(&proxy, host.as_str())?) Box::new(Socks5Stream::connect(&proxy, host.as_str())?)
} }
@ -152,39 +129,32 @@ pub fn connect(host: &str, proxy: Option<String>) -> Result<RacStream, Box<dyn E
return Err("proxy parse error".into()); return Err("proxy parse error".into());
} }
} else { } else {
let addr = host let addr = host.to_socket_addrs()?.next().ok_or::<Box<dyn Error>>("addr parse error".into())?;
.to_socket_addrs()?
.next()
.ok_or::<Box<dyn Error>>("addr parse error".into())?;
Box::new(TcpStream::connect(&addr)?) Box::new(TcpStream::connect(&addr)?)
}; };
let stream = if ssl { let stream = if ssl {
let ip: String = host let ip: String = host.split_once(":")
.split_once(":")
.map(|o| o.0.to_string()) .map(|o| o.0.to_string())
.unwrap_or(host.clone()); .unwrap_or(host.clone());
Box::new( Box::new(TlsConnector::builder()
TlsConnector::builder()
.danger_accept_invalid_certs(true) .danger_accept_invalid_certs(true)
.danger_accept_invalid_hostnames(true) .danger_accept_invalid_hostnames(true)
.build()? .build()?
.connect(&ip, stream)?, .connect(&ip, stream)?)
)
} else { } else {
stream stream
}; };
stream.set_read_timeout(Duration::from_secs(15)); // TODO: softcode this stream.set_read_timeout(Duration::from_secs(3));
stream.set_write_timeout(Duration::from_secs(15)); stream.set_write_timeout(Duration::from_secs(3));
if wrac { if wrac {
let (client, _) = client_with_config( let (client, _) = tungstenite::client(
&format!("ws://{host}"), &format!("ws{}://{host}", if ssl { "s" } else { "" }),
stream, stream
Some(WebSocketConfig::default().max_message_size(Some(512 * 1024 * 1024))), // TODO: softcode this
)?; )?;
Ok(RacStream::WRAC(client)) Ok(RacStream::WRAC(client))
} else { } else {
@ -192,14 +162,42 @@ pub fn connect(host: &str, proxy: Option<String>) -> Result<RacStream, Box<dyn E
} }
} }
/// Send message with fake auth
///
/// Explaination:
///
/// let (name, message) = message.split("> ") else { return send_message(stream, message) }
/// if send_message_auth(name, name, message) != 0 {
/// let name = "\x1f" + name
/// register_user(stream, name, name)
/// send_message_spoof_auth(stream, name + "> " + message)
/// }
pub fn send_message_spoof_auth(stream: &mut RacStream, message: &str, remove_null: bool) -> Result<(), Box<dyn Error>> {
let Some((name, message)) = message.split_once("> ") else { return send_message(stream, message) };
if let Ok(f) = send_message_auth(stream, &name, &message, &message, remove_null) {
if f != 0 {
let name = format!("\x1f{name}");
register_user(stream, &name, &name, remove_null)?;
send_message_spoof_auth(stream, &format!("{name}> {message}"), remove_null)?;
}
}
Ok(())
}
/// Send message /// Send message
/// ///
/// stream - any stream that can be written to /// stream - any stream that can be written to
/// message - message text /// message - message text
pub fn send_message(stream: &mut RacStream, message: &str) -> Result<(), Box<dyn Error>> { pub fn send_message(
stream: &mut RacStream,
message: &str
) -> Result<(), Box<dyn Error>> {
match stream { match stream {
RacStream::WRAC(websocket) => wrac::send_message(websocket, message), RacStream::WRAC(websocket) => wrac::send_message(websocket, message),
RacStream::RAC(stream) => rac::send_message(stream, message), RacStream::RAC(stream) => rac::send_message(stream, message)
} }
} }
@ -215,10 +213,11 @@ pub fn register_user(
stream: &mut RacStream, stream: &mut RacStream,
name: &str, name: &str,
password: &str, password: &str,
remove_null: bool
) -> Result<bool, Box<dyn Error>> { ) -> Result<bool, Box<dyn Error>> {
match stream { match stream {
RacStream::WRAC(websocket) => wrac::register_user(websocket, name, password), RacStream::WRAC(websocket) => wrac::register_user(websocket, name, password),
RacStream::RAC(stream) => rac::register_user(stream, name, password), RacStream::RAC(stream) => rac::register_user(stream, name, password, remove_null)
} }
} }
@ -238,10 +237,11 @@ pub fn send_message_auth(
name: &str, name: &str,
password: &str, password: &str,
message: &str, message: &str,
remove_null: bool
) -> Result<u8, Box<dyn Error>> { ) -> Result<u8, Box<dyn Error>> {
match stream { match stream {
RacStream::WRAC(websocket) => wrac::send_message_auth(websocket, name, password, message), RacStream::WRAC(websocket) => wrac::send_message_auth(websocket, name, password, message),
RacStream::RAC(stream) => rac::send_message_auth(stream, name, password, message), RacStream::RAC(stream) => rac::send_message_auth(stream, name, password, message, remove_null)
} }
} }
@ -257,12 +257,11 @@ pub fn read_messages(
stream: &mut RacStream, stream: &mut RacStream,
max_messages: usize, max_messages: usize,
last_size: usize, last_size: usize,
chunked: bool, remove_null: bool,
chunked: bool
) -> Result<Option<(Vec<String>, usize)>, Box<dyn Error>> { ) -> Result<Option<(Vec<String>, usize)>, Box<dyn Error>> {
match stream { match stream {
RacStream::WRAC(websocket) => { RacStream::WRAC(websocket) => wrac::read_messages(websocket, max_messages, last_size, chunked),
wrac::read_messages(websocket, max_messages, last_size, chunked) RacStream::RAC(stream) => rac::read_messages(stream, max_messages, last_size, remove_null, chunked)
}
RacStream::RAC(stream) => rac::read_messages(stream, max_messages, last_size, chunked),
} }
} }

View File

@ -1,7 +1,4 @@
use std::{ use std::{error::Error, io::{Read, Write}};
error::Error,
io::{Read, Write},
};
/// Send message /// Send message
/// ///
@ -24,13 +21,23 @@ pub fn register_user(
stream: &mut (impl Write + Read), stream: &mut (impl Write + Read),
name: &str, name: &str,
password: &str, password: &str,
remove_null: bool
) -> Result<bool, Box<dyn Error>> { ) -> Result<bool, Box<dyn Error>> {
stream.write_all(format!("\x03{name}\n{password}").as_bytes())?; stream.write_all(format!("\x03{name}\n{password}").as_bytes())?;
if remove_null {
if let Ok(out) = skip_null(stream) { if let Ok(out) = skip_null(stream) {
Ok(out[0] == 0) Ok(out[0] == 0)
} else { } else {
Ok(true) Ok(true)
} }
} else {
let mut buf = vec![0];
if let Ok(1) = stream.read(&mut buf) {
Ok(buf[0] == 0)
} else {
Ok(true)
}
}
} }
/// Send message with auth /// Send message with auth
@ -49,13 +56,24 @@ pub fn send_message_auth(
name: &str, name: &str,
password: &str, password: &str,
message: &str, message: &str,
remove_null: bool
) -> Result<u8, Box<dyn Error>> { ) -> Result<u8, Box<dyn Error>> {
stream.write_all(format!("\x02{name}\n{password}\n{message}").as_bytes())?; stream.write_all(format!("\x02{name}\n{password}\n{message}").as_bytes())?;
if remove_null {
if let Ok(out) = skip_null(stream) { if let Ok(out) = skip_null(stream) {
Ok(out[0]) Ok(out[0])
} else { } else {
Ok(0) Ok(0)
} }
} else {
let mut buf = vec![0];
if let Ok(1) = stream.read(&mut buf) {
Ok(buf[0])
} else {
Ok(0)
}
}
} }
/// Skip null bytes and return first non-null byte /// Skip null bytes and return first non-null byte
@ -64,7 +82,7 @@ pub fn skip_null(stream: &mut impl Read) -> Result<Vec<u8>, Box<dyn Error>> {
let mut buf = vec![0; 1]; let mut buf = vec![0; 1];
stream.read_exact(&mut buf)?; stream.read_exact(&mut buf)?;
if buf[0] != 0 { if buf[0] != 0 {
break Ok(buf); break Ok(buf)
} }
} }
} }
@ -89,17 +107,26 @@ pub fn read_messages(
stream: &mut (impl Read + Write), stream: &mut (impl Read + Write),
max_messages: usize, max_messages: usize,
last_size: usize, last_size: usize,
chunked: bool, remove_null: bool,
chunked: bool
) -> Result<Option<(Vec<String>, usize)>, Box<dyn Error>> { ) -> Result<Option<(Vec<String>, usize)>, Box<dyn Error>> {
stream.write_all(&[0x00])?; stream.write_all(&[0x00])?;
let packet_size = { let packet_size = {
let data = if remove_null {
let mut data = skip_null(stream)?; let mut data = skip_null(stream)?;
let mut buf = vec![0; 10]; let mut buf = vec![0; 10];
let len = stream.read(&mut buf)?; let len = stream.read(&mut buf)?;
buf.truncate(len); buf.truncate(len);
data.append(&mut buf); data.append(&mut buf);
remove_trailing_null(&mut data)?; remove_trailing_null(&mut data)?;
data
} else {
let mut data = vec![0; 10];
let len = stream.read(&mut data)?;
data.truncate(len);
data
};
String::from_utf8(data)? String::from_utf8(data)?
.trim_matches(char::from(0)) .trim_matches(char::from(0))
@ -118,22 +145,23 @@ pub fn read_messages(
packet_size - last_size packet_size - last_size
}; };
let mut packet_data = skip_null(stream)?; let packet_data = if remove_null {
let mut data = skip_null(stream)?;
let mut buf = vec![0; to_read - 1]; let mut buf = vec![0; to_read - 1];
stream.read_exact(&mut buf)?; stream.read_exact(&mut buf)?;
packet_data.append(&mut buf); data.append(&mut buf);
data
} else {
let mut data = vec![0; to_read];
stream.read_exact(&mut data)?;
data
};
let packet_data = String::from_utf8_lossy(&packet_data).to_string(); let packet_data = String::from_utf8_lossy(&packet_data).to_string();
let lines: Vec<&str> = packet_data.split("\n").collect(); let lines: Vec<&str> = packet_data.split("\n").collect();
let lines: Vec<String> = lines let lines: Vec<String> = lines.clone().into_iter()
.clone() .skip(if lines.len() >= max_messages { lines.len() - max_messages } else { 0 })
.into_iter()
.skip(if lines.len() >= max_messages {
lines.len() - max_messages
} else {
0
})
.map(|o| o.to_string()) .map(|o| o.to_string())
.collect(); .collect();

View File

@ -1,8 +1,6 @@
use std::{ use std::{error::Error, io::{Read, Write}};
error::Error, use tungstenite::{WebSocket, Message};
io::{Read, Write},
};
use tungstenite::{Message, WebSocket};
/// Send message /// Send message
/// ///
@ -10,11 +8,9 @@ use tungstenite::{Message, WebSocket};
/// message - message text /// message - message text
pub fn send_message( pub fn send_message(
stream: &mut WebSocket<impl Write + Read>, stream: &mut WebSocket<impl Write + Read>,
message: &str, message: &str
) -> Result<(), Box<dyn Error>> { ) -> Result<(), Box<dyn Error>> {
stream.write(Message::Binary( stream.write(Message::Binary(format!("\x01{message}").as_bytes().to_vec().into()))?;
format!("\x01{message}").as_bytes().to_vec().into(),
))?;
stream.flush()?; stream.flush()?;
Ok(()) Ok(())
} }
@ -29,11 +25,9 @@ pub fn send_message(
pub fn register_user( pub fn register_user(
stream: &mut WebSocket<impl Write + Read>, stream: &mut WebSocket<impl Write + Read>,
name: &str, name: &str,
password: &str, password: &str
) -> Result<bool, Box<dyn Error>> { ) -> Result<bool, Box<dyn Error>> {
stream.write(Message::Binary( stream.write(Message::Binary(format!("\x03{name}\n{password}").as_bytes().to_vec().into()))?;
format!("\x03{name}\n{password}").as_bytes().to_vec().into(),
))?;
stream.flush()?; stream.flush()?;
if let Ok(msg) = stream.read() { if let Ok(msg) = stream.read() {
Ok(!msg.is_binary() || msg.into_data().get(0).unwrap_or(&0) == &0) Ok(!msg.is_binary() || msg.into_data().get(0).unwrap_or(&0) == &0)
@ -56,14 +50,9 @@ pub fn send_message_auth(
stream: &mut WebSocket<impl Write + Read>, stream: &mut WebSocket<impl Write + Read>,
name: &str, name: &str,
password: &str, password: &str,
message: &str, message: &str
) -> Result<u8, Box<dyn Error>> { ) -> Result<u8, Box<dyn Error>> {
stream.write(Message::Binary( stream.write(Message::Binary(format!("\x02{name}\n{password}\n{message}").as_bytes().to_vec().into()))?;
format!("\x02{name}\n{password}\n{message}")
.as_bytes()
.to_vec()
.into(),
))?;
stream.flush()?; stream.flush()?;
if let Ok(msg) = stream.read() { if let Ok(msg) = stream.read() {
if msg.is_binary() { if msg.is_binary() {
@ -87,7 +76,7 @@ pub fn read_messages(
stream: &mut WebSocket<impl Write + Read>, stream: &mut WebSocket<impl Write + Read>,
max_messages: usize, max_messages: usize,
last_size: usize, last_size: usize,
chunked: bool, chunked: bool
) -> Result<Option<(Vec<String>, usize)>, Box<dyn Error>> { ) -> Result<Option<(Vec<String>, usize)>, Box<dyn Error>> {
stream.write(Message::Binary(vec![0x00].into()))?; stream.write(Message::Binary(vec![0x00].into()))?;
stream.flush()?; stream.flush()?;
@ -112,9 +101,7 @@ pub fn read_messages(
stream.write(Message::Binary(vec![0x00, 0x01].into()))?; stream.write(Message::Binary(vec![0x00, 0x01].into()))?;
packet_size packet_size
} else { } else {
stream.write(Message::Binary( stream.write(Message::Binary(format!("\x00\x02{}", last_size).as_bytes().to_vec().into()))?;
format!("\x00\x02{}", last_size).as_bytes().to_vec().into(),
))?;
packet_size - last_size packet_size - last_size
}; };
stream.flush()?; stream.flush()?;
@ -132,14 +119,8 @@ pub fn read_messages(
let packet_data = String::from_utf8_lossy(&packet_data).to_string(); let packet_data = String::from_utf8_lossy(&packet_data).to_string();
let lines: Vec<&str> = packet_data.split("\n").collect(); let lines: Vec<&str> = packet_data.split("\n").collect();
let lines: Vec<String> = lines let lines: Vec<String> = lines.clone().into_iter()
.clone() .skip(if lines.len() >= max_messages { lines.len() - max_messages } else { 0 })
.into_iter()
.skip(if lines.len() >= max_messages {
lines.len() - max_messages
} else {
0
})
.map(|o| o.to_string()) .map(|o| o.to_string())
.collect(); .collect();