gocha

EmuLua BizHawk Movie (BMV) Library (SNES Only)

Mar 16th, 2013
153
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Lua 8.56 KB | None | 0 0
  1. -- BMV (BizHawk movie) import functions.
  2. -- Currently supports only SNES movie...
  3.  
  4. -- movie base class
  5. function BaseMovie(filename)
  6.     local self = { meta = {}, data = {} }
  7.     local errorMessage = nil
  8.  
  9.     if filename ~= nil then
  10.         -- open movie file
  11.         local file = io.open(filename, "r")
  12.         if file == nil then
  13.             return nil, "error opening file \"" .. filename .. "\""
  14.         end
  15.  
  16.         -- parse lines from beginning
  17.         local line = file:read("*l")
  18.         local lineNo = 1
  19.         while line do
  20.             if string.sub(line, 1, 1) == "|" then
  21.                 if #line < 2 or string.sub(line, #line, #line) ~= "|" then
  22.                     file:close()
  23.                     return nil, filename .. ":" .. lineNo .. ": missing '|' at end of line"
  24.                 end
  25.  
  26.                 -- split
  27.                 local data = { }
  28.                 local sep = 1
  29.                 while sep < #line do
  30.                     local nextSep = string.find(line, "|", sep + 1, true)
  31.                     table.insert(data, string.sub(line, sep + 1, nextSep - 1))
  32.                     sep = nextSep
  33.                 end
  34.                 table.insert(self.data, data)
  35.             elseif line ~= "" then
  36.                 -- read key-value pair
  37.                 local name, value
  38.                 local sep = string.find(line, " ", 1, true)
  39.                 if sep ~= nil then
  40.                     name = string.sub(line, 1, sep - 1)
  41.                     value = string.sub(line, sep + 1)
  42.                 else
  43.                     name = line
  44.                     value = ""
  45.                 end
  46.  
  47.                 -- duplication check
  48.                 if self.meta[name] ~= nil then
  49.                     file:close()
  50.                     return nil, filename .. ":" .. lineNo .. ": duplicated item \"" .. name .. "\""
  51.                 end
  52.  
  53.                 -- store metadata (as string)
  54.                 self.meta[name] = value
  55.             end
  56.  
  57.             line = file:read("*l")
  58.             lineNo = lineNo + 1
  59.         end
  60.  
  61.         -- close movie file
  62.         file:close()
  63.     end
  64.  
  65.     -- start class functions
  66.  
  67.     self.PreferredMetaDataOrder = {}
  68.  
  69.     function self.Export(self, filename)
  70.         -- type check
  71.         if type(self) ~= "table" then
  72.             error("bad argument #1 to BaseMovie.Export (table expected, got " .. type(self) .. ")")
  73.         end
  74.         if type(filename) ~= "string" then
  75.             error("bad argument #2 to BaseMovie.Export (string expected, got " .. type(filename) .. ")")
  76.         end
  77.  
  78.         local file = io.open(filename, "wb")
  79.         if file == nil then
  80.             return false, "error opening file \"" .. filename .. "\""
  81.         end
  82.  
  83.         -- export metadata
  84.         local exportedMeta
  85.         for i, k in ipairs(self.PreferredMetaDataOrder) do
  86.             local v = self.meta[k]
  87.             if v ~= nil then
  88.                 file:write(k .. " " .. tostring(v) .. "\n")
  89.             end
  90.         end
  91.         for k, v in pairs(self.meta) do
  92.             file:write(k .. " " .. tostring(v) .. "\n")
  93.         end
  94.  
  95.         -- export input stream
  96.         for currentSample, ctlData in ipairs(self.data) do
  97.             if #ctlData > 0 then
  98.                 file:write("|")
  99.             end
  100.             for i, v in ipairs(ctlData) do
  101.                 file:write(v .. "|")
  102.             end
  103.             file:write("\n")
  104.         end
  105.  
  106.         file:close()
  107.         return true, ""
  108.     end
  109.  
  110.     function self.ConvertMetaType(self, typeTable)
  111.         -- type check
  112.         if type(self) ~= "table" then
  113.             error("bad argument #1 to BaseMovie.ConvertType (table expected, got " .. type(self) .. ")")
  114.         end
  115.         if type(typeTable) ~= "table" then
  116.             error("bad argument #2 to BaseMovie.ConvertType (table expected, got " .. type(typeTable) .. ")")
  117.         end
  118.  
  119.         -- type conversion for each metadata
  120.         for name, typeName in pairs(typeTable) do
  121.             if typeName == "number" then
  122.                 self.meta[name] = tonumber(self.meta[name])
  123.             elseif typeName == "string" then
  124.                 self.meta[name] = tostring(self.meta[name])
  125.             else
  126.                 error("unknown type \"" .. typeName .. "\"")
  127.             end
  128.         end
  129.     end
  130.  
  131.     function self.SplitInputSample(self, str, charTable)
  132.         if type(str) ~= "string" then
  133.             error("bad argument #2 to BaseMovie.SplitInputSample (string expected, got " .. type(str) .. ")")
  134.         end
  135.         if type(charTable) ~= "table" then
  136.             error("bad argument #3 to BaseMovie.SplitInputSample (table expected, got " .. type(charTable) .. ")")
  137.         end
  138.         if #str > #charTable then
  139.             error("bad argument #2 (too long string)")
  140.         end
  141.  
  142.         local t = {}
  143.         for i = 1, #str do
  144.             local c = str:sub(i, i)
  145.             if c == charTable[i].key then
  146.                 t[charTable[i].name] = true
  147.             elseif c == "." then
  148.                 t[charTable[i].name] = false
  149.             else
  150.                 return nil, tostring(i) .. ": bad input sample (" .. charTable[i].key .. " expected, got " .. c .. ")"
  151.             end
  152.         end
  153.         return t
  154.     end
  155.  
  156.     function self.JoinInputSample(self, t, charTable)
  157.         if type(t) ~= "table" then
  158.             error("bad argument #2 to BaseMovie.JoinInputSample (table expected, got " .. type(t) .. ")")
  159.         end
  160.         if type(charTable) ~= "table" then
  161.             error("bad argument #3 to BaseMovie.JoinInputSample (table expected, got " .. type(charTable) .. ")")
  162.         end
  163.  
  164.         local str = ""
  165.         for i = 1, #charTable do
  166.             if t[charTable[i].name] then
  167.                 str = str .. charTable[i].key
  168.             else
  169.                 str = str .. "."
  170.             end
  171.         end
  172.         return str
  173.     end
  174.  
  175.     -- end class functions
  176.  
  177.     -- success
  178.     return self, nil
  179. end
  180.  
  181. -- bizhawk movie class
  182. function BizHawkMovie(filename)
  183.     local self, errorMessage = BaseMovie(filename)
  184.     if self == nil then
  185.         return self, errorMessage
  186.     end
  187.  
  188.     -- format movie params
  189.     if self.meta.Platform == "SNES" then
  190.         -- metadata type conversion
  191.         self.MetaTypeTable = {
  192.             rerecordCount = "number"
  193.         }
  194.         self:ConvertMetaType(self.MetaTypeTable)
  195.  
  196.         -- list of available input
  197.         self.InputSampleTable = {
  198.             { key = "U", name = "up" },
  199.             { key = "D", name = "down" },
  200.             { key = "L", name = "left" },
  201.             { key = "R", name = "right" },
  202.             { key = "s", name = "select" },
  203.             { key = "S", name = "start" },
  204.             { key = "B", name = "B" },
  205.             { key = "A", name = "A" },
  206.             { key = "X", name = "X" },
  207.             { key = "Y", name = "Y" },
  208.             { key = "L", name = "L" },
  209.             { key = "R", name = "R" }
  210.         }
  211.  
  212.         self.PreferredMetaDataOrder = {
  213.             "emuVersion",
  214.             "MovieVersion",
  215.             "Platform",
  216.             "GameName",
  217.             "Author",
  218.             "rerecordCount",
  219.             "GUID",
  220.             "SHA1"
  221.         }
  222.  
  223.         -- convert input samples
  224.         local dataLength = nil
  225.         for dataIndex = 1, #self.data do
  226.             local data = self.data[dataIndex]
  227.             local t = {}
  228.  
  229.             if #data < 2 then
  230.                 return nil, "data[" .. dataIndex .. "]: no joypad input"
  231.             end
  232.  
  233.             -- TODO move this logic to BaseMovie()?
  234.             if dataLength == nil then
  235.                 dataLength = #data
  236.             end
  237.             if #data ~= dataLength then
  238.                 return nil, "data[" .. dataIndex .. "]: mismatch number of samples (between " .. #data .. " and " .. dataLength .. ")"
  239.             end
  240.  
  241.             -- TODO flag field
  242.             if #data[1] ~= 1 then
  243.                 return nil, "data[" .. dataIndex .. "][1]: invalid length"
  244.             end
  245.             if data[1] == "." then
  246.             elseif data[1] == "." then
  247.             else
  248.                 return nil, "data[" .. dataIndex .. "][1]: bad input sample " .. data[1]
  249.             end
  250.  
  251.             t.joypad = {}
  252.             local playerHead = 2
  253.             local playerTail = #data - 1
  254.             for player = 1, playerTail do
  255.                 t.joypad[player] = self:SplitInputSample(data[player + (playerHead - 1)], self.InputSampleTable)
  256.             end
  257.  
  258.             self.data[dataIndex] = t
  259.         end
  260.     else
  261.         return nil, "unknown platform \"" .. tostring(self.meta.Platform) .. "\""
  262.     end
  263.  
  264.     -- start class functions
  265.  
  266.     function self.Export(self, filename)
  267.         -- type check
  268.         if type(self) ~= "table" then
  269.             error("bad argument #1 to BizHawkMovie.Export (table expected, got " .. type(self) .. ")")
  270.         end
  271.         if type(filename) ~= "string" then
  272.             error("bad argument #2 to BizHawkMovie.Export (string expected, got " .. type(filename) .. ")")
  273.         end
  274.  
  275.         local file = io.open(filename, "wb")
  276.         if file == nil then
  277.             return false, "error opening file \"" .. filename .. "\""
  278.         end
  279.  
  280.         -- export metadata
  281.         for k, v in pairs(self.meta) do
  282.             file:write(k .. " " .. tostring(v) .. "\n")
  283.         end
  284.  
  285.         -- export input stream
  286.         for controller, ctlData in ipairs(self.data) do
  287.             if #ctlData.joypad > 0 then
  288.                 file:write("|")
  289.             end
  290.             for currentSample, inputTable in ipairs(ctlData.joypad) do
  291.                 local str = self:JoinInputSample(inputTable, self.InputSampleTable)
  292.                 file:write(str .. "|")
  293.             end
  294.             file:write("\n")
  295.         end
  296.  
  297.         file:close()
  298.         return true, ""
  299.     end
  300.  
  301.     -- end class functions
  302.  
  303.     return self, errorMessage
  304. end
  305.  
  306.  
  307.  
  308.  
  309.  
  310.  
  311.  
  312.  
  313.  
  314.  
  315.  
  316.  
  317.  
  318.  
  319. -- test starts here...
  320. function t2s(t)
  321.     local s = "{"
  322.     for k, v in pairs(t) do
  323.         if s ~= "{" then
  324.             s = s .. ","
  325.         end
  326.         s = s .. tostring(k) .. "=" .. tostring(v)
  327.     end
  328.     return s .. "}"
  329. end
  330.  
  331. self, err = BizHawkMovie("snes.bmv")
  332. if self == nil then
  333.     error(err)
  334. end
  335. self:Export("temp.bmv")
  336.  
  337. --[[
  338. print(#self.meta)
  339. for k, v in pairs(self.meta) do
  340.     print(k, "=", "\""..v.."\"", type(v))
  341. end
  342. print(#self.data)
  343. for i, v in ipairs(self.data) do
  344.     print(i)
  345.     for j, w in ipairs(v.joypad) do
  346.         -- local ttt = self:SplitInputSample(w, snesbmvInputSampleTable)
  347.         -- local sss = ""
  348.         -- for kkk, vvv in pairs(ttt) do
  349.         --  if vvv then
  350.         --      sss = sss .. kkk .. "=" .. tostring(vvv) .. "; "
  351.         --  end
  352.         -- end
  353.         print(t2s(w))
  354.     end
  355. end
  356. ]]
Add Comment
Please, Sign In to add comment