Skip to content
Open
Show file tree
Hide file tree
Changes from 12 commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
61242d6
экспорт схемы в json
kkadmor Jan 17, 2026
af7d70b
кнопка импорта в принтере плат
kkadmor Jan 17, 2026
9f3e797
Рабочий импорт
kkadmor Jan 17, 2026
513e377
добавлены вкладки
kkadmor Jan 25, 2026
c568d19
добавление в бд (не тестил)
kkadmor Jan 25, 2026
64ff86a
Промежуточный
kkadmor Jan 26, 2026
9579f06
промежуточный2
kkadmor Jan 28, 2026
dbe516a
Сохранение с HMAC (без конфига на HMAC ключ)
kkadmor Jan 30, 2026
8dc93e4
Загрузка ключа HMAC через конфиг
kkadmor Jan 30, 2026
cd8fdcb
Убрал все насеры, которые остались после попыток в бд
kkadmor Jan 30, 2026
382acf4
ретурн тру в уи акт
kkadmor Jan 30, 2026
17fa8a2
max_length при 0 не становится бесконечным, убираю
kkadmor Jan 30, 2026
4330cb8
Исправил проверку импорта
kkadmor Jan 30, 2026
c692dd3
Поменял проверку json
kkadmor Jan 30, 2026
1a8b313
микроправки
kkadmor Jan 30, 2026
3f70cbd
еще правки
kkadmor Jan 30, 2026
f47371f
Merge branch 'master220' into import-and-export-integrated-circuitpri…
kkadmor Jan 30, 2026
7bce7a2
фикс тгуи
kkadmor Jan 30, 2026
880f026
фикс для линтера
kkadmor Jan 30, 2026
56e0689
fix
kkadmor Jan 30, 2026
0204376
правки
kkadmor Jan 30, 2026
c2b428d
значение hmac ключа удаляются после использования
kkadmor Jan 30, 2026
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
8 changes: 8 additions & 0 deletions code/__HELPERS/text.dm
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,14 @@
for(var/i in 1 to times)
. += string

// Создает строку используя список из ASCII
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

🟡 Рекомендация по стилю: Комментарий в коде должен быть на английском языке. Это требование стайлгайда (пункт 4.1, строка 68) для обеспечения согласованности кодовой базы.

// Creates a string using a list of ASCII codes
References
  1. Комментарии в коде должны быть на английском. (link)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

🟡 Рекомендация по стилю: Согласно стайлгайду (пункт 4.1, правило о комментариях), все комментарии в коде должны быть на английском языке.

// Creates a string using a list of ASCII codes
References
  1. Комментарии должны быть на английском языке и объяснять 'почему', а не 'что'. (link)

/proc/ascii_list2text(list/ascii_text)
. = ""
for(var/ascii_char in ascii_text)
if(ascii_char < 32 || ascii_char > 127)
CRASH("ascii_list2text: invalid ASCII code")
. += ascii2text(ascii_char)

/// Runs sanitize and strip_html_simple
/// I believe strip_html_simple() is required to run first to prevent '<' from displaying as '&lt;' after sanitize() calls byond's html_encode()
/proc/strip_html(t, limit=MAX_MESSAGE_LEN)
Expand Down
2 changes: 2 additions & 0 deletions code/controllers/configuration/entries/config.dm
Original file line number Diff line number Diff line change
Expand Up @@ -856,4 +856,6 @@
/datum/config_entry/flag/smart_cache_assets
default = TRUE

/datum/config_entry/string/hmac_key

/datum/config_entry/flag/generate_assets_in_init
109 changes: 109 additions & 0 deletions code/modules/research/circuitprinter.dm
Original file line number Diff line number Diff line change
Expand Up @@ -309,6 +309,55 @@ using metal and glass, it uses glass and reagents (usually sulfuric acis).

update_static_data_for_all_viewers()

// Проверяет наличие всех данных
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

🟡 Рекомендация по стилю: Комментарий в коде должен быть на английском языке. Это требование стайлгайда (пункт 4.1, строка 68) для обеспечения согласованности кодовой базы.

