diff --git a/.gitattributes b/.gitattributes index f540426f..cb9c6b8a 100644 --- a/.gitattributes +++ b/.gitattributes @@ -7,62 +7,63 @@ # ============================================ # Minecraft Datapack – Force LF # ============================================ -*.mcfunction text eol=lf -*.json text eol=lf diff=json -*.mcmeta text eol=lf diff=json -*.snbt text eol=lf -*.md text eol=lf -*.txt text eol=lf -*.nbt binary +*.mcfunction text eol=lf +*.json text eol=lf diff=json +*.mcmeta text eol=lf diff=json +*.snbt text eol=lf +*.md text eol=lf +*.txt text eol=lf +*.nbt binary *.mcstructure binary -*.dat binary -*.dat_old binary -*.mca binary -*.mcc binary -*.region binary +*.dat binary +*.dat_old binary +*.mca binary +*.mcc binary +*.region binary # ============================================ # Shader Files (Resource Pack compatibility) # ============================================ -*.glsl text eol=lf -*.vsh text eol=lf -*.fsh text eol=lf -*.vert text eol=lf -*.frag text eol=lf -*.geom text eol=lf -*.comp text eol=lf -*.hlsl text eol=lf +*.glsl text eol=lf +*.vsh text eol=lf +*.fsh text eol=lf +*.vert text eol=lf +*.frag text eol=lf +*.geom text eol=lf +*.comp text eol=lf +*.hlsl text eol=lf # ============================================ # Config & Build Files # ============================================ -*.yml text eol=lf -*.yaml text eol=lf -*.toml text eol=lf -*.properties text eol=lf -*.env text eol=lf -*.ini text eol=lf -*.cfg text eol=lf -.gitignore text eol=lf +*.yml text eol=lf +*.yaml text eol=lf +*.toml text eol=lf +*.properties text eol=lf +*.env text eol=lf +*.ini text eol=lf +*.cfg text eol=lf +.gitignore text eol=lf .gitattributes text eol=lf -Makefile text eol=lf -Dockerfile text eol=lf +Makefile text eol=lf +Dockerfile text eol=lf # ============================================ # Script & Web Files # ============================================ -*.py text eol=lf -*.sh text eol=lf -*.bash text eol=lf -*.js text eol=lf -*.ts text eol=lf -*.jsx text eol=lf -*.tsx text eol=lf -*.html text eol=lf -*.css text eol=lf -*.scss text eol=lf -*.xml text eol=lf diff=xml -*.svg text eol=lf +*.py text eol=lf +*.sh text eol=lf +*.java text eol=lf +*.bash text eol=lf +*.js text eol=lf +*.ts text eol=lf +*.jsx text eol=lf +*.tsx text eol=lf +*.html text eol=lf +*.css text eol=lf +*.scss text eol=lf +*.xml text eol=lf diff=xml +*.svg text eol=lf # ============================================ # Windows-specific (optional) @@ -74,87 +75,87 @@ Dockerfile text eol=lf # ============================================ # Document & Office Files # ============================================ -*.pdf binary -*.doc binary -*.docx binary -*.xls binary -*.xlsx binary -*.ppt binary -*.pptx binary -*.odt binary -*.ods binary -*.odp binary -*.csv text eol=lf +*.pdf binary +*.doc binary +*.docx binary +*.xls binary +*.xlsx binary +*.ppt binary +*.pptx binary +*.odt binary +*.ods binary +*.odp binary +*.csv text eol=lf # ============================================ # Binary Assets – Images # ============================================ -*.png binary -*.jpg binary -*.jpeg binary -*.gif binary -*.webp binary -*.ico binary -*.bmp binary -*.tga binary -*.tiff binary -*.psd binary -*.xcf binary -*.hdr binary -*.exr binary +*.png binary +*.jpg binary +*.jpeg binary +*.gif binary +*.webp binary +*.ico binary +*.bmp binary +*.tga binary +*.tiff binary +*.psd binary +*.xcf binary +*.hdr binary +*.exr binary # ============================================ # Audio & Video # ============================================ -*.ogg binary -*.mp3 binary -*.wav binary -*.flac binary -*.aac binary -*.opus binary -*.mp4 binary -*.webm binary -*.avi binary -*.mkv binary +*.ogg binary +*.mp3 binary +*.wav binary +*.flac binary +*.aac binary +*.opus binary +*.mp4 binary +*.webm binary +*.avi binary +*.mkv binary # ============================================ # 3D / Font Assets # ============================================ -*.obj binary -*.mtl binary -*.fbx binary -*.gltf text eol=lf diff=json -*.glb binary -*.ttf binary -*.otf binary -*.woff binary +*.obj binary +*.mtl binary +*.fbx binary +*.gltf text eol=lf diff=json +*.glb binary +*.ttf binary +*.otf binary +*.woff binary *.woff2 binary # ============================================ # Archives & Compiled # ============================================ -*.zip binary -*.rar binary -*.7z binary -*.tar binary -*.gz binary -*.jar binary -*.exe binary -*.dll binary -*.so binary -*.dylib binary -*.class binary -*.pyc binary -*.mcpack binary +*.zip binary +*.rar binary +*.7z binary +*.tar binary +*.gz binary +*.jar binary +*.exe binary +*.dll binary +*.so binary +*.dylib binary +*.class binary +*.pyc binary +*.mcpack binary *.mcworld binary *.mcaddon binary # ============================================ # Lock files – text, diffable # ============================================ -*.lock text eol=lf +*.lock text eol=lf package-lock.json text eol=lf diff=json -yarn.lock text eol=lf +yarn.lock text eol=lf # ============================================ # Git diff drivers diff --git a/datapacks/dataLib/data/datalib.main/function/datalib/load.mcfunction b/datapacks/dataLib/data/datalib.main/function/datalib/load.mcfunction index e0aefeec..16086849 100644 --- a/datapacks/dataLib/data/datalib.main/function/datalib/load.mcfunction +++ b/datapacks/dataLib/data/datalib.main/function/datalib/load.mcfunction @@ -2,4 +2,4 @@ execute if data storage datalib:engine {global:{loaded:1b}} run return 0 -function #load:_private/load +function #load:_private/load \ No newline at end of file diff --git a/datapacks/dataLib/data/datalib/function/input/_private/book_capture.mcfunction b/datapacks/dataLib/data/datalib/function/input/_private/book_capture.mcfunction index 744a66ed..0fa0ac95 100644 --- a/datapacks/dataLib/data/datalib/function/input/_private/book_capture.mcfunction +++ b/datapacks/dataLib/data/datalib/function/input/_private/book_capture.mcfunction @@ -5,6 +5,11 @@ # Runs with @s bound to a single player holding the marked written_book. # Extracts SelectedItem.components."minecraft:writable_book_content".pages[0].raw # into datalib:input storage. Read-only capture, no execution. +# +# book.raw is a RAW, UNVALIDATED string — same contract as cbm.command and +# dialog.raw. If the caller needs it as a number/bool/tag-safe literal, +# run it through datalib:input/validate/check first: +# function datalib:input/validate/check with storage {source:"book.raw", type:"int"} # ====================================================================================== data modify storage datalib:input book.player set from entity @s UUID diff --git a/datapacks/dataLib/data/datalib/function/input/_private/cbm_capture.mcfunction b/datapacks/dataLib/data/datalib/function/input/_private/cbm_capture.mcfunction index 18bcd5aa..2e36eee6 100644 --- a/datapacks/dataLib/data/datalib/function/input/_private/cbm_capture.mcfunction +++ b/datapacks/dataLib/data/datalib/function/input/_private/cbm_capture.mcfunction @@ -25,6 +25,10 @@ data modify storage datalib:input cbm.pos set from entity @s Pos # must run this through datalib:core/security/sanitize and # datalib:debug/tools/utils/input_check before ever treating it as # runnable, and running it there stays optional, never mandatory. +# +# Separately, if a caller wants cbm.command as a number/bool/tag-safe +# literal (not as a runnable command), use datalib:input/validate/check: +# function datalib:input/validate/check with storage {source:"cbm.command", type:"int"} data modify storage datalib:input cbm.executed set value 0b execute if data storage datalib:input cbm{executed:0b} run function #datalib:input/command_block_minecart diff --git a/datapacks/dataLib/data/datalib/function/input/_private/dialog_capture.mcfunction b/datapacks/dataLib/data/datalib/function/input/_private/dialog_capture.mcfunction index 2855e85a..98d86ca0 100644 --- a/datapacks/dataLib/data/datalib/function/input/_private/dialog_capture.mcfunction +++ b/datapacks/dataLib/data/datalib/function/input/_private/dialog_capture.mcfunction @@ -13,6 +13,9 @@ # asked to avoid — no field here is ever run as a command, only stored. # ====================================================================================== +# dialog.raw is RAW, UNVALIDATED text. Run it through +# datalib:input/validate/check before treating it as a number/bool/tag-safe +# literal: function datalib:input/validate/check with storage {source:"dialog.raw", type:"int"} data modify storage datalib:input dialog.raw set value "$(value)" data modify storage datalib:input dialog.executed set value 0b execute if data storage datalib:input dialog{executed:0b} run function #datalib:input/dialog \ No newline at end of file diff --git a/datapacks/dataLib/data/datalib/function/input/summon_cbm.mcfunction b/datapacks/dataLib/data/datalib/function/input/summon_cbm.mcfunction index 085781e6..c89fecbf 100644 --- a/datapacks/dataLib/data/datalib/function/input/summon_cbm.mcfunction +++ b/datapacks/dataLib/data/datalib/function/input/summon_cbm.mcfunction @@ -1,3 +1,2 @@ kill @e[type=minecraft:minecart,tag=datalib_input,sort=nearest,limit=1,distance=..2] -kill @e[type=minecraft:interaction,tag=datalib_input,sort=nearest,limit=1,distance=..2] -execute as @s at @s run summon minecraft:interaction ~ ~ ~ {width:1.0f,height:1.0f,Tags:["datalib_input"],Passengers:[{id:"minecraft:command_block_minecart",Tags:["datalib_input"]}]} \ No newline at end of file +summon minecraft:command_block_minecart ~ ~ ~ {OnGround:1b,UpdateLastExecution:1b,Command:"",Tags:["datalib_input"],TrackOutput:1b} \ No newline at end of file diff --git a/datapacks/dataLib/data/datalib/function/input/validate/_private/check_bool.mcfunction b/datapacks/dataLib/data/datalib/function/input/validate/_private/check_bool.mcfunction new file mode 100644 index 00000000..7d369dc4 --- /dev/null +++ b/datapacks/dataLib/data/datalib/function/input/validate/_private/check_bool.mcfunction @@ -0,0 +1,13 @@ +# ====================================================================================== +# datalib:input/validate/_private/check_bool [INTERNAL] +# ====================================================================================== +# Requires scratch.value to be the exact string "true" or "false". No +# case-folding — "True"/"TRUE" are rejected on purpose, since silently +# accepting them just moves the ambiguity one step further down instead +# of resolving it. +# ====================================================================================== + +execute if data storage datalib:input_validate {scratch:{value:"true"}} run data modify storage datalib:input_validate result.valid set value 1b +execute if data storage datalib:input_validate {scratch:{value:"false"}} run data modify storage datalib:input_validate result.valid set value 1b + +execute unless data storage datalib:input_validate {result:{valid:1b}} run data modify storage datalib:input_validate result.error set value "expected exactly \"true\" or \"false\"" diff --git a/datapacks/dataLib/data/datalib/function/input/validate/_private/check_float.mcfunction b/datapacks/dataLib/data/datalib/function/input/validate/_private/check_float.mcfunction new file mode 100644 index 00000000..9c3ec7bb --- /dev/null +++ b/datapacks/dataLib/data/datalib/function/input/validate/_private/check_float.mcfunction @@ -0,0 +1,57 @@ +# ====================================================================================== +# datalib:input/validate/_private/check_float [INTERNAL] +# ====================================================================================== +# Same digit-counting strategy as check_int, but first strips at most one +# '.' (anywhere after a possible leading '-', not at position 0 of rest, +# not as the last character either — "1." and ".5" are rejected as +# ambiguous rather than guessed at). +# ====================================================================================== + +data modify storage datalib:input_validate scratch.rest set from storage datalib:input_validate scratch.value + +execute store result score #DL.Len dl.tmp run data get storage datalib:input_validate scratch.rest +execute if score #DL.Len dl.tmp matches 0 run data modify storage datalib:input_validate result.error set value "empty input" +execute if score #DL.Len dl.tmp matches 0 run return 0 + +# Count the dots via replace() — its return value is a real match count, +# unlike find()'s (see count_one_digit for why find()'s own return isn't +# usable as a count). +data modify storage stringlib:input replace.String set from storage datalib:input_validate scratch.rest +data modify storage stringlib:input replace.Find set value "." +data modify storage stringlib:input replace.Replace set value "" +data modify storage stringlib:input replace.n set value 0 + +scoreboard players set #DL.DotHits dl.tmp 0 +execute store result score #DL.DotHits dl.tmp run function stringlib:util/replace + +execute if score #DL.DotHits dl.tmp matches 2.. run data modify storage datalib:input_validate result.error set value "more than one '.'" +execute if score #DL.DotHits dl.tmp matches 2.. run return 0 + +# Exactly one dot: find its position (find()'s OUTPUT LIST is trustworthy — +# only its own return value isn't) and reject if it's first/last char. +execute if score #DL.DotHits dl.tmp matches 1 run data modify storage stringlib:input find.String set from storage datalib:input_validate scratch.rest +execute if score #DL.DotHits dl.tmp matches 1 run data modify storage stringlib:input find.Find set value "." +execute if score #DL.DotHits dl.tmp matches 1 run data modify storage stringlib:input find.n set value 1 +execute if score #DL.DotHits dl.tmp matches 1 run function stringlib:util/find +execute if score #DL.DotHits dl.tmp matches 1 run function datalib:input/validate/_private/check_float_dot_position +execute if score #DL.DotHits dl.tmp matches 1 if data storage datalib:input_validate {result:{error:"malformed decimal point"}} run return 0 + +# Strip leading '-' the same way check_int does. +data modify storage datalib:input_validate scratch.first set string storage datalib:input_validate scratch.rest 0 1 +execute if data storage datalib:input_validate {scratch:{first:"-"}} run data modify storage datalib:input_validate scratch.rest set string storage datalib:input_validate scratch.rest 1 +data remove storage datalib:input_validate scratch.first + +# Remove the single '.' itself before counting digits (it's not a digit, +# but it's the one non-digit char we're intentionally allowing). +execute if score #DL.DotHits dl.tmp matches 1 run function datalib:input/validate/_private/strip_one_dot + +execute store result score #DL.Len dl.tmp run data get storage datalib:input_validate scratch.rest +execute if score #DL.Len dl.tmp matches 0 run data modify storage datalib:input_validate result.error set value "no digits" +execute if score #DL.Len dl.tmp matches 0 run return 0 + +function datalib:input/validate/_private/count_digits + +execute if score #DL.DigitCount dl.tmp = #DL.Len dl.tmp run data modify storage datalib:input_validate result.valid set value 1b +execute unless score #DL.DigitCount dl.tmp = #DL.Len dl.tmp run data modify storage datalib:input_validate result.error set value "contains a non-digit character" + +data remove storage datalib:input_validate scratch.rest diff --git a/datapacks/dataLib/data/datalib/function/input/validate/_private/check_float_dot_position.mcfunction b/datapacks/dataLib/data/datalib/function/input/validate/_private/check_float_dot_position.mcfunction new file mode 100644 index 00000000..ad03d4d3 --- /dev/null +++ b/datapacks/dataLib/data/datalib/function/input/validate/_private/check_float_dot_position.mcfunction @@ -0,0 +1,17 @@ +# ====================================================================================== +# datalib:input/validate/_private/check_float_dot_position [INTERNAL] +# ====================================================================================== +# stringlib:output find holds a one-element list [idx] for the '.' just +# located by check_float. Rejects idx == 0 (leading dot, e.g. ".5") and +# idx == len-1 (trailing dot, e.g. "1.") as malformed rather than +# guessing an implied leading/trailing zero. +# ====================================================================================== + +execute store result score #DL.DotIdx dl.tmp run data get storage stringlib:output find[0] +execute store result score #DL.Len dl.tmp run data get storage datalib:input_validate scratch.rest + +scoreboard players operation #DL.LastIdx dl.tmp = #DL.Len dl.tmp +scoreboard players remove #DL.LastIdx dl.tmp 1 + +execute if score #DL.DotIdx dl.tmp matches 0 run data modify storage datalib:input_validate result.error set value "malformed decimal point" +execute if score #DL.DotIdx dl.tmp = #DL.LastIdx dl.tmp run data modify storage datalib:input_validate result.error set value "malformed decimal point" diff --git a/datapacks/dataLib/data/datalib/function/input/validate/_private/check_int.mcfunction b/datapacks/dataLib/data/datalib/function/input/validate/_private/check_int.mcfunction new file mode 100644 index 00000000..aad3ae83 --- /dev/null +++ b/datapacks/dataLib/data/datalib/function/input/validate/_private/check_int.mcfunction @@ -0,0 +1,39 @@ +# ====================================================================================== +# datalib:input/validate/_private/check_int [INTERNAL] +# ====================================================================================== +# +# FAST METHOD (no per-character recursion): +# A string is "all digits, optional leading -" iff: +# 1. stripping one leading '-' (if present) leaves a non-empty rest, AND +# 2. the sum of how many times each of '0'..'9' occurs in that rest, +# via stringlib:util/find, equals the rest's total length. +# If every character is one of the 10 digits, the counts must add up to +# the full length — any non-digit character breaks the sum by definition, +# without needing to inspect it directly. +# +# Reuses stringlib:util/find (read-only substring search) — this never +# executes the input, only counts characters via a library already +# audited for the $$(command) macro-injection fix. +# ====================================================================================== + +data modify storage datalib:input_validate scratch.rest set from storage datalib:input_validate scratch.value + +# Strip one leading '-' if present (allowed once, at position 0 only). +execute store result score #DL.Len dl.tmp run data get storage datalib:input_validate scratch.rest +execute if score #DL.Len dl.tmp matches 0 run data modify storage datalib:input_validate result.error set value "empty input" +execute if score #DL.Len dl.tmp matches 0 run return 0 + +data modify storage datalib:input_validate scratch.first set string storage datalib:input_validate scratch.rest 0 1 +execute if data storage datalib:input_validate {scratch:{first:"-"}} run data modify storage datalib:input_validate scratch.rest set string storage datalib:input_validate scratch.rest 1 +data remove storage datalib:input_validate scratch.first + +execute store result score #DL.Len dl.tmp run data get storage datalib:input_validate scratch.rest +execute if score #DL.Len dl.tmp matches 0 run data modify storage datalib:input_validate result.error set value "no digits after '-'" +execute if score #DL.Len dl.tmp matches 0 run return 0 + +function datalib:input/validate/_private/count_digits + +execute if score #DL.DigitCount dl.tmp = #DL.Len dl.tmp run data modify storage datalib:input_validate result.valid set value 1b +execute unless score #DL.DigitCount dl.tmp = #DL.Len dl.tmp run data modify storage datalib:input_validate result.error set value "contains a non-digit character" + +data remove storage datalib:input_validate scratch.rest diff --git a/datapacks/dataLib/data/datalib/function/input/validate/_private/check_tag_safe.mcfunction b/datapacks/dataLib/data/datalib/function/input/validate/_private/check_tag_safe.mcfunction new file mode 100644 index 00000000..24dd1aa5 --- /dev/null +++ b/datapacks/dataLib/data/datalib/function/input/validate/_private/check_tag_safe.mcfunction @@ -0,0 +1,40 @@ +# ====================================================================================== +# datalib:input/validate/_private/check_tag_safe [INTERNAL] +# ====================================================================================== +# Rejects empty strings and any string containing one of: space " ' { } [ ] : +# or a literal backslash. Safe result can be used as a scoreboard +# objective/player-name fragment or dropped unquoted into a single NBT +# string field without needing escaping. +# +# One stringlib:util/find per forbidden character — fixed cost (9 calls), +# not per-character-of-input. +# ====================================================================================== + +execute store result score #DL.Len dl.tmp run data get storage datalib:input_validate scratch.value +execute if score #DL.Len dl.tmp matches 0 run data modify storage datalib:input_validate result.error set value "empty input" +execute if score #DL.Len dl.tmp matches 0 run return 0 + +data modify storage stringlib:input find.String set from storage datalib:input_validate scratch.value +data modify storage stringlib:input find.n set value 1 + +data modify storage datalib:input_validate scratch.bad set value 0b + +function datalib:input/validate/_private/reject_if_present {char:" "} +function datalib:input/validate/_private/reject_if_present {char:"\""} +function datalib:input/validate/_private/reject_if_present {char:"'"} +function datalib:input/validate/_private/reject_if_present {char:"{"} +function datalib:input/validate/_private/reject_if_present {char:"}"} +function datalib:input/validate/_private/reject_if_present {char:"["} +function datalib:input/validate/_private/reject_if_present {char:"]"} +function datalib:input/validate/_private/reject_if_present {char:":"} +function datalib:input/validate/_private/reject_if_present {char:"\\"} +function datalib:input/validate/_private/reject_if_present {char:"§"} +function datalib:input/validate/_private/reject_if_present {char:"|"} +function datalib:input/validate/_private/reject_if_present {char:"^"} +function datalib:input/validate/_private/reject_if_present {char:"<"} +function datalib:input/validate/_private/reject_if_present {char:">"} + +execute unless data storage datalib:input_validate {scratch:{bad:1b}} run data modify storage datalib:input_validate result.valid set value 1b +execute if data storage datalib:input_validate {scratch:{bad:1b}} run data modify storage datalib:input_validate result.error set value "contains a disallowed character (space, quote, brace, bracket, colon, or backslash)" + +data remove storage datalib:input_validate scratch.bad diff --git a/datapacks/dataLib/data/datalib/function/input/validate/_private/count_digits.mcfunction b/datapacks/dataLib/data/datalib/function/input/validate/_private/count_digits.mcfunction new file mode 100644 index 00000000..0fccef83 --- /dev/null +++ b/datapacks/dataLib/data/datalib/function/input/validate/_private/count_digits.mcfunction @@ -0,0 +1,28 @@ +# ====================================================================================== +# datalib:input/validate/_private/count_digits [INTERNAL] +# ====================================================================================== +# Requires scratch.rest to be set (the string to scan, already stripped of +# any leading sign/dot handling by the caller). Sets score +# #DL.DigitCount dl.tmp to the total number of '0'-'9' characters found +# in scratch.rest, using stringlib:util/find once per digit. +# +# Note on cost: this is 10 stringlib:util/find calls regardless of input +# length — flat cost, not per-character — which is what makes it cheap +# next to a recursive char-walk. +# ====================================================================================== + +scoreboard players set #DL.DigitCount dl.tmp 0 + +data modify storage stringlib:input find.String set from storage datalib:input_validate scratch.rest +data modify storage stringlib:input find.n set value 0 + +function datalib:input/validate/_private/count_one_digit {digit:"0"} +function datalib:input/validate/_private/count_one_digit {digit:"1"} +function datalib:input/validate/_private/count_one_digit {digit:"2"} +function datalib:input/validate/_private/count_one_digit {digit:"3"} +function datalib:input/validate/_private/count_one_digit {digit:"4"} +function datalib:input/validate/_private/count_one_digit {digit:"5"} +function datalib:input/validate/_private/count_one_digit {digit:"6"} +function datalib:input/validate/_private/count_one_digit {digit:"7"} +function datalib:input/validate/_private/count_one_digit {digit:"8"} +function datalib:input/validate/_private/count_one_digit {digit:"9"} diff --git a/datapacks/dataLib/data/datalib/function/input/validate/_private/count_one_digit.mcfunction b/datapacks/dataLib/data/datalib/function/input/validate/_private/count_one_digit.mcfunction new file mode 100644 index 00000000..caf8af88 --- /dev/null +++ b/datapacks/dataLib/data/datalib/function/input/validate/_private/count_one_digit.mcfunction @@ -0,0 +1,27 @@ +# ====================================================================================== +# datalib:input/validate/_private/count_one_digit [INTERNAL — macro function] +# ====================================================================================== +# Called with {digit: "0".."9"}. stringlib:input find.String is assumed +# already set by the caller (count_digits) to the string being scanned — +# reused here as replace.String too. +# +# Uses stringlib:util/replace(Find:"", Replace:"") instead of +# stringlib:util/find, because replace()'s return value is a genuine +# running match count (see zprivate/replace/*.mcfunction: ReturnValue is +# incremented once per match). find()'s own return value is NOT a count — +# it's "execute if data storage stringlib:output find[]", which is just +# 1-or-fail regardless of how many indices are in the list. replace() +# gives the real number directly, with no separate list-length step +# needed, and its side effect (stringlib:output replace) is harmless here +# since count_digits never reads that key. +# ====================================================================================== + +data modify storage stringlib:input replace.String set from storage stringlib:input find.String +$data modify storage stringlib:input replace.Find set value "$(digit)" +data modify storage stringlib:input replace.Replace set value "" +data modify storage stringlib:input replace.n set value 0 + +scoreboard players set #DL.FindHits dl.tmp 0 +execute store result score #DL.FindHits dl.tmp run function stringlib:util/replace + +scoreboard players operation #DL.DigitCount dl.tmp += #DL.FindHits dl.tmp diff --git a/datapacks/dataLib/data/datalib/function/input/validate/_private/reject_if_present.mcfunction b/datapacks/dataLib/data/datalib/function/input/validate/_private/reject_if_present.mcfunction new file mode 100644 index 00000000..536fc23b --- /dev/null +++ b/datapacks/dataLib/data/datalib/function/input/validate/_private/reject_if_present.mcfunction @@ -0,0 +1,18 @@ +# ====================================================================================== +# datalib:input/validate/_private/reject_if_present [INTERNAL — macro function] +# ====================================================================================== +# Called with {char: ""}. stringlib:input find.String and +# find.n are already set by the caller. Sets scratch.bad = 1b if this +# character occurs anywhere. +# +# NOTE: unlike count_one_digit, this is a presence check, not a count — +# find()'s own return value (1 if found, fail if not — see find.mcfunction's +# final line) is exactly what a presence check needs, so store success +# is used deliberately here rather than reading the output list. +# ====================================================================================== + +$data modify storage stringlib:input find.Find set value "$(char)" + +execute store success score #DL.FindOk dl.tmp run function stringlib:util/find + +execute if score #DL.FindOk dl.tmp matches 1 run data modify storage datalib:input_validate scratch.bad set value 1b diff --git a/datapacks/dataLib/data/datalib/function/input/validate/_private/strip_one_dot.mcfunction b/datapacks/dataLib/data/datalib/function/input/validate/_private/strip_one_dot.mcfunction new file mode 100644 index 00000000..e40ca8d6 --- /dev/null +++ b/datapacks/dataLib/data/datalib/function/input/validate/_private/strip_one_dot.mcfunction @@ -0,0 +1,15 @@ +# ====================================================================================== +# datalib:input/validate/_private/strip_one_dot [INTERNAL] +# ====================================================================================== +# Removes exactly one '.' from scratch.rest (already confirmed to contain +# exactly one, at a valid non-edge position) via stringlib:util/replace, +# leaving the digit characters ready for count_digits. +# ====================================================================================== + +data modify storage stringlib:input replace.String set from storage datalib:input_validate scratch.rest +data modify storage stringlib:input replace.Find set value "." +data modify storage stringlib:input replace.Replace set value "" +data modify storage stringlib:input replace.n set value 1 + +function stringlib:util/replace +data modify storage datalib:input_validate scratch.rest set from storage stringlib:output replace diff --git a/datapacks/dataLib/data/datalib/function/input/validate/check.mcfunction b/datapacks/dataLib/data/datalib/function/input/validate/check.mcfunction new file mode 100644 index 00000000..6653482b --- /dev/null +++ b/datapacks/dataLib/data/datalib/function/input/validate/check.mcfunction @@ -0,0 +1,56 @@ +# ====================================================================================== +# datalib:input/validate/check +# ====================================================================================== +# +# TYPE-SAFETY GUARD FOR CAPTURED PLAYER INPUT. +# +# Every input capture point (book_capture, dialog_capture, cbm_capture) only +# ever stores the RAW string a player submitted — it does not know or care +# whether that string is the shape the caller actually needs. This function +# is the missing check: run it AFTER a capture, BEFORE you use the value as +# a number, bool, or safe scoreboard/tag literal. +# +# THIS DOES NOT EXECUTE THE INPUT. It only reads it and reports pass/fail. +# +# CALL WITH: +# function datalib:input/validate/check with storage {source: "", type: "int"|"float"|"bool"|"tag_safe"} +# +# $(source) — a dot-path INTO datalib:input, e.g. "book.raw", "dialog.raw", +# "cbm.command". Read via macro from datalib:input directly. +# $(type) — one of: +# "int" — string must parse as a whole number (optional leading -) +# "float" — string must parse as int or decimal (optional leading -) +# "bool" — string must be exactly "true" or "false" +# "tag_safe" — string must contain none of: space, ", ', {, }, [, ], :, +# the literal backslash character, or be empty. Safe to use +# as a scoreboard objective/player name fragment or a +# single unquoted NBT string component. +# +# OUTPUT (written to storage datalib:input_validate result): +# result.valid — 1b if input matches the requested type, else 0b +# result.error — human-readable reason when invalid (unset when valid) +# result.value — for "int"/"float": the raw string, unchanged (caller +# still owns turning it into a real number via +# stringlib:util/to_number AFTER checking result.valid — +# this function never calls to_number itself, since +# to_number has no failure mode of its own: feeding it +# a non-numeric string produces a raw SNBT parse error +# instead of a clean invalid result, which is exactly +# the gap this function exists to close upstream of it) +# +# THIS FUNCTION NEVER $$(...) MACRO-EXECUTES THE INPUT STRING. It only +# calls stringlib:util/find (read-only string search) against it. +# ====================================================================================== + +data remove storage datalib:input_validate result +data modify storage datalib:input_validate result.valid set value 0b + +$data modify storage datalib:input_validate scratch.value set from storage datalib:input $(source) +$data modify storage datalib:input_validate scratch.type set value "$(type)" + +execute if data storage datalib:input_validate {scratch:{type:"int"}} run function datalib:input/validate/_private/check_int +execute if data storage datalib:input_validate {scratch:{type:"float"}} run function datalib:input/validate/_private/check_float +execute if data storage datalib:input_validate {scratch:{type:"bool"}} run function datalib:input/validate/_private/check_bool +execute if data storage datalib:input_validate {scratch:{type:"tag_safe"}} run function datalib:input/validate/_private/check_tag_safe + +data remove storage datalib:input_validate scratch diff --git a/datapacks/dataLib/data/dl_load/function/loader/scoreboards.mcfunction b/datapacks/dataLib/data/dl_load/function/loader/scoreboards.mcfunction index e77abe75..6a349f68 100644 --- a/datapacks/dataLib/data/dl_load/function/loader/scoreboards.mcfunction +++ b/datapacks/dataLib/data/dl_load/function/loader/scoreboards.mcfunction @@ -70,6 +70,9 @@ scoreboard objectives add datalib.hook_fish dummy scoreboard players set #10000 dl.tmp 10000 scoreboard players set #100 dl.tmp 100 +# input/validate module — scratch scores, all on the existing dl.tmp +# objective rather than a new one (DL.Len, DL.DotHits, etc. all live here) + # Log level system: 0=off 1=error 2=warn 3=info 4=debug scoreboard objectives add dl.log_level dummy execute unless score #dl.log_level dl.log_level matches 0.. run scoreboard players set #dl.log_level dl.log_level 3 diff --git a/resourcepacks/dataLib/pack.mcmeta b/resourcepacks/dataLib/pack.mcmeta index f00a34d8..a5095b4b 100644 --- a/resourcepacks/dataLib/pack.mcmeta +++ b/resourcepacks/dataLib/pack.mcmeta @@ -2,11 +2,11 @@ "pack": { "description": "dataLib v6.0.1 — resource pack companion (fonts/textures for GUI & dialog rendering) | runtoolkit", "min_format": [ - 107, + 88, 1 ], "max_format": [ - 107, + 88, 1 ] },