OXYGENHUB
◆ main openmsx / src / IPSPatch.cc
3.9 KiB · C++ · last changed chore(): initial commit 1 month ago source blame history raw
1
#include "IPSPatch.hh"
2
3
#include "File.hh"
4
#include "Filename.hh"
5
#include "MSXException.hh"
6
7
#include "ranges.hh"
8
#include "xrange.hh"
9
10
#include <algorithm>
11
#include <array>
12
#include <cassert>
13
14
namespace openmsx {
15
16
std::vector<IPSPatch::Chunk> IPSPatch::parseChunks() const
17
{
18
	std::vector<Chunk> result;
19
20
	File ipsFile(filename.getResolved());
21
22
	std::array<uint8_t, 5> header;
23
	ipsFile.read(header);
24
	if (!std::ranges::equal(header, std::string_view("PATCH"))) {
25
		throw MSXException("Invalid IPS file: ", filename.getOriginal());
26
	}
27
	std::array<uint8_t, 3> offsetBuf;
28
	ipsFile.read(offsetBuf);
29
	while (!std::ranges::equal(offsetBuf, std::string_view("EOF"))) {
30
		size_t offset = 0x10000 * offsetBuf[0] + 0x100 * offsetBuf[1] + offsetBuf[2];
31
		std::array<uint8_t, 2> lenBuf;
32
		ipsFile.read(lenBuf);
33
		size_t length = 0x100 * lenBuf[0] + lenBuf[1];
34
		MemBuffer<uint8_t> v;
35
		if (length == 0) {
36
			// RLE encoded
37
			std::array<uint8_t, 3> rleBuf;
38
			ipsFile.read(rleBuf);
39
			length = 0x100 * rleBuf[0] + rleBuf[1];
40
			v.resize(length);
41
			std::ranges::fill(v, rleBuf[2]);
42
		} else {
43
			// patch bytes
44
			v.resize(length);
45
			ipsFile.read(v);
46
		}
47
		// find overlapping or adjacent patch regions
48
		auto b = std::ranges::lower_bound(result, offset, {}, &Chunk::startAddress);
49
		if (b != begin(result)) {
50
			--b;
51
			if (b->stopAddress() < offset) ++b;
52
		}
53
		if (auto e = std::ranges::upper_bound(result, offset + v.size(), {}, &Chunk::startAddress);
54
		    b != e) {
55
			// remove overlapping regions, merge adjacent regions
56
			--e;
57
			auto start = std::min(b->startAddress, offset);
58
			auto stop  = std::max(offset + length, e->stopAddress());
59
			auto length2 = stop - start;
60
			++e;
61
			MemBuffer<uint8_t> tmp(length2);
62
			for (auto it : xrange(b, e)) {
63
				copy_to_range(*it, subspan(tmp, it->startAddress - start));
64
			}
65
			copy_to_range(v, subspan(tmp, offset - start));
66
			*b = Chunk{.startAddress = start, .content = std::move(tmp)};
67
			result.erase(b + 1, e);
68
		} else {
69
			// add new region
70
			result.emplace(b, offset, std::move(v));
71
		}
72
73
		ipsFile.read(offsetBuf);
74
	}
75
	return result;
76
}
77
78
size_t IPSPatch::calcSize() const
79
{
80
	return chunks.empty()
81
		? parent->getSize()
82
		: std::max(parent->getSize(), chunks.back().stopAddress());
83
}
84
85
IPSPatch::IPSPatch(Filename filename_,
86
                   std::unique_ptr<const PatchInterface> parent_)
87
	: filename(std::move(filename_))
88
	, parent(std::move(parent_))
89
	, chunks(parseChunks())
90
	, size(calcSize())
91
{
92
}
93
94
void IPSPatch::copyBlock(size_t src, std::span<uint8_t> dst) const
95
{
96
	parent->copyBlock(src, dst);
97
98
	auto b = std::ranges::lower_bound(chunks, src, {}, &Chunk::startAddress);
99
	if (b != begin(chunks)) --b;
100
	auto srcEnd = src + dst.size();
101
	auto e = std::ranges::upper_bound(chunks, srcEnd - 1, {}, &Chunk::startAddress);
102
	for (auto it : xrange(b, e)) {
103
		auto chunkStart = it->startAddress;
104
		auto chunkSize = int(it->size());
105
		// calc chunkOffset, chunkStart
106
		auto chunkOffset = int(src - chunkStart);
107
		if (chunkOffset < 0) {
108
			// start after src
109
			assert(-chunkOffset < int(dst.size())); // dont start past end
110
			chunkOffset = 0;
111
		} else if (chunkOffset >= chunkSize) {
112
			// chunk completely before src, skip
113
			continue;
114
		} else {
115
			// start before src
116
			chunkSize  -= chunkOffset;
117
			chunkStart += chunkOffset;
118
		}
119
		// calc chunkSize
120
		assert(src <= chunkStart);
121
		if (auto overflow = int(chunkStart - src + chunkSize - dst.size());
122
		    overflow > 0) {
123
			assert(chunkSize > overflow);
124
			chunkSize -= overflow;
125
		}
126
		// copy
127
		assert(chunkOffset < int(it->size()));
128
		assert((chunkOffset + chunkSize) <= int(it->size()));
129
		assert(src <= chunkStart);
130
		assert((chunkStart + chunkSize) <= srcEnd);
131
		copy_to_range(subspan(*it, chunkOffset, size_t(chunkSize)),
132
		              subspan(dst, chunkStart - src));
133
	}
134
}
135
136
std::vector<Filename> IPSPatch::getFilenames() const
137
{
138
	auto result = parent->getFilenames();
139
	result.push_back(filename);
140
	return result;
141
}
142
143
} // namespace openmsx