Skip to content

Commit 0a28cb3

Browse files
authored
Merge pull request #2 from ZaparooProject/feature/chd-support
feat: add CHD disc image support
2 parents 1c33d0a + 82f3332 commit 0a28cb3

31 files changed

+4471
-39
lines changed

chd/bitstream.go

Lines changed: 190 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,190 @@
1+
// Copyright (c) 2025 Niema Moshiri and The Zaparoo Project.
2+
// SPDX-License-Identifier: GPL-3.0-or-later
3+
//
4+
// This file is part of go-gameid.
5+
//
6+
// go-gameid is free software: you can redistribute it and/or modify
7+
// it under the terms of the GNU General Public License as published by
8+
// the Free Software Foundation, either version 3 of the License, or
9+
// (at your option) any later version.
10+
//
11+
// go-gameid is distributed in the hope that it will be useful,
12+
// but WITHOUT ANY WARRANTY; without even the implied warranty of
13+
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14+
// GNU General Public License for more details.
15+
//
16+
// You should have received a copy of the GNU General Public License
17+
// along with go-gameid. If not, see <https://www.gnu.org/licenses/>.
18+
19+
package chd
20+
21+
// bitReader reads bits from a byte slice.
22+
type bitReader struct {
23+
data []byte
24+
offset int // bit offset
25+
bits uint // accumulated bits
26+
avail int // bits available in accumulator
27+
}
28+
29+
// newBitReader creates a new bit reader.
30+
func newBitReader(data []byte) *bitReader {
31+
return &bitReader{data: data}
32+
}
33+
34+
// read reads count bits from the stream.
35+
func (br *bitReader) read(count int) uint32 {
36+
// Fill accumulator as needed
37+
for br.avail < count {
38+
byteOff := br.offset / 8
39+
if byteOff >= len(br.data) {
40+
br.bits <<= 8
41+
br.avail += 8
42+
continue
43+
}
44+
br.bits = (br.bits << 8) | uint(br.data[byteOff])
45+
br.avail += 8
46+
br.offset += 8
47+
}
48+
49+
// Extract the bits
50+
br.avail -= count
51+
//nolint:gosec // Safe: bits accumulator is bounded by count which is at most 32
52+
result := uint32((br.bits >> br.avail) & ((1 << count) - 1))
53+
return result
54+
}
55+
56+
// huffmanDecoder decodes Huffman-encoded data for CHD V5 maps.
57+
type huffmanDecoder struct {
58+
lookup []uint32
59+
nodeBits []uint8
60+
numCodes int
61+
maxBits int
62+
}
63+
64+
// newHuffmanDecoder creates a Huffman decoder for the given parameters.
65+
func newHuffmanDecoder(numCodes, maxBits int) *huffmanDecoder {
66+
return &huffmanDecoder{
67+
numCodes: numCodes,
68+
maxBits: maxBits,
69+
nodeBits: make([]uint8, numCodes),
70+
lookup: make([]uint32, 1<<maxBits),
71+
}
72+
}
73+
74+
// importTreeRLE imports a Huffman tree encoded with RLE.
75+
func (hd *huffmanDecoder) importTreeRLE(br *bitReader) error {
76+
// Determine number of bits to read for each node
77+
var numBits int
78+
switch {
79+
case hd.maxBits >= 16:
80+
numBits = 5
81+
case hd.maxBits >= 8:
82+
numBits = 4
83+
default:
84+
numBits = 3
85+
}
86+
87+
// Read the tree with RLE decoding
88+
for curNode := 0; curNode < hd.numCodes; {
89+
nodeBits := br.read(numBits)
90+
if nodeBits != 1 {
91+
//nolint:gosec // Safe: nodeBits from Huffman tree is bounded to 0-32
92+
hd.nodeBits[curNode] = uint8(nodeBits)
93+
curNode++
94+
continue
95+
}
96+
// RLE encoding: read actual value
97+
nodeBits = br.read(numBits)
98+
if nodeBits == 1 {
99+
// Literal 1
100+
hd.nodeBits[curNode] = 1
101+
curNode++
102+
continue
103+
}
104+
// Repeat count follows
105+
repCount := int(br.read(numBits)) + 3
106+
//nolint:gosec // Safe: nodeBits from Huffman tree is bounded to 0-32
107+
curNode = hd.fillNodeBits(curNode, uint8(nodeBits), repCount)
108+
}
109+
110+
// Build lookup table
111+
return hd.buildLookup()
112+
}
113+
114+
// fillNodeBits fills nodeBits with a repeated value, returning the new curNode.
115+
func (hd *huffmanDecoder) fillNodeBits(curNode int, value uint8, repCount int) int {
116+
for i := 0; i < repCount && curNode < hd.numCodes; i++ {
117+
hd.nodeBits[curNode] = value
118+
curNode++
119+
}
120+
return curNode
121+
}
122+
123+
// buildLookup builds the lookup table from node bits.
124+
// This follows MAME's canonical code assignment which processes from highest to lowest bit length.
125+
func (hd *huffmanDecoder) buildLookup() error {
126+
// Build histogram of bit lengths
127+
bithisto := make([]uint32, 33)
128+
for i := range hd.numCodes {
129+
if hd.nodeBits[i] <= 32 {
130+
bithisto[hd.nodeBits[i]]++
131+
}
132+
}
133+
134+
// For each code length, determine the starting code number
135+
// Process from highest to lowest bit length (MAME convention)
136+
var curstart uint32
137+
for codelen := 32; codelen > 0; codelen-- {
138+
nextstart := (curstart + bithisto[codelen]) >> 1
139+
bithisto[codelen] = curstart
140+
curstart = nextstart
141+
}
142+
143+
// Now assign canonical codes and build lookup table
144+
// nodeBits stores the assigned code for each symbol
145+
nodeCodes := make([]uint32, hd.numCodes)
146+
for i := range hd.numCodes {
147+
bits := hd.nodeBits[i]
148+
if bits > 0 {
149+
nodeCodes[i] = bithisto[bits]
150+
bithisto[bits]++
151+
}
152+
}
153+
154+
// Build lookup table
155+
for i := range hd.numCodes {
156+
bits := int(hd.nodeBits[i])
157+
if bits > 0 {
158+
// Set up the entry: (symbol << 5) | numbits
159+
//nolint:gosec // Safe: i bounded by numCodes (16), bits bounded by maxBits (8)
160+
value := uint32((i << 5) | bits)
161+
162+
// Fill all matching entries
163+
shift := hd.maxBits - bits
164+
base := int(nodeCodes[i]) << shift
165+
end := int(nodeCodes[i]+1)<<shift - 1
166+
for j := base; j <= end; j++ {
167+
hd.lookup[j] = value
168+
}
169+
}
170+
}
171+
172+
return nil
173+
}
174+
175+
// decode decodes a single symbol from the bit stream.
176+
func (hd *huffmanDecoder) decode(br *bitReader) uint8 {
177+
// Peek maxBits bits
178+
peek := br.read(hd.maxBits)
179+
entry := hd.lookup[peek]
180+
//nolint:gosec // Safe: entry stores symbol in upper bits, bounded by numCodes (16)
181+
symbol := uint8(entry >> 5)
182+
bits := int(entry & 0x1f)
183+
184+
// Put back unused bits by adjusting the bit reader
185+
if bits < hd.maxBits {
186+
br.avail += hd.maxBits - bits
187+
}
188+
189+
return symbol
190+
}

0 commit comments

Comments
 (0)