Skip to content

Commit

Permalink
chore: deno and jsr
Browse files Browse the repository at this point in the history
  • Loading branch information
c43721 authored Oct 27, 2024
2 parents 503a136 + c5bb2aa commit 63155f8
Show file tree
Hide file tree
Showing 10 changed files with 294 additions and 164 deletions.
12 changes: 12 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
# Contributing

Please make a Github issue before making any pull requests.

# Bugs

Any bugs should be attached to a Github issue.

# Style

Please follow the existing styling of the repository, and include documentation
on new code.
27 changes: 26 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,28 @@
# rcon
# Deno Source RCON Protocol

Complete implementation of the
[Source RCON Protocol](https://developer.valvesoftware.com/wiki/Source_RCON_Protocol).

## Install

Checkout the [jsr page](https://jsr.io/@c43721/rcon) for more details.

### Examples

For more examples, see the [documentation on jsr](https://jsr.io/@c43721/rcon/doc).

## Contributing

If there's a feature or bug, please raise a github issue first alongside your PR
(if you're kind enough to make a PR.)

## Acknowledgements

- EnriqCG's [rcon-srcds](https://github.com/EnriqCG/rcon-srcds)
- ribizli's [deno_rcon](https://github.com/ribizli/deno_rcon)

Both of these repositories I've contributed to in the past and am super thankful for their work.

## License

Distributed under the MIT License. See [LICENSE](LICENSE) for more information.
34 changes: 34 additions & 0 deletions cli.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { parseArgs } from "@std/cli";
import Rcon from "./src/rcon.ts";

const args = parseArgs(Deno.args, {
string: ["password", "ip", "port", "command"],
boolean: ["console", "file"],
});

if (!args.password || !args.ip || !args.command) {
console.error("Must specify a password, ip and command");
} else {
const port = parseInt(args.port ?? "27015", 10);

using rcon = new Rcon({
host: args.ip,
port,
});

const didAuthenticate = await rcon.authenticate(args.password!);

if (didAuthenticate) {
const result = await rcon.execute(args.command!);

if (args.file) {
Deno.writeTextFile(`${Deno.cwd()}/rcon-result.txt`, result.toString(), {
append: true,
});
} else if (args.console) {
console.log(result);
}
} else {
console.error("RCON password incorrect");
}
}
2 changes: 2 additions & 0 deletions deno.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
{
"name": "@c43721/rcon",
"imports": {
"@std/bytes": "jsr:@std/bytes@^1.0.2",
"@std/cli": "jsr:@std/cli@^1.0.6",
"@std/io": "jsr:@std/io@^0.225.0"
},
"version": "0.0.1-alpha1",
Expand Down
47 changes: 25 additions & 22 deletions deno.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

15 changes: 14 additions & 1 deletion src/errors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,23 @@ export class NotAuthorizedException extends Error {
}
}

export class NotConnectedException extends Error {
constructor() {
super();
this.message = "Not connected";
}
}

export class UnableToParseResponseException extends Error {
constructor() {
super();
this.message = "Unable to parse response";
}
}

export class PacketSizeTooBigException extends Error {
constructor() {
super();
this.message = "Packet size too big";
}
}

49 changes: 26 additions & 23 deletions src/packet.ts
Original file line number Diff line number Diff line change
@@ -1,24 +1,25 @@
import { Buffer } from "node:buffer";

/**
* Encode data to packet buffer
* Encodes data to packet buffer
* @param type Packet Type
* @param id Packet ID
* @param body Packet body (payload)
* @param encoding Body encoding
* @returns Encoded packet buffer
*/
export const encode = (type: number, id: number, body: string): Buffer => {
const size = Buffer.byteLength(body) + 14; // body size + 10 + 4 (Null)
const buffer = Buffer.alloc(size);
export const encode = (type: number, id: number, body: string): Uint8Array => {
const dataBuffer = new TextEncoder().encode(body);
const dataLength = dataBuffer.length;

const sendBuffer = new Uint8Array(dataLength + 14);
const view = new DataView(sendBuffer.buffer);

buffer.writeInt32LE(size - 4, 0);
buffer.writeInt32LE(id, 4);
buffer.writeInt32LE(type, 8);
buffer.write(body, 12, size - 2);
buffer.writeInt16LE(0, size - 2);
view.setInt32(0, dataLength + 10, true);
view.setInt32(4, id, true);
view.setInt32(8, type, true);
// set the text data
sendBuffer.set(dataBuffer, 12);
view.setInt16(dataLength + 12, 0, true);

return buffer;
return sendBuffer;
};

/**
Expand All @@ -27,15 +28,19 @@ export const encode = (type: number, id: number, body: string): Buffer => {
* @param encoding Body encoding
* @returns Decoded packet object
*/
export const decode = (
buf: Buffer,
encoding: EncodingOptions = "ascii"
): DecodedPacket => {
export const decode = (data: Uint8Array): DecodedPacket => {
const dataView = new DataView(data.buffer);

const size = dataView.getInt32(0, true);
const id = dataView.getInt32(4, true);
const type = dataView.getInt32(8, true);
const payload = data.slice(12, 12 + size - 10);

return {
size: buf.readInt32LE(0),
id: buf.readInt32LE(4),
type: buf.readInt32LE(8),
body: buf.toString(encoding, 12, buf.byteLength - 2),
size,
id,
type,
body: new TextDecoder().decode(payload),
};
};

Expand All @@ -45,5 +50,3 @@ interface DecodedPacket {
type: number;
body: string;
}

export type EncodingOptions = "ascii" | "utf8";
28 changes: 18 additions & 10 deletions src/protocol.ts
Original file line number Diff line number Diff line change
@@ -1,36 +1,44 @@
/**
* RCON Packet Types
* Reference: https://developer.valvesoftware.com/wiki/Source_RCON#Requests_and_Responses
* The list of [RCON Packet Types](https://developer.valvesoftware.com/wiki/Source_RCON#Requests_and_Responses)
*
* @readonly
*/
const protocol = {
/**
* First packet for authentication
* Used for authenticating the connection with the server
*/
SERVERDATA_AUTH: 0x03,

/**
* Command issued to the server
* Represents a command issue to the server by a client
*/
SERVERDATA_EXECCOMMAND: 0x02,

/**
* Response of SERVERDATA_AUTH
* @remarks If body is -1, the auth failed
* Notifies the connection's current authentication status
*
* @remarks When sent, the server will respond with an empty {@link SERVERDATA_RESPONSE_VALUE} followed by a {@link SERVERDATA_AUTH_RESPONSE} indicating if the authentication was successful. A value of -1 for the packet id will be set if the authentication failed
*/
SERVERDATA_AUTH_RESPONSE: 0x02,

/**
* Response of SERVERDATA_EXECCOMMAND
* Response to a {@link SERVERDATA_EXECCOMMAND}
*/
SERVERDATA_RESPONSE_VALUE: 0x00,

/**
* The packet id used when issuing {@link SERVERDATA_AUTH} commands
*
* @internal
*/
ID_AUTH: 0x999,

ID_REQUEST: 0x123,

ID_TERM: 0x777,
/**
* The packet id used when working with multipacket responses
*
* @internal
*/
ID_TERM: 0x888,
} as const;

export default protocol;
Loading

0 comments on commit 63155f8

Please sign in to comment.