Conversation
|
Запрошено @cfif126 |
📝 WalkthroughWalkthroughAdds a complete NanoChat PDA cartridge: client UI (fragment, controls, popups, emoji picker), shared networked state/events/components, server-side messaging system (delivery, groups, EMP/power distortion, range, cooldown), forensic chat printing, prototypes, assets, and localization. Changes
Sequence Diagram(s)sequenceDiagram
participant Client
participant NanoChatUi
participant ServerSystem
participant RecipientCartridge
participant RecipientUI
Client->>NanoChatUi: User sends message(recipientId, text)
NanoChatUi->>ServerSystem: Emit NanoChatSendMessage event
activate ServerSystem
ServerSystem->>ServerSystem: Validate cooldown, sanitize, truncate
ServerSystem->>ServerSystem: Check power/solar/EMP -> distort or block
ServerSystem->>ServerSystem: Range check (map + distance)
alt Delivery allowed
ServerSystem->>RecipientCartridge: Append message to history
ServerSystem->>RecipientCartridge: Mark unread, play audio (optional)
ServerSystem->>RecipientUI: Push updated NanoChatUiState
else Distorted/undelivered
ServerSystem->>ServerSystem: Store distorted/undelivered copy in sender history
ServerSystem->>RecipientUI: No delivery
end
ServerSystem->>Client: Push updated NanoChatUiState for sender
deactivate ServerSystem
sequenceDiagram
participant User
participant NanoChatUiFragment
participant ServerSystem
participant GroupMembers
User->>NanoChatUiFragment: CreateGroup(name)
NanoChatUiFragment->>ServerSystem: NanoChatCreateGroup event
activate ServerSystem
ServerSystem->>ServerSystem: Generate groupId, add creator as member
ServerSystem->>GroupMembers: Broadcast group state and system message
deactivate ServerSystem
sequenceDiagram
participant ForensicScanner
participant ForensicSystem
participant PDAContainer
participant Paper
ForensicScanner->>ForensicSystem: Invoke PrintChatHistory(targetPDA)
activate ForensicSystem
ForensicSystem->>PDAContainer: Access program-container and cartridges
ForensicSystem->>ForensicSystem: Aggregate chat/contact/group messages
ForensicSystem->>Paper: Spawn paper, set content to formatted history
ForensicSystem->>ForensicScanner: Show success/failure popup
deactivate ForensicSystem
Estimated Code Review Effort🎯 5 (Critical) | ⏱️ ~120 minutes Suggested Labels
Suggested Reviewers
Poem
🚥 Pre-merge checks | ✅ 1 | ❌ 2❌ Failed checks (1 warning, 1 inconclusive)
✅ Passed checks (1 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 15
🧹 Nitpick comments (4)
Content.Shared/_Wega/CartridgeLoader/Cartridges/NanoChatUiMessageEvent.cs (1)
37-48: Consider documenting expected message length limits.The
Messageproperty accepts arbitrary string content. Ensure that the server-side handler inNanoChatCartridgeSystem.csvalidates message length to prevent abuse (excessively long messages, spam).🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@Content.Shared/_Wega/CartridgeLoader/Cartridges/NanoChatUiMessageEvent.cs` around lines 37 - 48, Add server-side validation for NanoChatSendMessage.Message in the NanoChatCartridgeSystem.cs handler: enforce a configurable maximum length (e.g., MAX_NANOCHAT_MESSAGE_LENGTH constant), trim or reject messages that exceed it, and return/log a clear error or drop the message to prevent abuse; perform this check when handling INanoChatUiMessagePayload/NanoChatSendMessage before broadcasting or persisting the message and ensure empty or whitespace-only messages are also rejected.Content.Server/PDA/PdaSystem.cs (1)
344-346: Event declaration is outside namespace scope.The
OwnerNameChangedEventis declared after the namespace closing brace, placing it in the global namespace. This is unconventional and may cause namespace resolution issues. Consider moving it inside theContent.Server.PDAnamespace or into a separate file.♻️ Proposed fix to move event inside namespace
} } - -[ByRefEvent] // Corvax-Wega-NanoChat -public record struct OwnerNameChangedEvent(); // Corvax-Wega-NanoChat + +[ByRefEvent] // Corvax-Wega-NanoChat +public record struct OwnerNameChangedEvent(); // Corvax-Wega-NanoChat +} // end namespace Content.Server.PDAOr better yet, move the event definition inside the namespace block before the closing brace:
private string? GetDeviceNetAddress(EntityUid uid) { // ... existing code ... } + + [ByRefEvent] // Corvax-Wega-NanoChat + public record struct OwnerNameChangedEvent(); // Corvax-Wega-NanoChat } } - -[ByRefEvent] // Corvax-Wega-NanoChat -public record struct OwnerNameChangedEvent(); // Corvax-Wega-NanoChat🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@Content.Server/PDA/PdaSystem.cs` around lines 344 - 346, The OwnerNameChangedEvent record struct is declared outside the Content.Server.PDA namespace — move its declaration inside the Content.Server.PDA namespace block (or into a new file within that namespace) so it is not in the global namespace; update the declaration of OwnerNameChangedEvent (the [ByRefEvent] public record struct OwnerNameChangedEvent()) to reside before the namespace closing brace (or create a new file with the same namespace and place the event there) to restore proper namespace resolution.Content.Client/_Wega/CartridgeLoader/Cartridges/NanoChatCreateGroupPopup.xaml (1)
7-7: SetMaxLengthon the group name input to match backend/client limits.Line 7 should cap length in the field itself (e.g., 16) so users don’t lose characters silently on submit.
♻️ Proposed XAML tweak
- <LineEdit Name="GroupNameInput" PlaceHolder="{Loc 'nano-chat-ui-group-name-placeholder'}"/> + <LineEdit Name="GroupNameInput" + PlaceHolder="{Loc 'nano-chat-ui-group-name-placeholder'}" + MaxLength="16"/>🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@Content.Client/_Wega/CartridgeLoader/Cartridges/NanoChatCreateGroupPopup.xaml` at line 7, Add a MaxLength to the group name input so the UI enforces the same character limit as the backend/client (e.g., 16); update the LineEdit with Name="GroupNameInput" to include MaxLength="16" (or the exact backend-defined constant) so users cannot enter more characters than allowed and won’t lose characters on submit.Content.Client/_Wega/CartridgeLoader/Cartridges/NanoChatEmojiPopup.xaml.cs (1)
15-69: Avoid per-instance allocation of the emoji catalog.Lines 15-69 allocate a large
List<string>for every popup instance. Make itstatic readonly(or immutable) to reduce allocations and make intent clearer.♻️ Proposed refactor
- private readonly List<string> _emojis = new() + private static readonly string[] Emojis = { // ... }; @@ - foreach (var emoji in _emojis) + foreach (var emoji in Emojis)🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@Content.Client/_Wega/CartridgeLoader/Cartridges/NanoChatEmojiPopup.xaml.cs` around lines 15 - 69, The _emojis field in NanoChatEmojiPopup.xaml.cs is allocated per instance; change the declaration of _emojis to a single shared immutable/static readonly collection (e.g., static readonly IReadOnlyList<string> or static readonly string[]) so the large emoji catalog is created once, and update any code that mutates _emojis to treat it as read-only or copy before modifying; reference the field named _emojis in the NanoChatEmojiPopup class when making this change.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In
`@Content.Client/_Wega/CartridgeLoader/Cartridges/NanoChatAddContactPopup.xaml.cs`:
- Around line 23-27: The current logic in the NanoChatAddContactPopup.xaml.cs
silently truncates normalizedId (from NormalizeContactId(ContactIdInput.Text))
to 5 chars which can map user input to a different contact; instead validate
that normalizedId matches the required pattern ("#" followed by exactly 4
digits) and reject/notify the user when it does not. Replace the truncation
logic around normalizedId/contactId with a validation check (use
NormalizeContactId(ContactIdInput.Text) → normalizedId, then assert Regex or
explicit check for prefix '#' and length 5 with digits) and surface a validation
error or disable submission rather than slicing the string.
In
`@Content.Client/_Wega/CartridgeLoader/Cartridges/NanoChatCreateGroupPopup.xaml.cs`:
- Around line 19-27: Extract the magic number by adding a shared constant (e.g.,
MAX_GROUP_NAME_LENGTH) and use it in the client truncation logic (currently
using GroupNameInput and OnGroupCreated) instead of the literal 16; trim the
input (call Trim() on GroupNameInput.Text) before checking emptiness and
truncating so leading/trailing spaces are removed; then add server-side
validation in NanoChatCartridgeSystem.CreateGroup to enforce the same
MAX_GROUP_NAME_LENGTH and reject or sanitize names that are empty after trim or
exceed the limit (return an error/throw so modified clients cannot bypass the
constraint).
In
`@Content.Client/_Wega/CartridgeLoader/Cartridges/NanoChatJoinGroupPopup.xaml.cs`:
- Around line 22-39: NormalizeGroupId currently trims/strips and may prepend "G"
to numeric input but allows non-canonical forms and the caller truncates IDs;
update NormalizeGroupId to return an uppercase canonical ID in the form "G"
followed by exactly 4 digits (e.g., use a regex like ^G\d{4}$ after making input
uppercase and removing spaces) and remove the caller-side truncation (the
groupId = normalizedId[..5] logic). Also, only call OnGroupJoined with the
validated canonical ID (from NormalizeGroupId) and handle invalid inputs (e.g.,
show an error or disable the join) instead of invoking OnGroupJoined with
non-canonical values; refer to NormalizeGroupId, OnGroupJoined, and
GroupIdInput.Text to locate edits.
In `@Content.Client/_Wega/CartridgeLoader/Cartridges/NanoChatUiFragment.xaml.cs`:
- Around line 21-22: The CloseChat handler is currently a client-only no-op
because CloseChat is never assigned, so clicking the button only clears
ActiveChatId locally while the server still holds ActiveChat; either wire
CloseChat to send the server-side close message from the NanoChatUi controller
(assign the CloseChat Action in the NanoChatUi class to call the
transport/message method that clears ActiveChat on the server) or disable/hide
the UI button until CloseChat is non-null; also apply the same fix for
LeaveChat/its button so both actions either have server-side wiring or are
disabled when not assigned (refer to CloseChat, LeaveChat, ActiveChatId,
ActiveChat, and the NanoChatUi class to locate where to assign or gate the
button).
- Around line 228-230: The UI is calling
Loc.GetString("nano-chat-ui-unknown-chat") in NanoChatUiFragment.xaml.cs
(ChatTitle.Text assignment) but that key is missing from the NanoChat locale
resources; add the missing "nano-chat-ui-unknown-chat" entry to the NanoChat
locale files (for all supported languages), provide appropriate translated
strings (e.g., "Unknown chat" for en), and update/rebuild any localization
resource bundle so Loc.GetString resolves correctly at runtime.
In `@Content.Server/_Wega/CartridgeLoader/Cartridges/NanoChatCartridgeSystem.cs`:
- Around line 379-420: SendGroupMessage appends per-recipient copies to
memberComp.Messages[groupId] but UpdateUiState currently serializes the shared
group.Messages, causing devices to see messages they never received; change
UpdateUiState (the code path called with (memberEntity, memberComp)) so when
building the UI payload for a group it prefers the per-cartridge history
memberComp.Messages[groupId] if present (falling back to group.Messages only
when the per-device list is missing), and ensure the same fix is applied for the
other occurrence around lines 431-437 where group.Messages is used instead of
memberComp.Messages; update references to ChatGroup/ChatMessage serialization so
it uses memberComp.Messages and memberComp.Groups for per-device state.
- Around line 72-76: Active chat state becomes inconsistent after
ejecting/reinserting a cartridge because MapInit only populates _activeChats
while OnCartridgeRemoved() removes the cartridge from ChatGroupData.Members but
does not clear the cartridge-side ent.Comp.Groups or remove its entry from
_activeChats; update OnCartridgeRemoved() to (1) remove the ent.Comp.ChatId key
from _activeChats, (2) clear ent.Comp.Groups (or reset to an empty collection)
so the client no longer sees stale groups, and (3) update any ChatGroupData
structures to drop references to this owner; do the same fix for the analogous
block at lines handling removal (the 105-110 area) so ejecting fully cleans both
server and ent.Comp state and reinserting repopulates _activeChats only via
MapInit/GenerateUniqueChatId as before.
- Around line 186-199: SetActiveChat currently trusts any chatId and will expose
group messages; add a membership guard so group chats can only be selected if
the user is a member. Specifically, in SetActiveChat (and the other similar
sites indicated: the block around 304-307 and the SendMessage logic around
431-437), detect when chatId refers to a group (the Groups dictionary / group id
pattern) and verify membership via the group's membership data or the user's
membership map before setting ent.Comp.ActiveChat, returning early (or
no-op/log) if the user is not a member; do the same check before returning group
messages in UpdateUiState and before broadcasting in SendMessage so forged
client packets cannot read or post to groups the user hasn’t joined.
- Around line 231-245: The code currently ignores the boolean result of
group.Members.Add and always broadcasts Join and refresh events; change the
logic in the block handling _groups.TryGetValue(groupId, out var group) to only
proceed when group.Members.Add(ent.Comp.ChatId) returns true (i.e., membership
actually changed). If Add returns false, skip creating/updating ent.Comp.Groups[
groupId ], skip calling NotifyGroupMembers(groupId, ...), and skip
UpdateAllGroupMembersUi(groupId); when Add returns true, update ent.Comp.Groups
with the new ChatGroup (using group.Members.Count) and then call
NotifyGroupMembers and UpdateAllGroupMembersUi as currently done.
In `@Content.Server/Forensics/Systems/ForensicScannerSystem.cs`:
- Around line 275-337: PrintChatHistory bypasses the scanner's print cooldown
(PrintReadyAt) and spawns paper unconditionally; add the same gating and
cooldown advance as OnPrint. At the top of PrintChatHistory check if
component.PrintReadyAt > _gameTiming.CurTime and if so call
_popupSystem.PopupEntity with the existing cooldown message and return; after
successfully spawning/setting paper (before popup "printed") advance the
cooldown by setting component.PrintReadyAt = _gameTiming.CurTime +
component.PrintCooldown (or call the existing OnPrint(uid, component) helper if
available) so the PDA chat print respects the same throttle as other prints.
- Around line 365-443: The scanner treats keys with empty message lists as "has
messages", causing blank sections; update logic to treat empty lists as "no
messages": when iterating chatComp.Messages skip/handle entries where
messages.Count == 0 (so they print the localized no-messages line instead of a
blank block), and when checking for contacts or groups use TryGetValue(...) and
consider the messages list empty if TryGetValue returns false OR the retrieved
list.Count == 0 (update the contact-check around
chatComp.Messages.ContainsKey(contact.ContactId) and the group branch that uses
chatComp.Messages.TryGetValue(groupId, out var groupMessages) to check
groupMessages.Count == 0).
In `@Resources/Locale/en-US/_Wega/forensics/forensics.ftl`:
- Around line 1-27: The en-US locale file contains Russian strings for multiple
keys (e.g., forensic-scanner-print-chat-history-text,
forensic-scanner-print-chat-history-message,
forensic-scanner-chat-history-title, forensic-scanner-chat-history-header,
forensic-scanner-chat-history-printed, forensic-scanner-no-cartridge,
forensic-scanner-no-chat-cartridge, forensic-scanner-chat-cartridge-header,
forensic-scanner-chat-no-data, forensic-scanner-chat-with-contact,
forensic-scanner-chat-with-unknown, forensic-scanner-chat-contact-no-messages,
forensic-scanner-chat-groups-header, forensic-scanner-chat-group-info,
forensic-scanner-chat-group-messages, forensic-scanner-chat-group-no-messages,
forensic-scanner-chat-archived-group, forensic-scanner-chat-you); replace each
Russian value with the correct English copy (or copy from the default/English
source) so en-US users see English UI and report text, preserving the same
message keys and interpolation placeholders like { $owner }, { $time }, { $id },
{ $name }, and { $members } exactly.
In `@Resources/Locale/en-US/_Wega/nanochat/nanochatui.ftl`:
- Around line 2-60: This file under en-US contains Russian strings—replace each
Russian value with its English translation so the en-US locale actually provides
English text; update all keys such as nano-chat-ui-add,
nano-chat-ui-add-contact-title, nano-chat-ui-contact-id-placeholder,
nano-chat-ui-contact-name-placeholder, nano-chat-ui-create-group-title,
nano-chat-ui-emoji-title, nano-chat-ui-group-name-placeholder,
nano-chat-ui-join-group-title, nano-chat-ui-message-placeholder,
nano-chat-ui-tab-contacts, nano-chat-ui-title, nano-chat-ui-unread-indicator and
the system messages nano-chat-create-group-message, nano-chat-join-message,
nano-chat-leave-message to appropriate English phrases (including translating
placeholders and preserving interpolation variables like {$name} and
{$groupName}). Ensure punctuation and placeholder formats are kept intact.
In `@Resources/Locale/ru-RU/_wega/cartridge-loader/cartridges.ftl`:
- Line 1: The Russian locale directory uses `_wega` while English uses `_Wega`;
rename the Russian directory to match the established casing (change `_wega` ->
`_Wega`) and update all references; specifically move/rename the directory
containing cartridges.ftl and update any config, import, or build references
that point to `_wega` so all locales use the same `_Wega` casing (use git mv to
preserve history and run tests/build to verify).
In `@Resources/Textures/_Wega/Interface/sound.svg.192dpi.png` copy.yml:
- Around line 1-2: The file named "sound.svg.192dpi.png copy.yml" appears to be
an accidental duplicate; either delete this file if it's redundant or rename it
to "sound.svg.192dpi.png.yml" so it matches the intended texture asset, ensuring
the YAML contents remain unchanged and any references to the texture in the
project point to the corrected filename.
---
Nitpick comments:
In
`@Content.Client/_Wega/CartridgeLoader/Cartridges/NanoChatCreateGroupPopup.xaml`:
- Line 7: Add a MaxLength to the group name input so the UI enforces the same
character limit as the backend/client (e.g., 16); update the LineEdit with
Name="GroupNameInput" to include MaxLength="16" (or the exact backend-defined
constant) so users cannot enter more characters than allowed and won’t lose
characters on submit.
In `@Content.Client/_Wega/CartridgeLoader/Cartridges/NanoChatEmojiPopup.xaml.cs`:
- Around line 15-69: The _emojis field in NanoChatEmojiPopup.xaml.cs is
allocated per instance; change the declaration of _emojis to a single shared
immutable/static readonly collection (e.g., static readonly
IReadOnlyList<string> or static readonly string[]) so the large emoji catalog is
created once, and update any code that mutates _emojis to treat it as read-only
or copy before modifying; reference the field named _emojis in the
NanoChatEmojiPopup class when making this change.
In `@Content.Server/PDA/PdaSystem.cs`:
- Around line 344-346: The OwnerNameChangedEvent record struct is declared
outside the Content.Server.PDA namespace — move its declaration inside the
Content.Server.PDA namespace block (or into a new file within that namespace) so
it is not in the global namespace; update the declaration of
OwnerNameChangedEvent (the [ByRefEvent] public record struct
OwnerNameChangedEvent()) to reside before the namespace closing brace (or create
a new file with the same namespace and place the event there) to restore proper
namespace resolution.
In `@Content.Shared/_Wega/CartridgeLoader/Cartridges/NanoChatUiMessageEvent.cs`:
- Around line 37-48: Add server-side validation for NanoChatSendMessage.Message
in the NanoChatCartridgeSystem.cs handler: enforce a configurable maximum length
(e.g., MAX_NANOCHAT_MESSAGE_LENGTH constant), trim or reject messages that
exceed it, and return/log a clear error or drop the message to prevent abuse;
perform this check when handling INanoChatUiMessagePayload/NanoChatSendMessage
before broadcasting or persisting the message and ensure empty or
whitespace-only messages are also rejected.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: bd4eb385-453b-4858-ba14-4b9ccb3ced75
⛔ Files ignored due to path filters (5)
Resources/Textures/_Wega/Interface/Misc/program_icons.rsi/nano_chat.pngis excluded by!**/*.pngResources/Textures/_Wega/Interface/sound.svg.192dpi.pngis excluded by!**/*.pngResources/Textures/_Wega/Interface/sound.svg.192dpi.svgis excluded by!**/*.svgResources/Textures/_Wega/Interface/soundoff.svg.192dpi.pngis excluded by!**/*.pngResources/Textures/_Wega/Interface/soundoff.svg.192dpi.svgis excluded by!**/*.svg
📒 Files selected for processing (37)
Content.Client/_Wega/CartridgeLoader/Cartridges/NanoChatAddContactPopup.xamlContent.Client/_Wega/CartridgeLoader/Cartridges/NanoChatAddContactPopup.xaml.csContent.Client/_Wega/CartridgeLoader/Cartridges/NanoChatContactControl.xamlContent.Client/_Wega/CartridgeLoader/Cartridges/NanoChatContactControl.xaml.csContent.Client/_Wega/CartridgeLoader/Cartridges/NanoChatCreateGroupPopup.xamlContent.Client/_Wega/CartridgeLoader/Cartridges/NanoChatCreateGroupPopup.xaml.csContent.Client/_Wega/CartridgeLoader/Cartridges/NanoChatEmojiPopup.xamlContent.Client/_Wega/CartridgeLoader/Cartridges/NanoChatEmojiPopup.xaml.csContent.Client/_Wega/CartridgeLoader/Cartridges/NanoChatGroupControl.csContent.Client/_Wega/CartridgeLoader/Cartridges/NanoChatGroupControl.xamlContent.Client/_Wega/CartridgeLoader/Cartridges/NanoChatJoinGroupPopup.xamlContent.Client/_Wega/CartridgeLoader/Cartridges/NanoChatJoinGroupPopup.xaml.csContent.Client/_Wega/CartridgeLoader/Cartridges/NanoChatMessageControl.xamlContent.Client/_Wega/CartridgeLoader/Cartridges/NanoChatMessageControl.xaml.csContent.Client/_Wega/CartridgeLoader/Cartridges/NanoChatUi.csContent.Client/_Wega/CartridgeLoader/Cartridges/NanoChatUiFragment.xamlContent.Client/_Wega/CartridgeLoader/Cartridges/NanoChatUiFragment.xaml.csContent.Server/Forensics/Systems/ForensicScannerSystem.csContent.Server/PDA/PdaSystem.csContent.Server/_Wega/CartridgeLoader/Cartridges/NanoChatCartridgeSystem.csContent.Shared/_Wega/CartridgeLoader/Cartridges/NanoChatCartridgeComponent.csContent.Shared/_Wega/CartridgeLoader/Cartridges/NanoChatUiMessageEvent.csContent.Shared/_Wega/CartridgeLoader/Cartridges/NanoChatUiState.csContent.Shared/_Wega/CartridgeLoader/Cartridges/SharedNanoChatCartridgeSystem.csResources/Locale/en-US/_Wega/cartridge-loader/cartridges.ftlResources/Locale/en-US/_Wega/forensics/forensics.ftlResources/Locale/en-US/_Wega/nanochat/nanochatui.ftlResources/Locale/en-US/ss14-ru/prototypes/_wega/entities/objects/devices/cartridges.ftlResources/Locale/ru-RU/_wega/cartridge-loader/cartridges.ftlResources/Locale/ru-RU/_wega/forensics/forensics.ftlResources/Locale/ru-RU/_wega/nanochat/nanochatui.ftlResources/Locale/ru-RU/ss14-ru/prototypes/_wega/entities/objects/devices/cartridges.ftlResources/Prototypes/Entities/Objects/Devices/pda.ymlResources/Prototypes/_Wega/Entities/Objects/Devices/cartridges.ymlResources/Textures/_Wega/Interface/Misc/program_icons.rsi/meta.jsonResources/Textures/_Wega/Interface/sound.svg.192dpi.png copy.ymlResources/Textures/_Wega/Interface/soundoff.svg.192dpi.png.yml
| var normalizedId = NormalizeContactId(ContactIdInput.Text); | ||
|
|
||
| var contactId = normalizedId.Length <= 5 | ||
| ? normalizedId | ||
| : normalizedId[..5]; |
There was a problem hiding this comment.
Do not silently truncate contact IDs.
Lines 25-27 can transform user input into a different valid ID (e.g., #12345 → #1234), which risks adding/messaging the wrong contact. Validate format (# + 4 digits) and reject invalid input instead of truncating.
🐛 Proposed fix
var normalizedId = NormalizeContactId(ContactIdInput.Text);
-
- var contactId = normalizedId.Length <= 5
- ? normalizedId
- : normalizedId[..5];
+ if (normalizedId.Length != 5 ||
+ normalizedId[0] != '#' ||
+ !normalizedId[1..].All(char.IsDigit))
+ return;
@@
- OnContactAdded?.Invoke(contactId, contactName);
+ OnContactAdded?.Invoke(normalizedId, contactName);🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In
`@Content.Client/_Wega/CartridgeLoader/Cartridges/NanoChatAddContactPopup.xaml.cs`
around lines 23 - 27, The current logic in the NanoChatAddContactPopup.xaml.cs
silently truncates normalizedId (from NormalizeContactId(ContactIdInput.Text))
to 5 chars which can map user input to a different contact; instead validate
that normalizedId matches the required pattern ("#" followed by exactly 4
digits) and reject/notify the user when it does not. Replace the truncation
logic around normalizedId/contactId with a validation check (use
NormalizeContactId(ContactIdInput.Text) → normalizedId, then assert Regex or
explicit check for prefix '#' and length 5 with digits) and surface a validation
error or disable submission rather than slicing the string.
| if (!string.IsNullOrWhiteSpace(GroupNameInput.Text)) | ||
| { | ||
| var groupName = GroupNameInput.Text.Length <= 16 | ||
| ? GroupNameInput.Text | ||
| : GroupNameInput.Text[..16]; | ||
|
|
||
| OnGroupCreated?.Invoke(groupName); | ||
| Close(); | ||
| } |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Check server-side CreateGroup implementation for validation
ast-grep --pattern $'private void CreateGroup($_, string groupName)
{
$$$
}'Repository: corvax-team/ss14-wl
Length of output: 45
🏁 Script executed:
fd -i "NanoChatCartridgeSystem" --type fRepository: corvax-team/ss14-wl
Length of output: 218
🏁 Script executed:
rg "CreateGroup" -A 15 --type csRepository: corvax-team/ss14-wl
Length of output: 15335
🏁 Script executed:
fd -i "nanochat" --type fRepository: corvax-team/ss14-wl
Length of output: 1826
🏁 Script executed:
cat -n Content.Server/_Wega/CartridgeLoader/Cartridges/NanoChatCartridgeSystem.cs | sed -n '200,250p'Repository: corvax-team/ss14-wl
Length of output: 1942
🏁 Script executed:
rg -i "const.*16\|const.*group.*name\|const.*max" Content.Server/_Wega/CartridgeLoader/Cartridges/NanoChatCartridgeSystem.csRepository: corvax-team/ss14-wl
Length of output: 45
🏁 Script executed:
rg -i "const.*16\|const.*group.*name\|const.*max" Content.Client/_Wega/CartridgeLoader/Cartridges/NanoChatCreateGroupPopup.xaml.csRepository: corvax-team/ss14-wl
Length of output: 45
🏁 Script executed:
cat -n Content.Client/_Wega/CartridgeLoader/Cartridges/NanoChatCreateGroupPopup.xaml.csRepository: corvax-team/ss14-wl
Length of output: 1128
Extract magic number to constant and add server-side validation.
Three issues:
-
Magic number 16: The truncation limit should be defined as a named constant in shared code to keep client and server in sync.
-
Missing server-side validation: The
CreateGroupmethod inNanoChatCartridgeSystem.cs(lines 202-227) accepts the group name without any length validation. A malicious or modified client could bypass the client-side truncation and send excessively long names. Add validation on the server. -
No whitespace trimming: Input like
" GroupName "passes the whitespace check but is not trimmed before truncation, resulting in names with unwanted spacing.
🛡️ Proposed client-side fix with trim
CreateButton.OnPressed += _ =>
{
- if (!string.IsNullOrWhiteSpace(GroupNameInput.Text))
+ var trimmedName = GroupNameInput.Text?.Trim();
+ if (!string.IsNullOrEmpty(trimmedName))
{
- var groupName = GroupNameInput.Text.Length <= 16
- ? GroupNameInput.Text
- : GroupNameInput.Text[..16];
+ var groupName = trimmedName.Length <= 16
+ ? trimmedName
+ : trimmedName[..16];
OnGroupCreated?.Invoke(groupName);
Close();
}
};🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In
`@Content.Client/_Wega/CartridgeLoader/Cartridges/NanoChatCreateGroupPopup.xaml.cs`
around lines 19 - 27, Extract the magic number by adding a shared constant
(e.g., MAX_GROUP_NAME_LENGTH) and use it in the client truncation logic
(currently using GroupNameInput and OnGroupCreated) instead of the literal 16;
trim the input (call Trim() on GroupNameInput.Text) before checking emptiness
and truncating so leading/trailing spaces are removed; then add server-side
validation in NanoChatCartridgeSystem.CreateGroup to enforce the same
MAX_GROUP_NAME_LENGTH and reject or sanitize names that are empty after trim or
exceed the limit (return an error/throw so modified clients cannot bypass the
constraint).
| var normalizedId = NormalizeGroupId(GroupIdInput.Text.Trim()); | ||
| var groupId = normalizedId.Length <= 5 | ||
| ? normalizedId | ||
| : normalizedId[..5]; | ||
|
|
||
| OnGroupJoined?.Invoke(groupId); | ||
| Close(); | ||
| } | ||
| }; | ||
| } | ||
|
|
||
| private string NormalizeGroupId(string contactId) | ||
| { | ||
| var cleanedId = contactId.Trim().Replace(" ", ""); | ||
| if (!cleanedId.StartsWith("G") && cleanedId.All(char.IsDigit)) | ||
| return "G" + cleanedId; | ||
|
|
||
| return cleanedId; |
There was a problem hiding this comment.
Validate canonical group ID instead of truncating.
Lines 23-25 may rewrite input into a different ID, and Lines 33-39 can pass non-canonical IDs (e.g., lowercase g1234) through. Normalize to uppercase and require exact G + 4 digits before OnGroupJoined.
🐛 Proposed fix
if (!string.IsNullOrWhiteSpace(GroupIdInput.Text))
{
- var normalizedId = NormalizeGroupId(GroupIdInput.Text.Trim());
- var groupId = normalizedId.Length <= 5
- ? normalizedId
- : normalizedId[..5];
+ var normalizedId = NormalizeGroupId(GroupIdInput.Text).ToUpperInvariant();
+ if (normalizedId.Length != 5 ||
+ normalizedId[0] != 'G' ||
+ !normalizedId[1..].All(char.IsDigit))
+ return;
- OnGroupJoined?.Invoke(groupId);
+ OnGroupJoined?.Invoke(normalizedId);
Close();
}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| var normalizedId = NormalizeGroupId(GroupIdInput.Text.Trim()); | |
| var groupId = normalizedId.Length <= 5 | |
| ? normalizedId | |
| : normalizedId[..5]; | |
| OnGroupJoined?.Invoke(groupId); | |
| Close(); | |
| } | |
| }; | |
| } | |
| private string NormalizeGroupId(string contactId) | |
| { | |
| var cleanedId = contactId.Trim().Replace(" ", ""); | |
| if (!cleanedId.StartsWith("G") && cleanedId.All(char.IsDigit)) | |
| return "G" + cleanedId; | |
| return cleanedId; | |
| var normalizedId = NormalizeGroupId(GroupIdInput.Text).ToUpperInvariant(); | |
| if (normalizedId.Length != 5 || | |
| normalizedId[0] != 'G' || | |
| !normalizedId[1..].All(char.IsDigit)) | |
| return; | |
| OnGroupJoined?.Invoke(normalizedId); | |
| Close(); | |
| } | |
| }; | |
| } | |
| private string NormalizeGroupId(string contactId) | |
| { | |
| var cleanedId = contactId.Trim().Replace(" ", ""); | |
| if (!cleanedId.StartsWith("G") && cleanedId.All(char.IsDigit)) | |
| return "G" + cleanedId; | |
| return cleanedId; |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In
`@Content.Client/_Wega/CartridgeLoader/Cartridges/NanoChatJoinGroupPopup.xaml.cs`
around lines 22 - 39, NormalizeGroupId currently trims/strips and may prepend
"G" to numeric input but allows non-canonical forms and the caller truncates
IDs; update NormalizeGroupId to return an uppercase canonical ID in the form "G"
followed by exactly 4 digits (e.g., use a regex like ^G\d{4}$ after making input
uppercase and removing spaces) and remove the caller-side truncation (the
groupId = normalizedId[..5] logic). Also, only call OnGroupJoined with the
validated canonical ID (from NormalizeGroupId) and handle invalid inputs (e.g.,
show an error or disable the join) instead of invoking OnGroupJoined with
non-canonical values; refer to NormalizeGroupId, OnGroupJoined, and
GroupIdInput.Text to locate edits.
| public Action<string>? CloseChat; | ||
| public Action<string>? LeaveChat; |
There was a problem hiding this comment.
Close chat is a client-only no-op right now.
This handler assumes CloseChat is wired, but Content.Client/_Wega/CartridgeLoader/Cartridges/NanoChatUi.cs:18-80 never assigns that delegate. Clicking the button only clears ActiveChatId locally; the server still keeps ActiveChat, so the selection comes back on the next state push. Please either wire a close-chat message or hide/disable the button until that path exists.
Also applies to: 74-81
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@Content.Client/_Wega/CartridgeLoader/Cartridges/NanoChatUiFragment.xaml.cs`
around lines 21 - 22, The CloseChat handler is currently a client-only no-op
because CloseChat is never assigned, so clicking the button only clears
ActiveChatId locally while the server still holds ActiveChat; either wire
CloseChat to send the server-side close message from the NanoChatUi controller
(assign the CloseChat Action in the NanoChatUi class to call the
transport/message method that clears ActiveChat on the server) or disable/hide
the UI button until CloseChat is non-null; also apply the same fix for
LeaveChat/its button so both actions either have server-side wiring or are
disabled when not assigned (refer to CloseChat, LeaveChat, ActiveChatId,
ActiveChat, and the NanoChatUi class to locate where to assign or gate the
button).
| else | ||
| { | ||
| ChatTitle.Text = Loc.GetString("nano-chat-ui-unknown-chat"); |
There was a problem hiding this comment.
Add the missing nano-chat-ui-unknown-chat localization key.
Loc.GetString("nano-chat-ui-unknown-chat") is used here, but neither NanoChat locale file defines it. The raw key/fallback will leak into the UI as soon as an active id no longer resolves.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@Content.Client/_Wega/CartridgeLoader/Cartridges/NanoChatUiFragment.xaml.cs`
around lines 228 - 230, The UI is calling
Loc.GetString("nano-chat-ui-unknown-chat") in NanoChatUiFragment.xaml.cs
(ChatTitle.Text assignment) but that key is missing from the NanoChat locale
resources; add the missing "nano-chat-ui-unknown-chat" entry to the NanoChat
locale files (for all supported languages), provide appropriate translated
strings (e.g., "Unknown chat" for en), and update/rebuild any localization
resource bundle so Loc.GetString resolves correctly at runtime.
| foreach (var (contactId, messages) in chatComp.Messages) | ||
| { | ||
| if (contactId.StartsWith("G")) | ||
| continue; | ||
|
|
||
| if (chatComp.Contacts.TryGetValue(contactId, out var contact)) | ||
| { | ||
| text.AppendLine(Loc.GetString("forensic-scanner-chat-with-contact", | ||
| ("name", contact.ContactName), | ||
| ("id", contactId))); | ||
| } | ||
| else | ||
| { | ||
| text.AppendLine(Loc.GetString("forensic-scanner-chat-with-unknown", | ||
| ("id", contactId))); | ||
| } | ||
|
|
||
| text.AppendLine(new string('-', 40)); | ||
|
|
||
| foreach (var message in messages) | ||
| { | ||
| var time = $"{(int)message.Timestamp.TotalHours:00}:{message.Timestamp.Minutes:00}"; | ||
| var sender = message.IsOwnMessage ? Loc.GetString("forensic-scanner-chat-you") : message.SenderName; | ||
|
|
||
| var status = message.Delivered ? "✓" : "✗"; | ||
|
|
||
| text.AppendLine($"[{time}] {sender} ({status}): {message.Message}"); | ||
| } | ||
|
|
||
| text.AppendLine(); | ||
| } | ||
|
|
||
| foreach (var contact in chatComp.Contacts.Values) | ||
| { | ||
| if (contact.ContactId.StartsWith("G")) | ||
| continue; | ||
|
|
||
| if (!chatComp.Messages.ContainsKey(contact.ContactId)) | ||
| { | ||
| text.AppendLine(Loc.GetString("forensic-scanner-chat-contact-no-messages", | ||
| ("name", contact.ContactName), | ||
| ("id", contact.ContactId))); | ||
| text.AppendLine(); | ||
| } | ||
| } | ||
|
|
||
| if (chatComp.Groups.Count > 0) | ||
| { | ||
| text.AppendLine(Loc.GetString("forensic-scanner-chat-groups-header")); | ||
| text.AppendLine(new string('=', 36)); | ||
|
|
||
| foreach (var (groupId, group) in chatComp.Groups) | ||
| { | ||
| text.AppendLine(Loc.GetString("forensic-scanner-chat-group-info", | ||
| ("name", group.GroupName), | ||
| ("id", groupId), | ||
| ("members", group.MemberCount))); | ||
|
|
||
| if (chatComp.Messages.TryGetValue(groupId, out var groupMessages)) | ||
| { | ||
| text.AppendLine(Loc.GetString("forensic-scanner-chat-group-messages")); | ||
| text.AppendLine(new string('-', 30)); | ||
|
|
||
| foreach (var message in groupMessages) | ||
| { | ||
| var time = $"{(int)message.Timestamp.TotalHours:00}:{message.Timestamp.Minutes:00}"; | ||
| var sender = message.IsOwnMessage ? | ||
| Loc.GetString("forensic-scanner-chat-you") : | ||
| message.SenderName; | ||
|
|
||
| var status = message.Delivered ? "✓" : "✗"; | ||
|
|
||
| text.AppendLine($"[{time}] {sender} ({status}): {message.Message}"); | ||
| } | ||
| } | ||
| else | ||
| { | ||
| text.AppendLine(Loc.GetString("forensic-scanner-chat-group-no-messages")); | ||
| } |
There was a problem hiding this comment.
Treat empty message lists as "no messages".
The current branches only distinguish missing keys from present keys. If a contact or group already has an empty list entry, the report prints a blank section instead of the localized empty-state line.
🛠️ Suggested fix
foreach (var (contactId, messages) in chatComp.Messages)
{
- if (contactId.StartsWith("G"))
+ if (contactId.StartsWith("G") || messages.Count == 0)
continue;
@@
- if (!chatComp.Messages.ContainsKey(contact.ContactId))
+ if (!chatComp.Messages.TryGetValue(contact.ContactId, out var contactMessages) ||
+ contactMessages.Count == 0)
{
text.AppendLine(Loc.GetString("forensic-scanner-chat-contact-no-messages",
("name", contact.ContactName),
("id", contact.ContactId)));
text.AppendLine();
@@
- if (chatComp.Messages.TryGetValue(groupId, out var groupMessages))
+ if (chatComp.Messages.TryGetValue(groupId, out var groupMessages) &&
+ groupMessages.Count > 0)
{
text.AppendLine(Loc.GetString("forensic-scanner-chat-group-messages"));
text.AppendLine(new string('-', 30));📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| foreach (var (contactId, messages) in chatComp.Messages) | |
| { | |
| if (contactId.StartsWith("G")) | |
| continue; | |
| if (chatComp.Contacts.TryGetValue(contactId, out var contact)) | |
| { | |
| text.AppendLine(Loc.GetString("forensic-scanner-chat-with-contact", | |
| ("name", contact.ContactName), | |
| ("id", contactId))); | |
| } | |
| else | |
| { | |
| text.AppendLine(Loc.GetString("forensic-scanner-chat-with-unknown", | |
| ("id", contactId))); | |
| } | |
| text.AppendLine(new string('-', 40)); | |
| foreach (var message in messages) | |
| { | |
| var time = $"{(int)message.Timestamp.TotalHours:00}:{message.Timestamp.Minutes:00}"; | |
| var sender = message.IsOwnMessage ? Loc.GetString("forensic-scanner-chat-you") : message.SenderName; | |
| var status = message.Delivered ? "✓" : "✗"; | |
| text.AppendLine($"[{time}] {sender} ({status}): {message.Message}"); | |
| } | |
| text.AppendLine(); | |
| } | |
| foreach (var contact in chatComp.Contacts.Values) | |
| { | |
| if (contact.ContactId.StartsWith("G")) | |
| continue; | |
| if (!chatComp.Messages.ContainsKey(contact.ContactId)) | |
| { | |
| text.AppendLine(Loc.GetString("forensic-scanner-chat-contact-no-messages", | |
| ("name", contact.ContactName), | |
| ("id", contact.ContactId))); | |
| text.AppendLine(); | |
| } | |
| } | |
| if (chatComp.Groups.Count > 0) | |
| { | |
| text.AppendLine(Loc.GetString("forensic-scanner-chat-groups-header")); | |
| text.AppendLine(new string('=', 36)); | |
| foreach (var (groupId, group) in chatComp.Groups) | |
| { | |
| text.AppendLine(Loc.GetString("forensic-scanner-chat-group-info", | |
| ("name", group.GroupName), | |
| ("id", groupId), | |
| ("members", group.MemberCount))); | |
| if (chatComp.Messages.TryGetValue(groupId, out var groupMessages)) | |
| { | |
| text.AppendLine(Loc.GetString("forensic-scanner-chat-group-messages")); | |
| text.AppendLine(new string('-', 30)); | |
| foreach (var message in groupMessages) | |
| { | |
| var time = $"{(int)message.Timestamp.TotalHours:00}:{message.Timestamp.Minutes:00}"; | |
| var sender = message.IsOwnMessage ? | |
| Loc.GetString("forensic-scanner-chat-you") : | |
| message.SenderName; | |
| var status = message.Delivered ? "✓" : "✗"; | |
| text.AppendLine($"[{time}] {sender} ({status}): {message.Message}"); | |
| } | |
| } | |
| else | |
| { | |
| text.AppendLine(Loc.GetString("forensic-scanner-chat-group-no-messages")); | |
| } | |
| foreach (var (contactId, messages) in chatComp.Messages) | |
| { | |
| if (contactId.StartsWith("G") || messages.Count == 0) | |
| continue; | |
| if (chatComp.Contacts.TryGetValue(contactId, out var contact)) | |
| { | |
| text.AppendLine(Loc.GetString("forensic-scanner-chat-with-contact", | |
| ("name", contact.ContactName), | |
| ("id", contactId))); | |
| } | |
| else | |
| { | |
| text.AppendLine(Loc.GetString("forensic-scanner-chat-with-unknown", | |
| ("id", contactId))); | |
| } | |
| text.AppendLine(new string('-', 40)); | |
| foreach (var message in messages) | |
| { | |
| var time = $"{(int)message.Timestamp.TotalHours:00}:{message.Timestamp.Minutes:00}"; | |
| var sender = message.IsOwnMessage ? Loc.GetString("forensic-scanner-chat-you") : message.SenderName; | |
| var status = message.Delivered ? "✓" : "✗"; | |
| text.AppendLine($"[{time}] {sender} ({status}): {message.Message}"); | |
| } | |
| text.AppendLine(); | |
| } | |
| foreach (var contact in chatComp.Contacts.Values) | |
| { | |
| if (contact.ContactId.StartsWith("G")) | |
| continue; | |
| if (!chatComp.Messages.TryGetValue(contact.ContactId, out var contactMessages) || | |
| contactMessages.Count == 0) | |
| { | |
| text.AppendLine(Loc.GetString("forensic-scanner-chat-contact-no-messages", | |
| ("name", contact.ContactName), | |
| ("id", contact.ContactId))); | |
| text.AppendLine(); | |
| } | |
| } | |
| if (chatComp.Groups.Count > 0) | |
| { | |
| text.AppendLine(Loc.GetString("forensic-scanner-chat-groups-header")); | |
| text.AppendLine(new string('=', 36)); | |
| foreach (var (groupId, group) in chatComp.Groups) | |
| { | |
| text.AppendLine(Loc.GetString("forensic-scanner-chat-group-info", | |
| ("name", group.GroupName), | |
| ("id", groupId), | |
| ("members", group.MemberCount))); | |
| if (chatComp.Messages.TryGetValue(groupId, out var groupMessages) && | |
| groupMessages.Count > 0) | |
| { | |
| text.AppendLine(Loc.GetString("forensic-scanner-chat-group-messages")); | |
| text.AppendLine(new string('-', 30)); | |
| foreach (var message in groupMessages) | |
| { | |
| var time = $"{(int)message.Timestamp.TotalHours:00}:{message.Timestamp.Minutes:00}"; | |
| var sender = message.IsOwnMessage ? | |
| Loc.GetString("forensic-scanner-chat-you") : | |
| message.SenderName; | |
| var status = message.Delivered ? "✓" : "✗"; | |
| text.AppendLine($"[{time}] {sender} ({status}): {message.Message}"); | |
| } | |
| } | |
| else | |
| { | |
| text.AppendLine(Loc.GetString("forensic-scanner-chat-group-no-messages")); | |
| } |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@Content.Server/Forensics/Systems/ForensicScannerSystem.cs` around lines 365 -
443, The scanner treats keys with empty message lists as "has messages", causing
blank sections; update logic to treat empty lists as "no messages": when
iterating chatComp.Messages skip/handle entries where messages.Count == 0 (so
they print the localized no-messages line instead of a blank block), and when
checking for contacts or groups use TryGetValue(...) and consider the messages
list empty if TryGetValue returns false OR the retrieved list.Count == 0 (update
the contact-check around chatComp.Messages.ContainsKey(contact.ContactId) and
the group branch that uses chatComp.Messages.TryGetValue(groupId, out var
groupMessages) to check groupMessages.Count == 0).
| forensic-scanner-print-chat-history-text = Напечатать историю чатов | ||
| forensic-scanner-print-chat-history-message = Распечатать все сообщения чатов из этого КПК | ||
|
|
||
| forensic-scanner-chat-history-title = Отчет истории чатов КПК | ||
| forensic-scanner-chat-history-header = | ||
| ОТЧЕТ ИСТОРИИ ЧАТОВ | ||
| Владелец: { $owner } | ||
| Время: { $time } | ||
| forensic-scanner-chat-history-printed = История чатов распечатана | ||
|
|
||
| forensic-scanner-no-cartridge = В КПК картриджи не найдены | ||
| forensic-scanner-no-chat-cartridge = В КПК не найдены картриджи чата | ||
|
|
||
| forensic-scanner-chat-cartridge-header = === Картридж чата: { $id } === | ||
| forensic-scanner-chat-no-data = Данные чатов отсутствуют | ||
|
|
||
| forensic-scanner-chat-with-contact = Чат с: { $name } ({ $id }) | ||
| forensic-scanner-chat-with-unknown = Чат с: Неизвестный ({ $id }) | ||
| forensic-scanner-chat-contact-no-messages = Контакт: { $name } ({ $id }) - нет сообщений | ||
|
|
||
| forensic-scanner-chat-groups-header = ГРУППОВЫЕ ЧАТЫ | ||
| forensic-scanner-chat-group-info = Группа: { $name } (ID: { $id }, Участников: { $members }) | ||
| forensic-scanner-chat-group-messages = Сообщения группы: | ||
| forensic-scanner-chat-group-no-messages = Нет сообщений в группе | ||
| forensic-scanner-chat-archived-group = Архивная группа: { $id } | ||
|
|
||
| forensic-scanner-chat-you = Вы |
There was a problem hiding this comment.
Replace the Russian copy in the en-US bundle.
This is the English locale file, but every new value is Russian. As-is, English/default-locale users will get Russian popups and printed report text for this feature.
🌐 Suggested English strings
-forensic-scanner-print-chat-history-text = Напечатать историю чатов
-forensic-scanner-print-chat-history-message = Распечатать все сообщения чатов из этого КПК
+forensic-scanner-print-chat-history-text = Print chat history
+forensic-scanner-print-chat-history-message = Print all chat messages from this PDA
-forensic-scanner-chat-history-title = Отчет истории чатов КПК
+forensic-scanner-chat-history-title = PDA chat history report
forensic-scanner-chat-history-header =
- ОТЧЕТ ИСТОРИИ ЧАТОВ
- Владелец: { $owner }
- Время: { $time }
-forensic-scanner-chat-history-printed = История чатов распечатана
+ CHAT HISTORY REPORT
+ Owner: { $owner }
+ Time: { $time }
+forensic-scanner-chat-history-printed = Chat history printed
-forensic-scanner-no-cartridge = В КПК картриджи не найдены
-forensic-scanner-no-chat-cartridge = В КПК не найдены картриджи чата
+forensic-scanner-no-cartridge = No cartridges found in the PDA
+forensic-scanner-no-chat-cartridge = No chat cartridges found in the PDA
-forensic-scanner-chat-cartridge-header = === Картридж чата: { $id } ===
-forensic-scanner-chat-no-data = Данные чатов отсутствуют
+forensic-scanner-chat-cartridge-header = === Chat cartridge: { $id } ===
+forensic-scanner-chat-no-data = No chat data available
-forensic-scanner-chat-with-contact = Чат с: { $name } ({ $id })
-forensic-scanner-chat-with-unknown = Чат с: Неизвестный ({ $id })
-forensic-scanner-chat-contact-no-messages = Контакт: { $name } ({ $id }) - нет сообщений
+forensic-scanner-chat-with-contact = Chat with: { $name } ({ $id })
+forensic-scanner-chat-with-unknown = Chat with: Unknown ({ $id })
+forensic-scanner-chat-contact-no-messages = Contact: { $name } ({ $id }) - no messages
-forensic-scanner-chat-groups-header = ГРУППОВЫЕ ЧАТЫ
-forensic-scanner-chat-group-info = Группа: { $name } (ID: { $id }, Участников: { $members })
-forensic-scanner-chat-group-messages = Сообщения группы:
-forensic-scanner-chat-group-no-messages = Нет сообщений в группе
-forensic-scanner-chat-archived-group = Архивная группа: { $id }
+forensic-scanner-chat-groups-header = GROUP CHATS
+forensic-scanner-chat-group-info = Group: { $name } (ID: { $id }, Members: { $members })
+forensic-scanner-chat-group-messages = Group messages:
+forensic-scanner-chat-group-no-messages = No messages in group
+forensic-scanner-chat-archived-group = Archived group: { $id }
-forensic-scanner-chat-you = Вы
+forensic-scanner-chat-you = You📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| forensic-scanner-print-chat-history-text = Напечатать историю чатов | |
| forensic-scanner-print-chat-history-message = Распечатать все сообщения чатов из этого КПК | |
| forensic-scanner-chat-history-title = Отчет истории чатов КПК | |
| forensic-scanner-chat-history-header = | |
| ОТЧЕТ ИСТОРИИ ЧАТОВ | |
| Владелец: { $owner } | |
| Время: { $time } | |
| forensic-scanner-chat-history-printed = История чатов распечатана | |
| forensic-scanner-no-cartridge = В КПК картриджи не найдены | |
| forensic-scanner-no-chat-cartridge = В КПК не найдены картриджи чата | |
| forensic-scanner-chat-cartridge-header = === Картридж чата: { $id } === | |
| forensic-scanner-chat-no-data = Данные чатов отсутствуют | |
| forensic-scanner-chat-with-contact = Чат с: { $name } ({ $id }) | |
| forensic-scanner-chat-with-unknown = Чат с: Неизвестный ({ $id }) | |
| forensic-scanner-chat-contact-no-messages = Контакт: { $name } ({ $id }) - нет сообщений | |
| forensic-scanner-chat-groups-header = ГРУППОВЫЕ ЧАТЫ | |
| forensic-scanner-chat-group-info = Группа: { $name } (ID: { $id }, Участников: { $members }) | |
| forensic-scanner-chat-group-messages = Сообщения группы: | |
| forensic-scanner-chat-group-no-messages = Нет сообщений в группе | |
| forensic-scanner-chat-archived-group = Архивная группа: { $id } | |
| forensic-scanner-chat-you = Вы | |
| forensic-scanner-print-chat-history-text = Print chat history | |
| forensic-scanner-print-chat-history-message = Print all chat messages from this PDA | |
| forensic-scanner-chat-history-title = PDA chat history report | |
| forensic-scanner-chat-history-header = | |
| CHAT HISTORY REPORT | |
| Owner: { $owner } | |
| Time: { $time } | |
| forensic-scanner-chat-history-printed = Chat history printed | |
| forensic-scanner-no-cartridge = No cartridges found in the PDA | |
| forensic-scanner-no-chat-cartridge = No chat cartridges found in the PDA | |
| forensic-scanner-chat-cartridge-header = === Chat cartridge: { $id } === | |
| forensic-scanner-chat-no-data = No chat data available | |
| forensic-scanner-chat-with-contact = Chat with: { $name } ({ $id }) | |
| forensic-scanner-chat-with-unknown = Chat with: Unknown ({ $id }) | |
| forensic-scanner-chat-contact-no-messages = Contact: { $name } ({ $id }) - no messages | |
| forensic-scanner-chat-groups-header = GROUP CHATS | |
| forensic-scanner-chat-group-info = Group: { $name } (ID: { $id }, Members: { $members }) | |
| forensic-scanner-chat-group-messages = Group messages: | |
| forensic-scanner-chat-group-no-messages = No messages in group | |
| forensic-scanner-chat-archived-group = Archived group: { $id } | |
| forensic-scanner-chat-you = You |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@Resources/Locale/en-US/_Wega/forensics/forensics.ftl` around lines 1 - 27,
The en-US locale file contains Russian strings for multiple keys (e.g.,
forensic-scanner-print-chat-history-text,
forensic-scanner-print-chat-history-message,
forensic-scanner-chat-history-title, forensic-scanner-chat-history-header,
forensic-scanner-chat-history-printed, forensic-scanner-no-cartridge,
forensic-scanner-no-chat-cartridge, forensic-scanner-chat-cartridge-header,
forensic-scanner-chat-no-data, forensic-scanner-chat-with-contact,
forensic-scanner-chat-with-unknown, forensic-scanner-chat-contact-no-messages,
forensic-scanner-chat-groups-header, forensic-scanner-chat-group-info,
forensic-scanner-chat-group-messages, forensic-scanner-chat-group-no-messages,
forensic-scanner-chat-archived-group, forensic-scanner-chat-you); replace each
Russian value with the correct English copy (or copy from the default/English
source) so en-US users see English UI and report text, preserving the same
message keys and interpolation placeholders like { $owner }, { $time }, { $id },
{ $name }, and { $members } exactly.
| nano-chat-ui-add = Добавить | ||
| nano-chat-ui-add-contact-title = Добавить контакт | ||
| nano-chat-ui-add-contact-tooltip = Добавить контакт | ||
|
|
||
| nano-chat-ui-cancel = Отмена | ||
|
|
||
| nano-chat-ui-close-chat-tooltip = Закрыть чат | ||
|
|
||
| nano-chat-ui-contact-id = ID контакта | ||
| nano-chat-ui-contact-id-placeholder = Введите ID контакта(5 символов)... | ||
| nano-chat-ui-contact-name = Имя контакта | ||
| nano-chat-ui-contact-name-placeholder = Введите имя контакта(9 символов)... | ||
|
|
||
| nano-chat-ui-create = Создать | ||
| nano-chat-ui-create-group-title = Создать группу | ||
| nano-chat-ui-create-group-tooltip = Создать новую группу | ||
|
|
||
| nano-chat-ui-emoji-select = Выберите эмодзи: | ||
| nano-chat-ui-emoji-title = Выбор эмодзи | ||
| nano-chat-ui-emoji-tooltip = Эмодзи | ||
|
|
||
| nano-chat-ui-erase-chat-tooltip = Стереть контакт | ||
|
|
||
| nano-chat-ui-group-id = ID группы | ||
| nano-chat-ui-group-id-placeholder = Введите ID группы(5 символов)... | ||
| nano-chat-ui-group-name = Название группы | ||
| nano-chat-ui-group-name-placeholder = Введите название группы(16 символов)... | ||
|
|
||
| nano-chat-ui-join = Присоединиться | ||
| nano-chat-ui-join-group-title = Присоединиться к группе | ||
| nano-chat-ui-join-group-tooltip = Присоединиться к группе | ||
|
|
||
| nano-chat-ui-leave-chat-tooltip = Покинуть группу | ||
|
|
||
| nano-chat-ui-message-placeholder = Введите сообщение... | ||
|
|
||
| nano-chat-ui-mute-tooltip = Отключить звук | ||
|
|
||
| nano-chat-ui-no-contacts = Нет контактов | ||
| nano-chat-ui-no-groups = Нет групп | ||
|
|
||
| nano-chat-ui-select-chat = Выберите чат | ||
| nano-chat-ui-select-chat-input = Выберите чат для сообщения | ||
|
|
||
| nano-chat-ui-send-tooltip = Отправить | ||
|
|
||
| nano-chat-ui-tab-contacts = Конт. | ||
| nano-chat-ui-tab-groups = Групп. | ||
|
|
||
| nano-chat-ui-title = NanoChat | ||
|
|
||
| nano-chat-ui-unmute-tooltip = Включить звук | ||
|
|
||
| nano-chat-ui-unread-indicator = (У) | ||
|
|
||
| # System | ||
| nano-chat-create-group-message = {$name} создал группу '{$groupName}'. | ||
| nano-chat-join-message = -> {$name} присоединился! | ||
| nano-chat-leave-message = <- {$name} покинул группу. |
There was a problem hiding this comment.
Populate the en-US locale with English strings.
This file lives under en-US, but every added value is still Russian. English clients will ship the feature with untranslated UI because this file already claims to satisfy the locale.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@Resources/Locale/en-US/_Wega/nanochat/nanochatui.ftl` around lines 2 - 60,
This file under en-US contains Russian strings—replace each Russian value with
its English translation so the en-US locale actually provides English text;
update all keys such as nano-chat-ui-add, nano-chat-ui-add-contact-title,
nano-chat-ui-contact-id-placeholder, nano-chat-ui-contact-name-placeholder,
nano-chat-ui-create-group-title, nano-chat-ui-emoji-title,
nano-chat-ui-group-name-placeholder, nano-chat-ui-join-group-title,
nano-chat-ui-message-placeholder, nano-chat-ui-tab-contacts, nano-chat-ui-title,
nano-chat-ui-unread-indicator and the system messages
nano-chat-create-group-message, nano-chat-join-message, nano-chat-leave-message
to appropriate English phrases (including translating placeholders and
preserving interpolation variables like {$name} and {$groupName}). Ensure
punctuation and placeholder formats are kept intact.
| @@ -0,0 +1 @@ | |||
| nano-chat-program-name = НаноЧат | |||
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Check directory naming patterns in locale folders
fd -t d -i 'wega' Resources/Locale/Repository: corvax-team/ss14-wl
Length of output: 220
🏁 Script executed:
#!/bin/bash
# Check contents of each directory
echo "=== en-US/_Wega/ ==="
ls -la Resources/Locale/en-US/_Wega/ 2>/dev/null | head -20
echo -e "\n=== ru-RU/_wega/ ==="
ls -la Resources/Locale/ru-RU/_wega/ 2>/dev/null | head -20
echo -e "\n=== en-US/ss14-ru/prototypes/_wega/ ==="
ls -la Resources/Locale/en-US/ss14-ru/prototypes/_wega/ 2>/dev/null | head -20
echo -e "\n=== ru-RU/ss14-ru/prototypes/_wega/ ==="
ls -la Resources/Locale/ru-RU/ss14-ru/prototypes/_wega/ 2>/dev/null | head -20
# Check if there are any case-sensitive references in code
echo -e "\n=== Checking for hardcoded path references ==="
rg '_Wega|_wega' --type yaml --type xml --type json Resources/ | grep -v "\.ftl" | head -20Repository: corvax-team/ss14-wl
Length of output: 1301
🏁 Script executed:
#!/bin/bash
# Search for references to wega paths in code files (not just prototypes)
echo "=== References to _Wega or _wega in code ==="
rg -i 'wega' --type yaml --type rust --type csharp --type json -B 2 -A 2 | head -60
# Check if there's any build or loading logic that references these paths
echo -e "\n=== Checking for directory/path loading references ==="
rg 'Locale|locale' --type rust --type csharp -i | grep -i 'path\|load\|directory' | head -20
# Check the actual file in question for context
echo -e "\n=== Content of cartridges.ftl files ==="
find Resources/Locale -name "cartridges.ftl" -exec echo "=== {} ===" \; -exec cat {} \;Repository: corvax-team/ss14-wl
Length of output: 18399
Align directory naming conventions across locales.
The Russian locale uses lowercase _wega (ru-RU/_wega/) while the English locale uses _Wega (en-US/_Wega/) at the top level. This inconsistency can cause issues on case-sensitive filesystems (Linux) and makes the codebase harder to maintain. Ensure both locales use the same casing convention.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@Resources/Locale/ru-RU/_wega/cartridge-loader/cartridges.ftl` at line 1, The
Russian locale directory uses `_wega` while English uses `_Wega`; rename the
Russian directory to match the established casing (change `_wega` -> `_Wega`)
and update all references; specifically move/rename the directory containing
cartridges.ftl and update any config, import, or build references that point to
`_wega` so all locales use the same `_Wega` casing (use git mv to preserve
history and run tests/build to verify).
| sample: | ||
| filter: true |
There was a problem hiding this comment.
Incorrect filename — appears to be an accidental copy.
The filename sound.svg.192dpi.png copy.yml contains " copy" which suggests this file was accidentally created when duplicating another file. The correct filename should be sound.svg.192dpi.png.yml (without " copy") to properly associate with the corresponding PNG texture.
Please either remove this file if it's a duplicate, or rename it to match the intended texture asset.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@Resources/Textures/_Wega/Interface/sound.svg.192dpi.png` copy.yml around
lines 1 - 2, The file named "sound.svg.192dpi.png copy.yml" appears to be an
accidental duplicate; either delete this file if it's redundant or rename it to
"sound.svg.192dpi.png.yml" so it matches the intended texture asset, ensuring
the YAML contents remain unchanged and any references to the texture in the
project point to the corrected filename.
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@Resources/Prototypes/InventoryTemplates/aghost_inventory_template.yml`:
- Line 53: The uiWindowPos for pocket1 conflicts with the eyes slot (both set to
0,3); update the pocket1 entry's uiWindowPos value to a non-overlapping
coordinate (e.g., shift row or column such as 0,4 or 1,3) so it no longer shares
the same position as the eyes slot; locate the pocket1 definition and change its
uiWindowPos accordingly while keeping the eyes uiWindowPos unchanged.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: ec1a08d6-96b8-46c4-b3c0-192a852b29b3
📒 Files selected for processing (1)
Resources/Prototypes/InventoryTemplates/aghost_inventory_template.yml
Описание PR
Добавлена система NanoChat - чат-картридж для КПК, позволяющий игрокам обмениваться текстовыми сообщениями в пределах станции. Реализован полный цикл: от добавления контактов до отправки сообщений с учетом различных помех.
Код взят и лицензирован на wega-team/ss14-wega#174 и wega-team/ss14-wega#179 (GPLv3)
Ну ещё кармашки агосту дал, тоже попросили.
Почему / Баланс
Альтернатива радио
Технические детали
Я ленивый, меня просто попросили портировать
Медиа
Требования
Согласие с условиями
Критические изменения
Отсутствуют
Список изменений
🆑 Evgencheg, Zekins
Summary by CodeRabbit
New Features
Chores