1 // license:BSD-3-Clause
2 // copyright-holders:Nathan Woods, Miodrag Milanovic
3 /***************************************************************************
4 
5     image.cpp
6 
7     Core image functions and definitions.
8 
9 ***************************************************************************/
10 
11 #include <cctype>
12 
13 #include "emu.h"
14 #include "emuopts.h"
15 #include "image.h"
16 #include "config.h"
17 #include "xmlfile.h"
18 #include "softlist.h"
19 
20 
21 //**************************************************************************
22 //  IMAGE MANAGER
23 //**************************************************************************
24 
25 //-------------------------------------------------
26 //  image_manager - constructor
27 //-------------------------------------------------
28 
image_manager(running_machine & machine)29 image_manager::image_manager(running_machine &machine)
30 	: m_machine(machine)
31 {
32 	// make sure that any required devices have been allocated
33 	for (device_image_interface &image : image_interface_iterator(machine.root_device()))
34 	{
35 		// ignore things not user loadable
36 		if (!image.user_loadable())
37 			continue;
38 
39 		// find the image option in image_options()
40 		const std::string &startup_image(machine.options().image_option(image.instance_name()).value());
41 
42 		// is an image specified for this image?
43 		if (!startup_image.empty())
44 		{
45 			// we do have a startup image specified - load it
46 			image_init_result result = image_init_result::FAIL;
47 
48 			// try as a softlist
49 			if (software_name_parse(startup_image))
50 				result = image.load_software(startup_image);
51 
52 			// failing that, try as an image
53 			if (result != image_init_result::PASS)
54 				result = image.load(startup_image);
55 
56 			// failing that, try creating it (if appropriate)
57 			if (result != image_init_result::PASS && image.support_command_line_image_creation())
58 				result = image.create(startup_image);
59 
60 			// did the image load fail?
61 			if (result != image_init_result::PASS)
62 			{
63 				// retrieve image error message
64 				std::string image_err = std::string(image.error());
65 				std::string startup_image_name = startup_image;
66 
67 				// unload the bad image
68 				image.unload();
69 
70 				// make sure it is removed from the ini file too
71 				machine.options().image_option(image.instance_name()).specify("");
72 				if (machine.options().write_config())
73 					write_config(machine.options(), nullptr, &machine.system());
74 
75 				throw emu_fatalerror(EMU_ERR_DEVICE, "Device %s load (-%s %s) failed: %s",
76 						image.device().name(),
77 						image.instance_name(),
78 						startup_image_name,
79 						image_err);
80 			}
81 		}
82 	}
83 
84 	machine.configuration().config_register("image_directories", config_load_delegate(&image_manager::config_load, this), config_save_delegate(&image_manager::config_save, this));
85 }
86 
87 //-------------------------------------------------
88 //  unload_all - unload all images and
89 //  extract options
90 //-------------------------------------------------
unload_all()91 void image_manager::unload_all()
92 {
93 	// extract the options
94 	options_extract();
95 
96 	for (device_image_interface &image : image_interface_iterator(machine().root_device()))
97 	{
98 		// unload this image
99 		image.unload();
100 	}
101 }
102 
config_load(config_type cfg_type,util::xml::data_node const * parentnode)103 void image_manager::config_load(config_type cfg_type, util::xml::data_node const *parentnode)
104 {
105 	if ((cfg_type == config_type::GAME) && (parentnode != nullptr))
106 	{
107 		for (util::xml::data_node const *node = parentnode->get_child("device"); node; node = node->get_next_sibling("device"))
108 		{
109 			const char *const dev_instance = node->get_attribute_string("instance", nullptr);
110 
111 			if ((dev_instance != nullptr) && (dev_instance[0] != '\0'))
112 			{
113 				for (device_image_interface &image : image_interface_iterator(machine().root_device()))
114 				{
115 					if (!strcmp(dev_instance, image.instance_name().c_str()))
116 					{
117 						const char *const working_directory = node->get_attribute_string("directory", nullptr);
118 						if (working_directory != nullptr)
119 							image.set_working_directory(working_directory);
120 					}
121 				}
122 			}
123 		}
124 	}
125 }
126 
127 /*-------------------------------------------------
128     config_save - saves out image device
129     directories to the configuration file
130 -------------------------------------------------*/
131 
config_save(config_type cfg_type,util::xml::data_node * parentnode)132 void image_manager::config_save(config_type cfg_type, util::xml::data_node *parentnode)
133 {
134 	/* only care about game-specific data */
135 	if (cfg_type == config_type::GAME)
136 	{
137 		for (device_image_interface &image : image_interface_iterator(machine().root_device()))
138 		{
139 			const char *const dev_instance = image.instance_name().c_str();
140 
141 			util::xml::data_node *const node = parentnode->add_child("device", nullptr);
142 			if (node != nullptr)
143 			{
144 				node->set_attribute("instance", dev_instance);
145 				node->set_attribute("directory", image.working_directory().c_str());
146 			}
147 		}
148 	}
149 }
150 
151 /*-------------------------------------------------
152     write_config - emit current option statuses as
153     INI files
154 -------------------------------------------------*/
155 
write_config(emu_options & options,const char * filename,const game_driver * gamedrv)156 int image_manager::write_config(emu_options &options, const char *filename, const game_driver *gamedrv)
157 {
158 	char buffer[128];
159 	int retval = 1;
160 
161 	if (gamedrv != nullptr)
162 	{
163 		sprintf(buffer, "%s.ini", gamedrv->name);
164 		filename = buffer;
165 	}
166 
167 	emu_file file(options.ini_path(), OPEN_FLAG_WRITE | OPEN_FLAG_CREATE);
168 	osd_file::error filerr = file.open(filename);
169 	if (filerr == osd_file::error::NONE)
170 	{
171 		std::string inistring = options.output_ini();
172 		file.puts(inistring.c_str());
173 		retval = 0;
174 	}
175 	return retval;
176 }
177 
178 /*-------------------------------------------------
179     options_extract - extract device options
180     out of core into the options
181 -------------------------------------------------*/
182 
options_extract()183 void image_manager::options_extract()
184 {
185 	for (device_image_interface &image : image_interface_iterator(machine().root_device()))
186 	{
187 		// There are two scenarios where we want to extract the option:
188 		//
189 		//  1.  When for the device, is_reset_on_load() is false (mounting devices for which is reset_and_load()
190 		//      is true forces a reset, hence the name)
191 		//
192 		//  2.  When is_reset_on_load(), and this results in a device being unmounted (unmounting is_reset_and_load()
193 		//      doesn't force an unmount).
194 		//
195 		//      Note that as a part of #2, we cannot extract the option when the image in question is a part of an
196 		//      active reset_on_load; hence the check for is_reset_and_loading() (see issue #2414)
197 		if (!image.is_reset_on_load()
198 			|| (!image.exists() && !image.is_reset_and_loading()
199 				&& machine().options().has_image_option(image.instance_name()) && !machine().options().image_option(image.instance_name()).value().empty()))
200 		{
201 			// we have to assemble the image option differently for software lists and for normal images
202 			std::string image_opt;
203 			if (image.exists())
204 			{
205 				if (!image.loaded_through_softlist())
206 					image_opt = image.filename();
207 				else if (image.part_entry() && !image.part_entry()->name().empty())
208 					image_opt = util::string_format("%s:%s:%s", image.software_list_name(), image.full_software_name(), image.part_entry()->name());
209 				else
210 					image_opt = util::string_format("%s:%s", image.software_list_name(), image.full_software_name());
211 			}
212 
213 			// and set the option (provided that it hasn't been removed out from under us)
214 			if (machine().options().exists(image.instance_name()) && machine().options().has_image_option(image.instance_name()))
215 				machine().options().image_option(image.instance_name()).specify(std::move(image_opt));
216 		}
217 	}
218 
219 	// write the config, if appropriate
220 	if (machine().options().write_config())
221 		write_config(machine().options(), nullptr, &machine().system());
222 }
223 
224 
225 /*-------------------------------------------------
226     postdevice_init - initialize devices for a specific
227     running_machine
228 -------------------------------------------------*/
229 
postdevice_init()230 void image_manager::postdevice_init()
231 {
232 	/* make sure that any required devices have been allocated */
233 	for (device_image_interface &image : image_interface_iterator(machine().root_device()))
234 	{
235 		image_init_result result = image.finish_load();
236 
237 		/* did the image load fail? */
238 		if (result != image_init_result::PASS)
239 		{
240 			/* retrieve image error message */
241 			std::string image_err = std::string(image.error());
242 
243 			/* unload all images */
244 			unload_all();
245 
246 			throw emu_fatalerror(EMU_ERR_DEVICE, "Device %s load failed: %s",
247 					image.device().name(),
248 					image_err);
249 		}
250 	}
251 	/* add a callback for when we shut down */
252 	machine().add_notifier(MACHINE_NOTIFY_EXIT, machine_notify_delegate(&image_manager::unload_all, this));
253 }
254