// Checks for presence of all data
References
  1. Комментарии в коде должны быть на английском. (link)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

🟡 Рекомендация по стилю: Согласно стайлгайду (пункт 4.1, правило о комментариях), все комментарии в коде должны быть на английском языке. В этом файле несколько таких комментариев.

// Checks if all data is present
References
  1. Комментарии должны быть на английском языке и объяснять 'почему', а не 'что'. (link)

/obj/machinery/r_n_d/circuit_imprinter/proc/can_save_circuit_by_json(mob/living/user, json_data)
var/list/data
if(json_data)
data = json_decode(json_data)
else
return FALSE

if(!linked_console)
balloon_alert(user, "не привязано к консоли!")
return FALSE

if(!LAZYACCESS(data, "dupe_data"))
return FALSE

if(!LAZYACCESS(data, "name"))
return FALSE

if(!LAZYACCESS(data, "materials"))
return FALSE

if(!LAZYACCESS(data, "integrated_circuit"))
return FALSE

if(!LAZYACCESS(data, "Icon"))
return FALSE

if(!LAZYACCESS(data, "IconState"))
return FALSE

if(!LAZYACCESS(data, "desc"))
return FALSE
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

🟡 Рекомендация по улучшению: Блок проверки полей JSON содержит много однотипных проверок. Для улучшения читаемости и упрощения дальнейшей поддержки кода, этот блок можно отрефакторить, используя цикл по списку обязательных ключей.

	var/list/required_keys = list("dupe_data", "name", "materials", "integrated_circuit", "Icon", "IconState", "desc")
	for(var/key in required_keys)
		if(!LAZYACCESS(data, key))
			return FALSE


return TRUE
Comment on lines 313 to 321
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

🟡 Рекомендация: Обнаружена потенциальная ошибка выполнения в процедуре can_save_circuit_by_json. Функция json_decode() может вернуть null, если json_data содержит некорректный JSON. В этом случае последующее обращение к data в цикле for вызовет ошибку. Рекомендую добавить проверку if(!islist(data)) return FALSE после вызова json_decode() для предотвращения падения сервера.

/obj/machinery/r_n_d/circuit_imprinter/proc/can_save_circuit_by_json(mob/living/user, json_data)
	var/list/data = json_decode(json_data)
	if(!islist(data))
		return FALSE
	var/list/required_keys = list("dupe_data", "name", "materials", "integrated_circuit", "Icon", "IconState", "desc")
	for(var/key in required_keys)
		if(!LAZYACCESS(data, key))
			return FALSE
	return TRUE
References
  1. Требовать проверку на null (if(!variable)) перед использованием объекта, особенно после locate(), find_in_view() и т.п. (link)


// Для иморта платы
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

🟡 Рекомендация по стилю: Комментарий в коде должен быть на английском языке. Это требование стайлгайда (пункт 4.1, строка 68) для обеспечения согласованности кодовой базы.

// For board import
References
  1. Комментарии в коде должны быть на английском. (link)

/obj/machinery/r_n_d/circuit_imprinter/proc/save_circuit_by_json(mob/living/user, json_data)
if(!can_save_circuit_by_json(user, json_data))
return

var/list/data = json_decode(json_data)
Comment on lines +324 to +328
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

🟡 Рекомендация по производительности: Процедура save_circuit_by_json вызывает can_save_circuit_by_json, которая уже выполняет json_decode. Затем save_circuit_by_json выполняет json_decode повторно. Это избыточно и немного снижает производительность. Рекомендуется передавать уже декодированные данные из can_save_circuit_by_json или изменить логику, чтобы избежать двойного декодирования.


LAZYADD(scanned_designs, list(data))

balloon_alert(user, "схема сохранена")
playsound(src, 'sound/machines/ping.ogg', 50)

update_static_data_for_all_viewers()

/obj/machinery/r_n_d/circuit_imprinter/proc/print_module(list/design)
flick("[base_icon_state]_ani", src)

