Garry's Mod Wiki

Revision Difference

binary_string#568354

<cat>Dev.Lua</cat>⤶ <title>Concepts - Binary Strings</title>⤶ ⤶ # Binary Strings⤶ ⤶ Lua has no separate type for binary data. A <page>string</page> is a sequence of bytes, any value from `0` to `255` can appear in it, including `0`, and the same type holds text and raw data.⤶ Which one you have depends on where the string came from and where it is going.⤶ ⤶ Binary strings come out of <page>net.ReadData</page>, <page>util.Compress</page>, <page>util.Base64Decode</page>, <page>render.Capture</page>, <page>string.dump</page> and <page>File:Read</page>, and go into <page>net.WriteData</page>, <page>util.Decompress</page>, <page>File:Write</page> and <page>sound.Generate</page>.⤶ A BLOB column read with <page>sql.QueryTyped</page> is one as well.⤶ ⤶ ## Bytes vs. characters⤶ ⤶ `#` and <page>string.len</page> count bytes. A character outside ASCII takes more than one, `#"\195\188"` is `2` for a single letter.⤶ Use <page>utf8.len</page> when you want characters, it also returns `false` when the string is not valid text.⤶ ⤶ Strings cannot be changed in place. Build binary data with <page>string.char</page> and read it back with <page>string.byte</page>, both deal in byte values.⤶ Concatenation, <page>string.sub</page>, <page>string.rep</page> and <page>table.concat</page> keep every byte.⤶ For structured data, a <page text="File">file_class</page> opened in `rb` or `wb` mode has typed readers and writers such as <page>File:ReadULong</page> and <page>File:WriteFloat</page>. There is no `string.pack` in Garry's Mod.⤶ ⤶ <note>`string.byte( str, 1, -1 )` returns one value per byte, and a long string errors with `string slice too long`. Read it in chunks.</note>⤶ ⤶ ## Where bytes get lost⤶ ⤶ * <page>net.WriteString</page> writes a <page text="null terminated string">net.ReadString</page>, so it stops at the first `0` byte and the rest is never sent. Use <page>net.WriteData</page> for anything that might contain a `0`.⤶ * <page>net.WriteData</page> writes no length, so the receiver has to know how many bytes to read. Write the length first with <page>net.WriteUInt</page>, see <page text="Net Library Usage">Net_Library_Usage</page>. A compressed string that is the whole message can use the message length instead, as shown on <page>net.WriteData</page>.⤶ * <page>Global.print</page> and the console stop at the first `0` byte, so a binary string looks shorter than it is. Print `#str` or a hex dump instead.⤶ * <page>util.TableToJSON</page> keeps every byte and <page>util.JSONToTable</page> reads them all back. Bytes above `127` are written as they are, so the JSON is invalid UTF-8 and a browser or a web server will read it differently. Encode with <page>util.Base64Encode</page> first when the JSON leaves Lua or the data goes into a <page>DHTML</page> page. Pass `true` as the second argument, or the output gets a line break every 76 characters.⤶ * Patterns treat `.`, `%`, `(`, `[` and the other magic characters specially, and binary data is full of them. Search with <page>string.find</page> and `plain` set to `true`.⤶ ⤶ <example>⤶ <description>⤶ A compressed string holds `0` bytes, so it goes over the net as data with its length in front.⤶ </description>⤶ <code>⤶ -- Server⤶ local packed = util.Compress( bigString )⤶ ⤶ net.Start( "example_blob" )⤶ net.WriteUInt( #packed, 16 )⤶ net.WriteData( packed, #packed )⤶ net.Send( ply )⤶ ⤶ -- Client⤶ net.Receive( "example_blob", function()⤶ local len = net.ReadUInt( 16 )⤶ local packed = net.ReadData( len )⤶ local bigString = util.Decompress( packed, 65536 )⤶ ⤶ -- The first bytes as hex, to see what arrived⤶ print( ( packed:sub( 1, 8 ):gsub( ".", function( c ) return string.format( "%02X ", c:byte() ) end ) ) )⤶ end )⤶ </code>⤶ </example>