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