Expand Down Expand Up @@ -376,6 +425,66 @@ using metal and glass, it uses glass and reagents (usually sulfuric acis).
LAZYREMOVE(scanned_designs, design)

update_static_data_for_all_viewers()
if("export")
var/design_id = text2num(params["designId"])

if(design_id < 1 || design_id > length(scanned_designs))
return TRUE

var/list/design = LAZYACCESS(scanned_designs, design_id)

var/mob/user= ui.user

var/json_data = json_encode(design)

var/hmac_key = CONFIG_GET(string/hmac_key) ? CONFIG_GET(string/hmac_key) : ""

// Создает HMAC если ключ корректен
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

🟡 Рекомендация по стилю: Комментарий в коде должен быть на английском языке. Это требование стайлгайда (пункт 4.1, строка 68) для обеспечения согласованности кодовой базы.

			// Creates HMAC if the key is correct
References
  1. Комментарии в коде должны быть на английском. (link)

var/hmac_base64 = ""
if(validate_hmac_key(hmac_key))
hmac_base64 = hmac_md5_base64(hmac_key, json_data)

if(!hmac_base64)
tgui_alert(user, "Ошибка создания электронной подписи!", "Ошибка экспорта")
return TRUE

var/json_base64 = rustg_encode_base64(json_data)
var/final_text = rustg_encode_base64("[json_base64].[hmac_base64]")

// Окно в вводом из которого можно скопировать текст
tgui_input_text(user, "Скопируйте текст схемы:", "Экспорт схемы", default = final_text, max_length=999999999)

if("import")
var/mob/user = ui.user

var/text = tgui_input_text(user, "Вставьте текст интегральной платы", "Импорт схемы", encode=FALSE, max_length = 999999999)
text = ascii_list2text(rustlib_decode_base64(text)) ? ascii_list2text(rustlib_decode_base64(text)) : ""

var/hmac_key = CONFIG_GET(string/hmac_key) ? CONFIG_GET(string/hmac_key) : ""

if(!text || findtext(text, "."))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

critical

🔴 Критическая ошибка: Условие if(!text || findtext(text, ".")) некорректно. Оно приводит к тому, что любая корректная строка для импорта (которая должна содержать точку в качестве разделителя) будет отклонена. findtext возвращает позицию, и если точка найдена, условие становится истинным, что вызывает ошибку. Условие следует инвертировать, чтобы проверять отсутствие точки.

			if(!text || !findtext(text, "."))

tgui_alert(user, "Ошибка расшифровки. Убедитесь в корректности данных", "Ошибка импорта")
return TRUE

var/list/parts = splittext(text, ".") // [1] - json в виде списка ASCII [2] - HMAC в виде списка ASCII

var/json_data = ascii_list2text(rustlib_decode_base64(parts[1])) ? ascii_list2text(rustlib_decode_base64(parts[1])) : ""
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

🟡 Рекомендация по улучшению: Аналогично предыдущему замечанию, вызов ascii_list2text(rustlib_decode_base64(parts[1])) повторяется. Рекомендуется сохранить результат в переменную.

			var/list/decoded_json_b64 = rustlib_decode_base64(parts[1])
			var/json_data = decoded_json_b64 ? ascii_list2text(decoded_json_b64) : ""


if(!json_data)
tgui_alert(user, "Некорректный JSON! Проверьте корректность данных.", "Ошибка импорта")
return TRUE

// Создает HMAC если ключ корректен
var/hmac_base64 = ""
if(validate_hmac_key(hmac_key) && json_data)
hmac_base64 = hmac_md5_base64(hmac_key, json_data)

// Сравнивает переданный и полученный HMAC
if(parts[2] != hmac_base64)
tgui_alert(user, "Ошибка электронной подписи! Проверьте корректность данных.", "Ошибка импорта")
return TRUE

save_circuit_by_json(user, json_data)

return TRUE

Expand Down
104 changes: 104 additions & 0 deletions code/modules/wiremod/core/hmac.dm
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
#define HMAC_BLOCK_SIZE 64
#define HMAC_IPAD 54 // ASCII "6"
#define HMAC_OPAD 92 // ASCII "\"

