Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion csgo_gc/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ target_precompile_headers(csgo_gc PRIVATE stdafx.h)
set_target_properties(csgo_gc PROPERTIES PREFIX "")

if (MSVC)
target_compile_options(csgo_gc PRIVATE /W4 /wd4244)
target_compile_options(csgo_gc PRIVATE /W4 /wd4244 /wd4819)
target_compile_definitions(csgo_gc PRIVATE _CRT_SECURE_NO_WARNINGS)
else()
target_compile_options(csgo_gc PRIVATE -Wall -Wextra)
Expand Down
168 changes: 108 additions & 60 deletions csgo_gc/gc_client.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,10 @@ struct RconCommand
std::promise<std::string> response;
};

using RconCommandHolder = std::shared_ptr<RconCommand>;

constexpr size_t SourceRconResponseBodyLimit = 4096 - 10;

template<typename T>
bool TryParseNumber(std::string_view text, T &value)
{
Expand Down Expand Up @@ -241,6 +245,41 @@ std::vector<const CSOEconItem *> SortedInventoryItems(const Inventory &inventory
return items;
}

template<typename HeaderFor, typename LineFor>
std::string BuildLimitedRconLines(size_t lineCount, HeaderFor headerFor, LineFor lineFor)
{
std::vector<std::string> lines;
lines.reserve(lineCount);

size_t lineBytes = 0;
bool truncated = false;

for (size_t i = 0; i < lineCount; i++)
{
std::string line = lineFor(i);
std::string header = headerFor(lines.size() + 1, false);
size_t nextLineBytes = lineBytes + 1 + line.size();
if (header.size() + nextLineBytes > SourceRconResponseBodyLimit)
{
truncated = true;
break;
}

lineBytes = nextLineBytes;
lines.push_back(std::move(line));
}

std::string response = headerFor(lines.size(), truncated);
response.reserve(response.size() + lineBytes);
for (const std::string &line : lines)
{
response.push_back('\n');
response.append(line);
}

return response;
}

std::string Usage(std::string_view usage)
{
std::string response{ "ERR usage: " };
Expand Down Expand Up @@ -302,8 +341,10 @@ std::string ClientGC::RunRconCommand(std::string command)
auto request = std::make_shared<RconCommand>(std::move(command));
std::future<std::string> response = request->response.get_future();

auto holder = new std::shared_ptr<RconCommand>{ request };
PostToGC(GCEvent::RconCommand, 0, &holder, sizeof(holder));
auto holder = std::make_unique<RconCommandHolder>(request);
RconCommandHolder *rawHolder = holder.get();
PostToGC(GCEvent::RconCommand, 0, &rawHolder, sizeof(rawHolder));
holder.release();

if (response.wait_for(std::chrono::seconds{ 5 }) != std::future_status::ready)
{
Expand Down Expand Up @@ -402,13 +443,13 @@ void ClientGC::HandleEvent(GCEvent type, uint64_t id, const std::vector<uint8_t>
break;

case GCEvent::RconCommand:
if (buffer.size() == sizeof(std::shared_ptr<RconCommand> *))
if (buffer.size() == sizeof(RconCommandHolder *))
{
std::shared_ptr<RconCommand> *holder;
memcpy(&holder, buffer.data(), sizeof(holder));
RconCommandHolder *rawHolder;
memcpy(&rawHolder, buffer.data(), sizeof(rawHolder));

std::shared_ptr<RconCommand> request = std::move(*holder);
delete holder;
std::unique_ptr<RconCommandHolder> holder{ rawHolder };
RconCommandHolder request = std::move(*holder);

request->response.set_value(ExecuteRconCommand(request->command));
}
Expand Down Expand Up @@ -444,6 +485,20 @@ const ClientGC::RconCommandDef *ClientGC::RconCommands(size_t &count)
return Commands;
}

std::string ClientGC::RconCommandUsageList()
{
std::ostringstream response;

size_t commandCount = 0;
const RconCommandDef *commands = RconCommands(commandCount);
for (size_t i = 0; i < commandCount; i++)
{
response << (i ? ", " : "") << commands[i].usage;
}

return response.str();
}

std::string ClientGC::ExecuteRconCommand(std::string_view command)
{
RconRequest request;
Expand Down Expand Up @@ -486,17 +541,7 @@ std::string ClientGC::RconHelp(const RconRequest &request)
return Usage("help");
}

std::ostringstream response;
response << "OK commands:";

size_t commandCount = 0;
const RconCommandDef *commands = RconCommands(commandCount);
for (size_t i = 0; i < commandCount; i++)
{
response << (i ? ", " : " ") << commands[i].usage;
}

return response.str();
return "OK commands: " + RconCommandUsageList();
}

std::string ClientGC::RconPing(const RconRequest &request)
Expand Down Expand Up @@ -550,20 +595,18 @@ std::string ClientGC::RconListItems(const RconRequest &request)
std::vector<const CSOEconItem *> items = SortedInventoryItems(m_inventory);
const ItemSchema &schema = m_inventory.GetItemSchema();

std::ostringstream response;
response << "OK count=" << items.size() << " shown=" << std::min<size_t>(limit, items.size());

size_t shown = 0;
for (const CSOEconItem *item : items)
{
if (shown++ >= limit)
{
break;
}
response << "\n" << ItemSummary(schema, *item);
}

return response.str();
size_t maxShown = std::min<size_t>(limit, items.size());
return BuildLimitedRconLines(maxShown,
[&](size_t shown, bool truncated) {
std::ostringstream response;
response << "OK total=" << items.size()
<< " shown=" << shown
<< " truncated=" << (truncated ? 1 : 0);
return response.str();
},
[&](size_t index) {
return ItemSummary(schema, *items[index]);
});
}

std::string ClientGC::RconFindItem(const RconRequest &request)
Expand Down Expand Up @@ -594,20 +637,18 @@ std::string ClientGC::RconFindItem(const RconRequest &request)
}

