Hydrax_Animatez

lz4

Sep 28th, 2024 (edited)
44
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 6.99 KB | None | 0 0
  1. --!strict
  2. -- metatablecat 2022
  3.  
  4. local lz4 = {}
  5.  
  6. type Streamer = {
  7. Offset: number,
  8. Source: string,
  9. Length: number,
  10. IsFinished: boolean,
  11. LastUnreadBytes: number,
  12.  
  13. read: (Streamer, len: number?, shiftOffset: boolean?) -> string,
  14. seek: (Streamer, len: number) -> (),
  15. append: (Streamer, newData: string) -> (),
  16. toEnd: (Streamer) -> ()
  17. }
  18.  
  19. type BlockData = {
  20. [number]: {
  21. Literal: string,
  22. LiteralLength: number,
  23. MatchOffset: number?,
  24. MatchLength: number?
  25. }
  26. }
  27.  
  28. local function plainFind(str, pat)
  29. return string.find(str, pat, 0, true)
  30. end
  31.  
  32. local function streamer(str): Streamer
  33. local Stream = {}
  34. Stream.Offset = 0
  35. Stream.Source = str
  36. Stream.Length = string.len(str)
  37. Stream.IsFinished = false
  38. Stream.LastUnreadBytes = 0
  39.  
  40. function Stream.read(self: Streamer, len: number?, shift: boolean?): string
  41. local len = len or 1
  42. local shift = if shift ~= nil then shift else true
  43. local dat = string.sub(self.Source, self.Offset + 1, self.Offset + len)
  44.  
  45. local dataLength = string.len(dat)
  46. local unreadBytes = len - dataLength
  47.  
  48. if shift then
  49. self:seek(len)
  50. end
  51.  
  52. self.LastUnreadBytes = unreadBytes
  53. return dat
  54. end
  55.  
  56. function Stream.seek(self: Streamer, len: number)
  57. local len = len or 1
  58.  
  59. self.Offset = math.clamp(self.Offset + len, 0, self.Length)
  60. self.IsFinished = self.Offset >= self.Length
  61. end
  62.  
  63. function Stream.append(self: Streamer, newData: string)
  64. -- adds new data to the end of a stream
  65. self.Source ..= newData
  66. self.Length = string.len(self.Source)
  67. self:seek(0) --hacky but forces a recalculation of the isFinished flag
  68. end
  69.  
  70. function Stream.toEnd(self: Streamer)
  71. self:seek(self.Length)
  72. end
  73.  
  74. return Stream
  75. end
  76.  
  77. function lz4.compress(str: string): string
  78. local blocks: BlockData = {}
  79. local iostream = streamer(str)
  80.  
  81. if iostream.Length > 12 then
  82. local firstFour = iostream:read(4)
  83.  
  84. local processed = firstFour
  85. local lit = firstFour
  86. local match = ""
  87. local LiteralPushValue = ""
  88. local pushToLiteral = true
  89.  
  90. repeat
  91. pushToLiteral = true
  92. local nextByte = iostream:read()
  93.  
  94. if plainFind(processed, nextByte) then
  95. local next3 = iostream:read(3, false)
  96.  
  97. if string.len(next3) < 3 then
  98. --push bytes to literal block then break
  99. LiteralPushValue = nextByte .. next3
  100. iostream:seek(3)
  101. else
  102. match = nextByte .. next3
  103.  
  104. local matchPos = plainFind(processed, match)
  105. if matchPos then
  106. iostream:seek(3)
  107. repeat
  108. local nextMatchByte = iostream:read(1, false)
  109. local newResult = match .. nextMatchByte
  110.  
  111. local repos = plainFind(processed, newResult)
  112. if repos then
  113. match = newResult
  114. matchPos = repos
  115. iostream:seek(1)
  116. end
  117. until not plainFind(processed, newResult) or iostream.IsFinished
  118.  
  119. local matchLen = string.len(match)
  120. local pushMatch = true
  121.  
  122. if iostream.Length - iostream.Offset <= 5 then
  123. LiteralPushValue = match
  124. pushMatch = false
  125. --better safe here, dont bother pushing to match ever
  126. end
  127.  
  128. if pushMatch then
  129. pushToLiteral = false
  130.  
  131. -- gets the position from the end of processed, then slaps it onto processed
  132. local realPosition = string.len(processed) - matchPos
  133. processed = processed .. match
  134.  
  135. table.insert(blocks, {
  136. Literal = lit,
  137. LiteralLength = string.len(lit),
  138. MatchOffset = realPosition + 1,
  139. MatchLength = matchLen,
  140. })
  141. lit = ""
  142. end
  143. else
  144. LiteralPushValue = nextByte
  145. end
  146. end
  147. else
  148. LiteralPushValue = nextByte
  149. end
  150.  
  151. if pushToLiteral then
  152. lit = lit .. LiteralPushValue
  153. processed = processed .. nextByte
  154. end
  155. until iostream.IsFinished
  156. table.insert(blocks, {
  157. Literal = lit,
  158. LiteralLength = string.len(lit)
  159. })
  160. else
  161. local str = iostream.Source
  162. blocks[1] = {
  163. Literal = str,
  164. LiteralLength = string.len(str)
  165. }
  166. end
  167.  
  168. -- generate the output chunk
  169. -- %s is for adding header
  170. local output = string.rep("\x00", 4)
  171. local function write(char)
  172. output = output .. char
  173. end
  174. -- begin working through chunks
  175. for chunkNum, chunk in blocks do
  176. local litLen = chunk.LiteralLength
  177. local matLen = (chunk.MatchLength or 4) - 4
  178.  
  179. -- create token
  180. local tokenLit = math.clamp(litLen, 0, 15)
  181. local tokenMat = math.clamp(matLen, 0, 15)
  182.  
  183. local token = bit32.lshift(tokenLit, 4) + tokenMat
  184. write(string.pack("<I1", token))
  185.  
  186. if litLen >= 15 then
  187. litLen = litLen - 15
  188. --begin packing extra bytes
  189. repeat
  190. local nextToken = math.clamp(litLen, 0, 0xFF)
  191. write(string.pack("<I1", nextToken))
  192. if nextToken == 0xFF then
  193. litLen = litLen - 255
  194. end
  195. until nextToken < 0xFF
  196. end
  197.  
  198. -- push raw lit data
  199. write(chunk.Literal)
  200.  
  201. if chunkNum ~= #blocks then
  202. -- push offset as u16
  203. write(string.pack("<I2", chunk.MatchOffset))
  204.  
  205. -- pack extra match bytes
  206. if matLen >= 15 then
  207. matLen = matLen - 15
  208.  
  209. repeat
  210. local nextToken = math.clamp(matLen, 0, 0xFF)
  211. write(string.pack("<I1", nextToken))
  212. if nextToken == 0xFF then
  213. matLen = matLen - 255
  214. end
  215. until nextToken < 0xFF
  216. end
  217. end
  218. end
  219. --append chunks
  220. local compLen = string.len(output) - 4
  221. local decompLen = iostream.Length
  222.  
  223. return string.pack("<I4", compLen) .. string.pack("<I4", decompLen) .. output
  224. end
  225.  
  226. function lz4.decompress(lz4data: string): string
  227. local inputStream = streamer(lz4data)
  228.  
  229. local compressedLen = string.unpack("<I4", inputStream:read(4))
  230. local decompressedLen = string.unpack("<I4", inputStream:read(4))
  231. local reserved = string.unpack("<I4", inputStream:read(4))
  232.  
  233. if compressedLen == 0 then
  234. return inputStream:read(decompressedLen)
  235. end
  236.  
  237. local outputStream = streamer("")
  238.  
  239. repeat
  240. local token = string.byte(inputStream:read())
  241. local litLen = bit32.rshift(token, 4)
  242. local matLen = bit32.band(token, 15) + 4
  243.  
  244. if litLen >= 15 then
  245. repeat
  246. local nextByte = string.byte(inputStream:read())
  247. litLen += nextByte
  248. until nextByte ~= 0xFF
  249. end
  250.  
  251. local literal = inputStream:read(litLen)
  252. outputStream:append(literal)
  253. outputStream:toEnd()
  254. if outputStream.Length < decompressedLen then
  255. --match
  256. local offset = string.unpack("<I2", inputStream:read(2))
  257. if matLen >= 19 then
  258. repeat
  259. local nextByte = string.byte(inputStream:read())
  260. matLen += nextByte
  261. until nextByte ~= 0xFF
  262. end
  263.  
  264. outputStream:seek(-offset)
  265. local pos = outputStream.Offset
  266. local match = outputStream:read(matLen)
  267. local unreadBytes = outputStream.LastUnreadBytes
  268. local extra
  269. if unreadBytes then
  270. repeat
  271. outputStream.Offset = pos
  272. extra = outputStream:read(unreadBytes)
  273. unreadBytes = outputStream.LastUnreadBytes
  274. match ..= extra
  275. until unreadBytes <= 0
  276. end
  277.  
  278. outputStream:append(match)
  279. outputStream:toEnd()
  280. end
  281.  
  282. until outputStream.Length >= decompressedLen
  283.  
  284. return outputStream.Source
  285. end
  286.  
  287. return lz4
Add Comment
Please, Sign In to add comment