/*
* Основная функция HMAC-MD5
* hex_key - ключ в hex-формате
* data - данные для подписи
*/
Comment on lines +5 to +9
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

🟡 Рекомендация по стилю: Согласно стайлгайду (пункт 4.1, правило о комментариях), все комментарии в коде должны быть на английском языке.

/*
 * Main HMAC-MD5 function
 * hex_key - key in hex format
 * data - data to be signed
 */
References
  1. Комментарии должны быть на английском языке и объяснять 'почему', а не 'что'. (link)

/proc/hmac_md5_hex(hex_key, data)
var/key_bin = hex_to_bin(hex_key)

if(!key_bin)
CRASH("hmac_md5_hex: Invalid hex key for HMAC")

if(length(key_bin) > HMAC_BLOCK_SIZE)
key_bin = hex_to_bin(md5(key_bin))

if(length(key_bin) < HMAC_BLOCK_SIZE)
key_bin += repeat_string(HMAC_BLOCK_SIZE - length(key_bin), "0")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

critical

🔴 Критическая ошибка безопасности: Ключ HMAC дополняется символом '0' (ascii2text(48)) вместо нулевого байта (ascii2text(0)). Согласно стандарту HMAC, дополнение должно производиться нулевыми байтами. Эта ошибка приводит к генерации неверной HMAC-подписи, что полностью нарушает механизм проверки целостности данных.

        key_bin += repeat_string(HMAC_BLOCK_SIZE - length(key_bin), ascii2text(0))

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Нулевой байт не добавляет ничего к строке и она никак не меняется. Поэтому "0"


var/ipad = repeat_string(HMAC_BLOCK_SIZE, ascii2text(HMAC_IPAD))
var/opad = repeat_string(HMAC_BLOCK_SIZE, ascii2text(HMAC_OPAD))

var/i_key = xor_strings(key_bin, ipad)
var/o_key = xor_strings(key_bin, opad)

var/inner_hash = md5(i_key + data)

var/inner_hash_bin = hex_to_bin(inner_hash)
var/hmac_hex = md5(o_key + inner_hash_bin)

return hmac_hex

/proc/hmac_md5_base64(hex_key, data)
var/hmac_hex = hmac_md5_hex(hex_key, data)
var/hmac_bin = hex_to_bin(hmac_hex)
return rustg_encode_base64(hmac_bin)

/proc/xor_strings(a, b)
if(length(a) != length(b))
CRASH("XOR: strings of different lengths ([length(a)] vs [length(b)])")

var/result = ""
for(var/i = 1; i <= length(a); i++)
var/byte_a = text2ascii(copytext(a, i, i+1))
var/byte_b = text2ascii(copytext(b, i, i+1))
result += ascii2text(byte_a ^ byte_b)

return result

/proc/hex_to_bin(hex_string)
if(!istext(hex_string))
return null

hex_string = replacetext(hex_string, " ", "")
hex_string = replacetext(hex_string, "\n", "")
hex_string = replacetext(hex_string, "\t", "")

if(length(hex_string) % 2 != 0)
hex_string = "0" + hex_string

var/bin = ""
for(var/i = 1; i <= length(hex_string); i += 2)
var/hex_pair = copytext(hex_string, i, i+2)
var/num = hex2num(hex_pair)

if(isnull(num))
return null

bin += ascii2text(num)

return bin

/proc/bin_to_hex(bin_string)
if(!istext(bin_string))
return ""

var/hex = ""
for(var/i = 1; i <= length(bin_string); i++)
var/byte = text2ascii(copytext(bin_string, i, i+1))
hex += num2hex(byte, 2)

return lowertext(hex)

/proc/validate_hmac_key(hex_key)
if(!istext(hex_key))
return FALSE

var/len = length(hex_key)
if(len != 40 && len != 64)
return FALSE
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

