/* ###
 * IP: GHIDRA
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package firmware.option_rom;

import ghidra.app.util.Option;
import ghidra.app.util.bin.BinaryReader;
import ghidra.app.util.bin.ByteProvider;
import ghidra.app.util.importer.MessageLog;
import ghidra.app.util.opinion.AbstractLibrarySupportLoader;
import ghidra.app.util.opinion.LoadSpec;
import ghidra.program.flatapi.FlatProgramAPI;
import ghidra.program.model.address.Address;
import ghidra.program.model.lang.LanguageCompilerSpecPair;
import ghidra.program.model.listing.Program;
import ghidra.util.Msg;
import ghidra.util.task.TaskMonitor;

import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;

public class LegacyOptionROMLoader extends AbstractLibrarySupportLoader {
	@Override
	public Collection<LoadSpec> findSupportedLoadSpecs(ByteProvider provider) {
		ArrayList<LoadSpec> loadSpecs = new ArrayList<>();
		BinaryReader reader = new BinaryReader(provider, true);
		try {
			new LegacyOptionROMHeader(reader);
		} catch (IOException e) {
			return loadSpecs;
		}

		loadSpecs.add(new LoadSpec(this, 0,
				new LanguageCompilerSpecPair("x86:LE:16:Real Mode", "default"), true));
		return loadSpecs;
	}

	@Override
	protected void load(ByteProvider provider, LoadSpec loadSpec, List<Option> options,
			Program program, TaskMonitor monitor, MessageLog log) throws IOException {
		BinaryReader reader = new BinaryReader(provider, true);
		LegacyOptionROMHeader header = new LegacyOptionROMHeader(reader);

		// Create a segment for the entire option ROM.
		FlatProgramAPI api = new FlatProgramAPI(program, monitor);
		InputStream romStream = provider.getInputStream(0);
		try {
			api.createMemoryBlock("Option ROM", api.toAddr(0), romStream, romStream.available(),
					false);
		} catch (Exception e) {
			Msg.showError(this, null, getName() + " Loader", e.getMessage(), e);
			return;
		}

		// Retrieve the decoded entry point address from the legacy option ROM header, and define
		// the entry point function.
		Address entryPoint = api.toAddr(header.getEntryPointOffset());
		api.addEntryPoint(entryPoint);
		api.createFunction(entryPoint, "entry");

		// Mark the legacy option ROM header, PCI data structure, and device list as data.
		try {
			api.createData(api.toAddr(0), header.toDataType());
			PCIDataStructureHeader pcirHeader = header.getPCIRHeader();
			api.createData(api.toAddr(header.getPCIRHeaderOffset()), pcirHeader.toDataType());

			DeviceList deviceList = header.getDeviceList();
			if (deviceList != null) {
				api.createData(api.toAddr(header.getPCIRHeaderOffset() +
						pcirHeader.getDeviceListOffset()), deviceList.toDataType());
			}
		} catch (Exception e) {
			Msg.showError(this, null, getName() + " Loader", e.getMessage(), e);
		}
	}

	@Override
	public String getName() {
		return "x86 PCI Option ROM";
	}
}