constexpr size_t MaxShown = 50;
std::ostringstream response;
response << "OK matches=" << matches.size() << " shown=" << std::min(MaxShown, matches.size());

size_t shown = 0;
for (const CSOEconItem *item : matches)
{
if (shown++ >= MaxShown)
{
break;
}
response << "\n" << ItemSummary(schema, *item);
}

return response.str();
size_t maxShown = std::min(MaxShown, matches.size());
return BuildLimitedRconLines(maxShown,
[&](size_t shown, bool truncated) {
std::ostringstream response;
response << "OK total=" << matches.size()
<< " shown=" << shown
<< " truncated=" << (truncated ? 1 : 0);
return response.str();
},
[&](size_t index) {
return ItemSummary(schema, *matches[index]);
});
}

std::string ClientGC::RconItemInfo(const RconRequest &request)
Expand All @@ -630,22 +671,29 @@ std::string ClientGC::RconItemInfo(const RconRequest &request)
}

const ItemSchema &schema = m_inventory.GetItemSchema();
std::ostringstream response;
response << "OK " << ItemSummary(schema, *item)
<< " inventory=" << item->inventory()
<< " origin=" << item->origin()
<< " flags=" << item->flags()
<< " in_use=" << item->in_use()
<< " equipped=" << EquippedSummary(*item)
<< " attributes=" << item->attribute_size();
auto header = [&](size_t shown, bool truncated) {
std::ostringstream response;
response << "OK " << ItemSummary(schema, *item)
<< " inventory=" << item->inventory()
<< " origin=" << item->origin()
<< " flags=" << item->flags()
<< " in_use=" << item->in_use()
<< " equipped=" << EquippedSummary(*item)
<< " attributes=" << item->attribute_size()
<< " attr_shown=" << shown
<< " truncated=" << (truncated ? 1 : 0);
return response.str();
};

for (const CSOEconItemAttribute &attribute : item->attribute())
{
response << "\nattr defindex=" << attribute.def_index()
return BuildLimitedRconLines(static_cast<size_t>(item->attribute_size()),
header,
[&](size_t index) {
const CSOEconItemAttribute &attribute = item->attribute(static_cast<int>(index));
std::ostringstream line;
line << "attr defindex=" << attribute.def_index()
<< " value=" << QuoteRconValue(schema.AttributeString(&attribute));
}

return response.str();
return line.str();
});
}

std::string ClientGC::RconGiveItem(const RconRequest &request)
Expand Down
1 change: 1 addition & 0 deletions csgo_gc/gc_client.h
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ class ClientGC final : public SharedGC
~ClientGC();
uint32_t LocalPlayerMusicKitMVPsForRoundMVPEvent() const;
std::string RunRconCommand(std::string command);
static std::string RconCommandUsageList();

private:
struct RconRequest
Expand Down
2 changes: 1 addition & 1 deletion csgo_gc/gc_shared.h
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ enum class GCEvent
LocalPlayerRoundMVP, // sent to client gc when the local player earns round MVP
SyncLocalPlayerMusicKitState, // sent to client gc from the main thread, buffer contains the local userid
ClientSOCacheUnsubscribe, // sent to server gc when a client disconnects, id contains the steam id
RconCommand, // sent to client gc, buffer contains a RconCommand shared_ptr holder
RconCommand, // sent to client gc, buffer contains an owned RconCommand holder pointer
};

struct EventData
Expand Down
2 changes: 1 addition & 1 deletion csgo_gc/rcon_server.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -536,7 +536,7 @@ std::string RconServer::ExecuteCommand(std::string command)

if (name == "help")
{
return "OK commands: help, ping, status, clients, list_items [limit], find_item <itemid|defindex|text>, item_info <itemid>, give_item <defindex> [count] [key=value...], remove_item <itemid>, refresh_inventory, save_inventory";
return "OK commands: " + ClientGC::RconCommandUsageList();
}

return "ERR no client gc";
Expand Down
17 changes: 17 additions & 0 deletions docs/rcon.md
Original file line number Diff line number Diff line change
Expand Up @@ -73,11 +73,17 @@ OK <message>
ERR <message>
```

Responses are kept within one Source RCON response packet. Commands that can
produce many lines, such as inventory listing or item detail inspection, include
`shown` and `truncated` fields. `truncated=1` means output stopped before all
available lines could fit in the single response packet.

Examples:

```text
OK pong
OK item_ids=1234567890123
OK total=500 shown=48 truncated=1
ERR no client gc
ERR unknown parameter foo
```
Expand Down Expand Up @@ -120,6 +126,9 @@ list_items [limit]

The default limit is `50`; the maximum is `500`.

The response includes `total`, `shown`, and `truncated` fields. If
`truncated=1`, reduce the limit or use `find_item` to narrow the output.

### `find_item`

Finds inventory items by exact item id, exact defindex, display name, or custom
Expand All @@ -131,6 +140,10 @@ Syntax:
find_item <itemid|defindex|text>
```

The response includes `total`, `shown`, and `truncated` fields. At most the
first 50 matches are considered for display, and fewer may be shown if the
single Source RCON packet budget is reached.

### `item_info`

Shows detailed information for one inventory item, including attributes and
Expand All @@ -142,6 +155,10 @@ Syntax:
item_info <itemid>
```

The response includes `attributes`, `attr_shown`, and `truncated` fields. If an
item has unusually many or long attributes, `truncated=1` indicates that some
attribute lines were omitted from the response.

### `give_item`

Creates one or more inventory items and sends live SO create updates to the game.
Expand Down
Loading