🟡 Рекомендация по улучшению: Функция validate_hmac_key проверяет длину ключа на равенство 40 или 64 символам. В описании пулл-реквеста и в файле конфигурации упоминается только длина в 40 символов. Причина для поддержки 64-символьной длины не ясна. Рекомендуется либо добавить комментарий, объясняющий эту логику, либо убрать проверку на 64 символа для соответствия документации.


var/valid_chars = "0123456789abcdefABCDEF"
for(var/i = 1; i <= len; i++)
var/char = copytext(hex_key, i, i+1)
if(!findtext(valid_chars, char))
return FALSE

return TRUE

#undef HMAC_BLOCK_SIZE
#undef HMAC_IPAD
#undef HMAC_OPAD
5 changes: 5 additions & 0 deletions config/example/config.txt
Original file line number Diff line number Diff line change
Expand Up @@ -520,4 +520,9 @@ CACHE_ASSETS 0
## This config option limits the maximum chunk count for which the server will accept a payload, default is 32
TGUI_MAX_CHUNK_COUNT 128

## HMAC key for encode integrated circuit
## Written as hex in a string
## Size - 40 characters
HMAC_KEY ffffffffffffffffffffffffffffffffffffffff

### INITIALIZATION SETTINGS END ###
1 change: 1 addition & 0 deletions paradise.dme
Original file line number Diff line number Diff line change
Expand Up @@ -3840,6 +3840,7 @@
#include "code\modules\wiremod\core\component.dm"
#include "code\modules\wiremod\core\datatypes.dm"
#include "code\modules\wiremod\core\duplicator.dm"
#include "code\modules\wiremod\core\hmac.dm"
#include "code\modules\wiremod\core\integrated_circuit.dm"
#include "code\modules\wiremod\core\marker.dm"
#include "code\modules\wiremod\core\port.dm"
Expand Down
64 changes: 46 additions & 18 deletions tgui/packages/tgui/interfaces/ComponentPrinter.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -31,14 +31,25 @@ export const ComponentPrinter = (props) => {
return (
<Window title={'Дубликатор печатных плат'} width={670} height={600}>
<Window.Content>
<Section
title="Сохранённые схемы"
buttons={
<Button
icon="file-import"
onClick={() => act('import')}
>
{toTitleCase("Import")}
</Button>
}
/>
<Box>
{Object.values(designs).length === 0 && (
<Stack.Item mt={1} fontSize={1}>
<NoticeBox info>Сохранённые схемы отсутствуют.</NoticeBox>
</Stack.Item>
)}
{Object.values(designs).map((design) => (
<Section key={design.id}>
<Section key={design.id} style={{ position: 'relative'}}>
<DmIcon
icon={design.icon}
icon_state={design.IconState}
Expand All @@ -62,23 +73,40 @@ export const ComponentPrinter = (props) => {
{toTitleCase(design.name)}
</Button>

{(design.cost &&
Object.keys(design.cost)
.map((mat) => toTitleCase(mat) + ': ' + design.cost[mat])
.join(', ')) || <Box>Ресурсы для печати не требуются.</Box>}

<Button
mr={1}
icon="trash-can"
position="absolute"
right={1}
top={1}
onClick={() =>
act('del_design', {
designId: design.id,
})
}
/>
<Box style={{ display: 'inline' }}>
{(design.cost &&
Object.keys(design.cost)
.map((mat) => toTitleCase(mat) + ': ' + design.cost[mat])
.join(', ')) || <Box>Ресурсы для печати не требуются.</Box>}
</Box>
<Box
style={{
position: 'absolute',
right: '8px',
top: '8px',
display: 'flex',
gap: '5px',
}}
>
<Button
icon="save"
onClick={() =>
act('export', {
designId: design.id,
})
}
>
{toTitleCase("Export")}
</Button>
<Button
icon="trash-can"
onClick={() =>
act('del_design', {
designId: design.id,
})
}
/>
</Box>
</Section>
))}
</Box>
Expand Down
2 changes: 1 addition & 1 deletion tgui/public/tgui.bundle.js

Large diffs are not rendered by default.