2djab.lua

by 6_4
574 days agolua
COPY
1
--[[
2
  2djab decode/encode program
3
  Vague specification:
4
    - Probably little endian
5
    - 2djab files start with "2djab1" (the 1 is the version)
6
    - Then have a 4-byte unsigned integer dictating the number of posters in the file
7
    - Then, each poster sequentially follows
8
    - Each poster:
9
      - Starts with a 2-byte unsigned integer (dictating its length in bytes)
10
      - Has a UTF-8 label, string starting with a 2-byte unsigned integer for length in bytes
11
        - (length of 0 is assumed to be no label)
12
      - Has a UTF-8 tooltip, string starting with a 2-byte unsigned integer for length in bytes
13
        - (length of 0 is assumed to be no label)
14
      - Has a palette
15
        - Starts with a 1-byte unsigned integer, dictating its length in bytes
16
          - The amount of colors it contains is its length divided by 3
17
        - Continues with many 3-byte integers, one for each color
18
      - Has an image data section
19
        - Starts with a 2-byte unsigned integer, dictating its length in bytes
20
        - Then has many 1-byte unsigned integers
21
          - If the 1-byte unsigned integer is within the range 0-63 inclusive:
22
            - it is a palette index
23
          - If the 1-byte unsigned integer is within the range 64-127 inclusive:
24
            - its value minus 64 is a palette index repeated twice
25
          - If the 1-byte unsigned integer is within the range 128-255 inclusive:
26
            - its value minus 128 is the amount of times that the previous palette index should be repeated
27
            - The previous palette index byte is still included
28
            - This byte must come directly after a byte within the 0-63 range.
29
  2djabc files are just the 2djab file but compressed with LibDeflate:CompressZlib
30
]]
31
local success, LibDeflate = pcall(require, "LibDeflate")
32

33
if not success then
34
  print("Error in requiring LibDeflate. Install? [y/n] ")
35
  local s = read()
36
  if s:lower():find("y") then
37
    shell.run("wget https://raw.githubusercontent.com/MCJack123/CC-Archive/master/LibDeflate.lua")
38
    LibDeflate = require("LibDeflate")
39
  else
40
    return
41
  end
42
end
43

44
local function decode2djab(str)
45
  local ver, numPosters = ("c6I4"):unpack(str:sub(1, 10))
46

47
  assert(ver == "2djab1")
48
  local pages = {}
49
  local out = {
50
    pages = pages
51
  }
52

53
  local posterStrs = {("s2"):rep(numPosters):unpack(str:sub(11))}
54
  for i = 1, numPosters do
55
    local posterStr = posterStrs[i]
56
    local label, tooltip, palette, data = ("s2s2s1s2"):unpack(posterStr)
57
    palette = {("I3"):rep(#palette / 3):unpack(palette)}
58
    palette[#palette] = nil
59
    data = data:gsub("([\0-\63])([\128-\255])", function(a, b) return a:rep(b:byte() - 127) end)
60
    data = data:gsub("[\64-\127]", function(a) return string.char(a:byte() - 64):rep(2) end)
61
    data = {data:byte(1, -1)}
62
    pages[#pages+1] = {
63
      label = label, tooltip = tooltip, palette = palette, pixels = data
64
    }
65
  end
66

67
  return out
68
end
69

70
local function encode2djab(input)
71
  local pages = input.pages
72
  local numPages = #pages
73
  local output = "2djab1" .. ("I4"):pack(numPages)
74

75
  for _i, page in ipairs(pages) do
76
    local data = page.pixels
77
    data[#data + 1] = -1
78
    local d2 = ''
79

80
    local currentPixel = -1
81
    local currentPixelCount = 0
82
    for i, pix in ipairs(data) do
83
      if pix == currentPixel then
84
        currentPixelCount = currentPixelCount + 1
85
        if currentPixelCount == 128 then
86
          d2 = d2 .. string.char(currentPixel) .. "\255"
87
          currentPixel, currentPixelCount = -1, 0
88
        end
89
      elseif pix ~= currentPixel then
90
        if currentPixel ~= -1 then
91
          if currentPixelCount == 1 then
92
            d2 = d2 .. string.char(currentPixel)
93
          elseif currentPixelCount == 2 then
94
            d2 = d2 .. string.char(currentPixel + 64)
95
          else
96
            d2 = d2 .. string.char(currentPixel) .. string.char(currentPixelCount + 127)
97
          end
98
        end
99
        currentPixel, currentPixelCount = pix, 1
100
      end
101
    end
102

103
    output = output .. ("s2"):pack(("s2s2s1s2"):pack(
104
      page.label or '',
105
      page.tooltip or '',
106
      ("I3"):rep(#page.palette):pack(unpack(page.palette)),
107
      d2
108
    ))
109
  end
110

111
  return output
112
end
113

114
local function decode2djabc(input)
115
  return decode2djab(LibDeflate:DecompressZlib(input))
116
end
117

118
local function encode2djabc(input)
119
  return LibDeflate:CompressZlib(encode2djab(input), {level = 9})
120
end
121

122
local function readfile(name)
123
	local hn = fs.open(shell.resolve(name), "rb")
124
  assert(hn, "Input file does not exist")
125
  local text = hn.readAll()
126
  hn.close()
127
  return text
128
end
129

130
local function writefile(name, text)
131
  local hn = fs.open(shell.resolve(name), "wb")
132
  hn.write(text)
133
  hn.close()
134
end
135

136
local mode, inputfile, outputfile = arg[1], arg[2], arg[3]
137
if not outputfile or not inputfile or (mode ~= "encode" and mode ~= "decode") then
138
  print("Usage: 2djab <encode|decode> <infile> <outfile>")
139
end
140

141
if mode == "encode" then
142
  print("Reading file " .. inputfile .. "...")
143
  local text = readfile(inputfile)
144
  print("Decoding JSON...")
145
  local dat = textutils.unserializeJSON(text)
146
  print("Encoding file...")
147
  local res = outputfile:find("%.2djabc$") and encode2djabc(dat) or encode2djab(dat)
148
  print("Writing file...")
149
  writefile(outputfile, res)
150
  print("Done!")
151
elseif mode == "decode" then
152
  print("Reading file " .. inputfile .. "...")
153
  local text = readfile(inputfile)
154
  print("Decoding 2djab...")
155
  local dat = inputfile:find("%.2djabc$") and decode2djabc(text) or decode2djab(text)
156
  print("Encoding JSON...")
157
  local res = textutils.serializeJSON(dat)
158
  print("Writing file...")
159
  writefile(outputfile, res)
160
  print("Done!")
161
end