1-- license:BSD-3-Clause
2-- copyright-holders:Carl
3-- data files are json files named <romname>.json
4-- {
5--   "import":"<import filename>"
6--   "ports":{
7--     "<ioport name>":{
8--       "labels":{
9--         "<field mask>":{
10--           "player":<int player number>,
11--           "name":"<field label>"
12--         }
13--     },{
14--       ...
15--     }
16--   }
17-- }
18-- any additional metadata can be included for other usage
19-- and will be ignored
20local exports = {}
21exports.name = "portname"
22exports.version = "0.0.1"
23exports.description = "IOPort name/translation plugin"
24exports.license = "The BSD 3-Clause License"
25exports.author = { name = "Carl" }
26
27local portname = exports
28
29function portname.startplugin()
30	local json = require("json")
31	local ctrlrpath = lfs.env_replace(manager:options().entries.ctrlrpath:value():match("([^;]+)"))
32	local function get_filename(nosoft)
33		local filename
34		if emu.softname() ~= "" and not nosoft then
35			local soft = emu.softname():match("([^:]*)$")
36			filename = emu.romname() .. "_" .. soft .. ".json"
37		else
38			filename = emu.romname() .. ".json"
39		end
40		return filename
41	end
42
43	local function parse_names(ctable, depth)
44		if depth >= 5 then
45			emu.print_error("portname: max import depth exceeded\n")
46			return
47		end
48		if ctable.import then
49			local file = emu.file(ctrlrpath .. "/portname", "r")
50			local ret = file:open(ctable.import)
51			if not ret then
52				parse_names(json.parse(file:read(file:size())), depth + 1)
53			end
54		end
55		if not ctable.ports then
56			return
57		end
58		for pname, port in pairs(ctable.ports) do
59			local ioport = manager:machine():ioport().ports[pname]
60			if ioport then
61				for mask, label in pairs(port.labels) do
62					for num3, field in pairs(ioport.fields) do
63						local nummask = tonumber(mask, 16)
64						if nummask == field.mask and label.player == field.player then
65							field.live.name = label.name
66						end
67					end
68				end
69			end
70		end
71	end
72
73	emu.register_start(function()
74		local file = emu.file(ctrlrpath .. "/portname", "r")
75		local ret = file:open(get_filename())
76		if ret then
77			if emu.softname() ~= "" then
78				local parent
79				for tag, image in pairs(manager:machine().images) do
80					parent = image.software_parent
81					if parent ~= "" then
82						break
83					end
84				end
85				if parent ~= "" then
86					ret = file:open(emu.romname() .. "_" .. parent:match("([^:]*)$")  .. ".json")
87				end
88			end
89			if ret then
90				ret = file:open(get_filename(true))
91				if ret then
92					ret = file:open(manager:machine():system().parent .. ".json")
93					if ret then
94						return
95					end
96				end
97			end
98		end
99		parse_names(json.parse(file:read(file:size())), 0)
100	end)
101
102	local function menu_populate()
103		return {{ _("Save input names to file"), "", 0 }}
104	end
105
106	local function menu_callback(index, event)
107		if event == "select" then
108			local ports = {}
109			for pname, port in pairs(manager:machine():ioport().ports) do
110				local labels = {}
111				local sort = {}
112				for fname, field in pairs(port.fields) do
113					local mask = string.format("%x", field.mask)
114					if not labels[mask] then
115						sort[#sort + 1] = mask
116						labels[mask] = { name = fname, player = field.player }
117						setmetatable(labels[mask], { __tojson = function(v,s)
118							local label = { name = v.name, player = v.player }
119							setmetatable(label, { __jsonorder = { "player", "name" }})
120							return json.stringify(label) end })
121					end
122				end
123				if #sort > 0 then
124					table.sort(sort, function(i, j) return tonumber(i, 16) < tonumber(j, 16) end)
125					setmetatable(labels, { __jsonorder = sort })
126					ports[pname] = { labels = labels }
127				end
128			end
129			local function check_path(path)
130				local attr = lfs.attributes(path)
131				if not attr then
132					lfs.mkdir(path)
133					if not lfs.attributes(path) then
134						manager:machine():popmessage(_("Failed to save input name file"))
135						emu.print_verbose("portname: unable to create path " .. path .. "\n")
136						return false
137				end
138				elseif attr.mode ~= "directory" then
139					manager:machine():popmessage(_("Failed to save input name file"))
140					emu.print_verbose("portname: path exists but isn't directory " .. path .. "\n")
141					return false
142				end
143				return true
144			end
145			if not check_path(ctrlrpath) then
146				return false
147			end
148			if not check_path(ctrlrpath .. "/portname") then
149				return false
150			end
151			local filename = get_filename()
152			local file = io.open(ctrlrpath .. "/portname/" .. filename, "r")
153			if file then
154				emu.print_verbose("portname: input name file exists " .. filename .. "\n")
155				manager:machine():popmessage(_("Failed to save input name file"))
156				file:close()
157				return false
158			end
159			file = io.open(ctrlrpath .. "/portname/" .. filename, "w")
160			local ctable = { romname = emu.romname(), ports = ports }
161			if emu.softname() ~= "" then
162				ctable.softname = emu.softname()
163			end
164			setmetatable(ctable, { __jsonorder = { "romname", "softname", "ports" }})
165			file:write(json.stringify(ctable, { indent = true }))
166			file:close()
167			manager:machine():popmessage(string.format(_("Input port name file saved to %s"), ctrlrpath .. "/portname/" .. filename))
168		end
169		return false
170	end
171
172	emu.register_menu(menu_callback, menu_populate, _("Input ports"))
173end
174
175return exports
176