tangled
alpha
login
or
join now
baileytownsend.dev
/
DMG-Playground
0
fork
atom
Playing around with reading gameboy roms, and maybe emulation
0
fork
atom
overview
issues
pulls
pipelines
it's printing something
baileytownsend.dev
7 months ago
dcbe1c72
1b840921
+463
-2
4 changed files
expand all
collapse all
unified
split
Cargo.lock
Cargo.toml
src
main.rs
tile_map.rs
+229
Cargo.lock
···
5
5
[[package]]
6
6
name = "GameBoyPlayground"
7
7
version = "0.1.0"
8
8
+
dependencies = [
9
9
+
"minifb",
10
10
+
]
11
11
+
12
12
+
[[package]]
13
13
+
name = "bitflags"
14
14
+
version = "1.3.2"
15
15
+
source = "registry+https://github.com/rust-lang/crates.io-index"
16
16
+
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
17
17
+
18
18
+
[[package]]
19
19
+
name = "bitflags"
20
20
+
version = "2.9.1"
21
21
+
source = "registry+https://github.com/rust-lang/crates.io-index"
22
22
+
checksum = "1b8e56985ec62d17e9c1001dc89c88ecd7dc08e47eba5ec7c29c7b5eeecde967"
23
23
+
24
24
+
[[package]]
25
25
+
name = "cc"
26
26
+
version = "1.2.30"
27
27
+
source = "registry+https://github.com/rust-lang/crates.io-index"
28
28
+
checksum = "deec109607ca693028562ed836a5f1c4b8bd77755c4e132fc5ce11b0b6211ae7"
29
29
+
dependencies = [
30
30
+
"shlex",
31
31
+
]
32
32
+
33
33
+
[[package]]
34
34
+
name = "cfg-if"
35
35
+
version = "1.0.1"
36
36
+
source = "registry+https://github.com/rust-lang/crates.io-index"
37
37
+
checksum = "9555578bc9e57714c812a1f84e4fc5b4d21fcb063490c624de019f7464c91268"
38
38
+
39
39
+
[[package]]
40
40
+
name = "gdi32-sys"
41
41
+
version = "0.1.2"
42
42
+
source = "registry+https://github.com/rust-lang/crates.io-index"
43
43
+
checksum = "8e3eb92c1107527888f86b6ebb0b7f82794777dbf172a932998660a0a2e26c11"
44
44
+
dependencies = [
45
45
+
"winapi 0.2.8",
46
46
+
"winapi-build",
47
47
+
]
48
48
+
49
49
+
[[package]]
50
50
+
name = "kernel32-sys"
51
51
+
version = "0.2.2"
52
52
+
source = "registry+https://github.com/rust-lang/crates.io-index"
53
53
+
checksum = "7507624b29483431c0ba2d82aece8ca6cdba9382bff4ddd0f7490560c056098d"
54
54
+
dependencies = [
55
55
+
"winapi 0.2.8",
56
56
+
"winapi-build",
57
57
+
]
58
58
+
59
59
+
[[package]]
60
60
+
name = "lazy_static"
61
61
+
version = "0.2.11"
62
62
+
source = "registry+https://github.com/rust-lang/crates.io-index"
63
63
+
checksum = "76f033c7ad61445c5b347c7382dd1237847eb1bce590fe50365dcb33d546be73"
64
64
+
65
65
+
[[package]]
66
66
+
name = "lazy_static"
67
67
+
version = "1.5.0"
68
68
+
source = "registry+https://github.com/rust-lang/crates.io-index"
69
69
+
checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe"
70
70
+
71
71
+
[[package]]
72
72
+
name = "libc"
73
73
+
version = "0.2.174"
74
74
+
source = "registry+https://github.com/rust-lang/crates.io-index"
75
75
+
checksum = "1171693293099992e19cddea4e8b849964e9846f4acee11b3948bcc337be8776"
76
76
+
77
77
+
[[package]]
78
78
+
name = "libredox"
79
79
+
version = "0.1.6"
80
80
+
source = "registry+https://github.com/rust-lang/crates.io-index"
81
81
+
checksum = "4488594b9328dee448adb906d8b126d9b7deb7cf5c22161ee591610bb1be83c0"
82
82
+
dependencies = [
83
83
+
"bitflags 2.9.1",
84
84
+
"libc",
85
85
+
"redox_syscall",
86
86
+
]
87
87
+
88
88
+
[[package]]
89
89
+
name = "minifb"
90
90
+
version = "0.10.7"
91
91
+
source = "registry+https://github.com/rust-lang/crates.io-index"
92
92
+
checksum = "8cebe353532532dd30eaab68ece91e624ccf6f97453f2da84042f5915450a137"
93
93
+
dependencies = [
94
94
+
"cc",
95
95
+
"gdi32-sys",
96
96
+
"kernel32-sys",
97
97
+
"orbclient",
98
98
+
"time",
99
99
+
"user32-sys",
100
100
+
"winapi 0.2.8",
101
101
+
"x11-dl",
102
102
+
]
103
103
+
104
104
+
[[package]]
105
105
+
name = "orbclient"
106
106
+
version = "0.3.48"
107
107
+
source = "registry+https://github.com/rust-lang/crates.io-index"
108
108
+
checksum = "ba0b26cec2e24f08ed8bb31519a9333140a6599b867dac464bb150bdb796fd43"
109
109
+
dependencies = [
110
110
+
"libc",
111
111
+
"libredox",
112
112
+
"sdl2",
113
113
+
"sdl2-sys",
114
114
+
]
115
115
+
116
116
+
[[package]]
117
117
+
name = "pkg-config"
118
118
+
version = "0.3.32"
119
119
+
source = "registry+https://github.com/rust-lang/crates.io-index"
120
120
+
checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c"
121
121
+
122
122
+
[[package]]
123
123
+
name = "redox_syscall"
124
124
+
version = "0.5.15"
125
125
+
source = "registry+https://github.com/rust-lang/crates.io-index"
126
126
+
checksum = "7e8af0dde094006011e6a740d4879319439489813bd0bcdc7d821beaeeff48ec"
127
127
+
dependencies = [
128
128
+
"bitflags 2.9.1",
129
129
+
]
130
130
+
131
131
+
[[package]]
132
132
+
name = "sdl2"
133
133
+
version = "0.35.2"
134
134
+
source = "registry+https://github.com/rust-lang/crates.io-index"
135
135
+
checksum = "f7959277b623f1fb9e04aea73686c3ca52f01b2145f8ea16f4ff30d8b7623b1a"
136
136
+
dependencies = [
137
137
+
"bitflags 1.3.2",
138
138
+
"lazy_static 1.5.0",
139
139
+
"libc",
140
140
+
"sdl2-sys",
141
141
+
]
142
142
+
143
143
+
[[package]]
144
144
+
name = "sdl2-sys"
145
145
+
version = "0.35.2"
146
146
+
source = "registry+https://github.com/rust-lang/crates.io-index"
147
147
+
checksum = "e3586be2cf6c0a8099a79a12b4084357aa9b3e0b0d7980e3b67aaf7a9d55f9f0"
148
148
+
dependencies = [
149
149
+
"cfg-if",
150
150
+
"libc",
151
151
+
"version-compare",
152
152
+
]
153
153
+
154
154
+
[[package]]
155
155
+
name = "shlex"
156
156
+
version = "1.3.0"
157
157
+
source = "registry+https://github.com/rust-lang/crates.io-index"
158
158
+
checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
159
159
+
160
160
+
[[package]]
161
161
+
name = "time"
162
162
+
version = "0.1.45"
163
163
+
source = "registry+https://github.com/rust-lang/crates.io-index"
164
164
+
checksum = "1b797afad3f312d1c66a56d11d0316f916356d11bd158fbc6ca6389ff6bf805a"
165
165
+
dependencies = [
166
166
+
"libc",
167
167
+
"wasi",
168
168
+
"winapi 0.3.9",
169
169
+
]
170
170
+
171
171
+
[[package]]
172
172
+
name = "user32-sys"
173
173
+
version = "0.1.3"
174
174
+
source = "registry+https://github.com/rust-lang/crates.io-index"
175
175
+
checksum = "e6b719983b952c04198829b51653c06af36f0e44c967fcc1a2bb397ceafbf80a"
176
176
+
dependencies = [
177
177
+
"winapi 0.2.8",
178
178
+
"winapi-build",
179
179
+
]
180
180
+
181
181
+
[[package]]
182
182
+
name = "version-compare"
183
183
+
version = "0.1.1"
184
184
+
source = "registry+https://github.com/rust-lang/crates.io-index"
185
185
+
checksum = "579a42fc0b8e0c63b76519a339be31bed574929511fa53c1a3acae26eb258f29"
186
186
+
187
187
+
[[package]]
188
188
+
name = "wasi"
189
189
+
version = "0.10.0+wasi-snapshot-preview1"
190
190
+
source = "registry+https://github.com/rust-lang/crates.io-index"
191
191
+
checksum = "1a143597ca7c7793eff794def352d41792a93c481eb1042423ff7ff72ba2c31f"
192
192
+
193
193
+
[[package]]
194
194
+
name = "winapi"
195
195
+
version = "0.2.8"
196
196
+
source = "registry+https://github.com/rust-lang/crates.io-index"
197
197
+
checksum = "167dc9d6949a9b857f3451275e911c3f44255842c1f7a76f33c55103a909087a"
198
198
+
199
199
+
[[package]]
200
200
+
name = "winapi"
201
201
+
version = "0.3.9"
202
202
+
source = "registry+https://github.com/rust-lang/crates.io-index"
203
203
+
checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
204
204
+
dependencies = [
205
205
+
"winapi-i686-pc-windows-gnu",
206
206
+
"winapi-x86_64-pc-windows-gnu",
207
207
+
]
208
208
+
209
209
+
[[package]]
210
210
+
name = "winapi-build"
211
211
+
version = "0.1.1"
212
212
+
source = "registry+https://github.com/rust-lang/crates.io-index"
213
213
+
checksum = "2d315eee3b34aca4797b2da6b13ed88266e6d612562a0c46390af8299fc699bc"
214
214
+
215
215
+
[[package]]
216
216
+
name = "winapi-i686-pc-windows-gnu"
217
217
+
version = "0.4.0"
218
218
+
source = "registry+https://github.com/rust-lang/crates.io-index"
219
219
+
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
220
220
+
221
221
+
[[package]]
222
222
+
name = "winapi-x86_64-pc-windows-gnu"
223
223
+
version = "0.4.0"
224
224
+
source = "registry+https://github.com/rust-lang/crates.io-index"
225
225
+
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
226
226
+
227
227
+
[[package]]
228
228
+
name = "x11-dl"
229
229
+
version = "2.14.0"
230
230
+
source = "registry+https://github.com/rust-lang/crates.io-index"
231
231
+
checksum = "326c500cdc166fd7c70dd8c8a829cd5c0ce7be5a5d98c25817de2b9bdc67faf8"
232
232
+
dependencies = [
233
233
+
"lazy_static 0.2.11",
234
234
+
"libc",
235
235
+
"pkg-config",
236
236
+
]
+1
Cargo.toml
···
4
4
edition = "2024"
5
5
6
6
[dependencies]
7
7
+
minifb = "0.10.7"
+37
-2
src/main.rs
···
1
1
mod cartridge_header;
2
2
mod enums;
3
3
-
3
3
+
mod tile_map;
4
4
use crate::cartridge_header::CartridgeHeader;
5
5
use crate::enums::CartridgeHeaderAddress::OldLicenseeCode;
6
6
use crate::enums::{
7
7
CGBFlag, CartridgeHeaderAddress, CartridgeType, DestinationCode, Error, RamSize, RomSize,
8
8
};
9
9
+
use crate::tile_map::{GPU, VRAM_BEGIN, VRAM_END};
10
10
+
use minifb::{Key, Window, WindowOptions};
9
11
use std::fs::File;
10
12
use std::io::Read;
11
13
14
14
+
const WINDOW_DIMENSIONS: [usize; 2] = [(160 * 1), (144 * 1)];
15
15
+
12
16
// https://github.com/ISSOtm/gb-bootroms/blob/2dce25910043ce2ad1d1d3691436f2c7aabbda00/src/dmg.asm#L259-L269
13
17
// Each tile is encoded using 2 (!) bytes
14
18
// The tiles are represented below
···
43
47
));
44
48
}
45
49
};
46
46
-
// cart_header.print_test();
47
50
48
51
let title: String = String::from_iter(cart_header.title);
49
52
println!("Title: {}", title);
···
70
73
println!("Version: {:?}", cart_header.version);
71
74
println!("Header Checksum: {:#X}", cart_header.header_checksum);
72
75
println!("Global Checksum: {:#X}", cart_header.global_checksum);
76
76
+
77
77
+
let mut gpu = GPU::new();
78
78
+
let tile_map_buffer = &rom_buffer[VRAM_BEGIN as usize..VRAM_END as usize];
79
79
+
for (i, byte) in tile_map_buffer.iter().enumerate() {
80
80
+
gpu.write_vram(i, *byte);
81
81
+
}
82
82
+
gpu.render_tile_to_rgb(0);
83
83
+
// let range_of_tiles = 0..255;
84
84
+
// for tile_id in range_of_tiles {
85
85
+
// let idk = gpu.print_tile_ascii(tile_id);
86
86
+
// println!("{:?}", idk);
87
87
+
// }
88
88
+
let mut window = Window::new(
89
89
+
"DMG-01",
90
90
+
WINDOW_DIMENSIONS[0],
91
91
+
WINDOW_DIMENSIONS[1],
92
92
+
WindowOptions {
93
93
+
scale: minifb::Scale::X2,
94
94
+
..WindowOptions::default()
95
95
+
},
96
96
+
)
97
97
+
.unwrap();
98
98
+
let mut tile_ids: Vec<u8> = (0..100).collect();
99
99
+
let tile_map_buffer = gpu.render_tile_map(&tile_ids, 166, 144);
100
100
+
// let idk = gpu.render_tile_to_rgb(1).unwrap();
101
101
+
let buffer_u32: Vec<u32> = tile_map_buffer
102
102
+
.iter()
103
103
+
.map(|(r, g, b)| ((*r as u32) << 16) | ((*g as u32) << 8) | (*b as u32))
104
104
+
.collect();
105
105
+
while window.is_open() && !window.is_key_down(Key::Escape) {
106
106
+
window.update_with_buffer(&buffer_u32).unwrap();
107
107
+
}
73
108
74
109
Ok(())
75
110
}
+196
src/tile_map.rs
···
1
1
+
pub const VRAM_BEGIN: usize = 0x8000;
2
2
+
pub const VRAM_END: usize = 0x9FFF;
3
3
+
pub const VRAM_SIZE: usize = VRAM_END - VRAM_BEGIN + 1;
4
4
+
5
5
+
#[derive(Copy, Clone, Debug, PartialEq)]
6
6
+
pub enum TilePixelValue {
7
7
+
Zero,
8
8
+
One,
9
9
+
Two,
10
10
+
Three,
11
11
+
}
12
12
+
13
13
+
impl TilePixelValue {
14
14
+
/// Convert pixel value to grayscale color (0-255)
15
15
+
pub fn to_grayscale(&self) -> u8 {
16
16
+
match self {
17
17
+
TilePixelValue::Zero => 255, // White
18
18
+
TilePixelValue::One => 170, // Light gray (66% brightness)
19
19
+
TilePixelValue::Two => 85, // Dark gray (33% brightness)
20
20
+
TilePixelValue::Three => 0, // Black
21
21
+
}
22
22
+
}
23
23
+
24
24
+
/// Convert pixel value to RGB color tuple
25
25
+
pub fn to_rgb(&self) -> (u8, u8, u8) {
26
26
+
let gray = self.to_grayscale();
27
27
+
(gray, gray, gray)
28
28
+
}
29
29
+
30
30
+
/// Convert pixel value to classic Game Boy green colors
31
31
+
pub fn to_gameboy_green(&self) -> (u8, u8, u8) {
32
32
+
match self {
33
33
+
TilePixelValue::Zero => (224, 248, 208), // Lightest green
34
34
+
TilePixelValue::One => (136, 192, 112), // Light green
35
35
+
TilePixelValue::Two => (52, 104, 86), // Dark green
36
36
+
TilePixelValue::Three => (8, 24, 32), // Darkest green/black
37
37
+
}
38
38
+
}
39
39
+
}
40
40
+
41
41
+
type Tile = [[TilePixelValue; 8]; 8];
42
42
+
43
43
+
fn empty_tile() -> Tile {
44
44
+
[[TilePixelValue::Zero; 8]; 8]
45
45
+
}
46
46
+
47
47
+
pub struct GPU {
48
48
+
vram: [u8; VRAM_SIZE],
49
49
+
tile_set: [Tile; 384], // 384 tiles total (256 from first set + 128 from second set)
50
50
+
}
51
51
+
52
52
+
impl GPU {
53
53
+
pub fn new() -> Self {
54
54
+
Self {
55
55
+
vram: [0; VRAM_SIZE],
56
56
+
tile_set: [empty_tile(); 384],
57
57
+
}
58
58
+
}
59
59
+
60
60
+
pub fn read_vram(&self, address: usize) -> u8 {
61
61
+
self.vram[address]
62
62
+
}
63
63
+
64
64
+
pub fn write_vram(&mut self, index: usize, value: u8) {
65
65
+
self.vram[index] = value;
66
66
+
67
67
+
// If our index is greater than 0x1800, we're not writing to the tile set storage
68
68
+
// so we can just return.
69
69
+
if index >= 0x1800 {
70
70
+
return;
71
71
+
}
72
72
+
73
73
+
// Tiles rows are encoded in two bytes with the first byte always
74
74
+
// on an even address. Bitwise ANDing the address with 0xffe
75
75
+
// gives us the address of the first byte.
76
76
+
let normalized_index = index & 0xFFFE;
77
77
+
78
78
+
// First we need to get the two bytes that encode the tile row.
79
79
+
let byte1 = self.vram[normalized_index];
80
80
+
let byte2 = self.vram[normalized_index + 1];
81
81
+
82
82
+
// A tile is 8 rows tall. Since each row is encoded with two bytes a tile
83
83
+
// is therefore 16 bytes in total.
84
84
+
let tile_index = index / 16;
85
85
+
// Every two bytes is a new row
86
86
+
let row_index = (index % 16) / 2;
87
87
+
88
88
+
// Now we're going to loop 8 times to get the 8 pixels that make up a given row.
89
89
+
for pixel_index in 0..8 {
90
90
+
let mask = 1 << (7 - pixel_index);
91
91
+
let lsb = byte1 & mask;
92
92
+
let msb = byte2 & mask;
93
93
+
94
94
+
let value = match (lsb != 0, msb != 0) {
95
95
+
(true, true) => TilePixelValue::Three,
96
96
+
(false, true) => TilePixelValue::Two,
97
97
+
(true, false) => TilePixelValue::One,
98
98
+
(false, false) => TilePixelValue::Zero,
99
99
+
};
100
100
+
101
101
+
self.tile_set[tile_index][row_index][pixel_index] = value;
102
102
+
}
103
103
+
}
104
104
+
105
105
+
/// Get a tile by its index
106
106
+
pub fn get_tile(&self, tile_index: usize) -> Option<&Tile> {
107
107
+
if tile_index < self.tile_set.len() {
108
108
+
Some(&self.tile_set[tile_index])
109
109
+
} else {
110
110
+
None
111
111
+
}
112
112
+
}
113
113
+
114
114
+
/// Render a tile to a color buffer (64 pixels as RGB values)
115
115
+
pub fn render_tile_to_rgb(&self, tile_index: usize) -> Option<[(u8, u8, u8); 64]> {
116
116
+
let tile = self.get_tile(tile_index)?;
117
117
+
let mut color_buffer = [(0, 0, 0); 64];
118
118
+
119
119
+
for (row_idx, row) in tile.iter().enumerate() {
120
120
+
for (col_idx, &pixel) in row.iter().enumerate() {
121
121
+
let buffer_index = row_idx * 8 + col_idx;
122
122
+
color_buffer[buffer_index] = pixel.to_gameboy_green();
123
123
+
}
124
124
+
}
125
125
+
126
126
+
Some(color_buffer)
127
127
+
}
128
128
+
129
129
+
/// Render a tile to grayscale buffer (64 pixels as grayscale values)
130
130
+
pub fn render_tile_to_grayscale(&self, tile_index: usize) -> Option<[u8; 64]> {
131
131
+
let tile = self.get_tile(tile_index)?;
132
132
+
let mut gray_buffer = [0u8; 64];
133
133
+
134
134
+
for (row_idx, row) in tile.iter().enumerate() {
135
135
+
for (col_idx, &pixel) in row.iter().enumerate() {
136
136
+
let buffer_index = row_idx * 8 + col_idx;
137
137
+
gray_buffer[buffer_index] = pixel.to_grayscale();
138
138
+
}
139
139
+
}
140
140
+
141
141
+
Some(gray_buffer)
142
142
+
}
143
143
+
144
144
+
/// Render multiple tiles in a grid pattern
145
145
+
pub fn render_tile_map(
146
146
+
&self,
147
147
+
tile_indices: &[u8],
148
148
+
map_width: usize,
149
149
+
map_height: usize,
150
150
+
) -> Vec<(u8, u8, u8)> {
151
151
+
let total_pixels = map_width * 8 * map_height * 8; // 8x8 pixels per tile
152
152
+
let mut color_buffer = vec![(0, 0, 0); total_pixels];
153
153
+
154
154
+
for (map_idx, &tile_idx) in tile_indices.iter().enumerate() {
155
155
+
if let Some(tile) = self.get_tile(tile_idx as usize) {
156
156
+
let tile_x = map_idx % map_width;
157
157
+
let tile_y = map_idx / map_width;
158
158
+
159
159
+
for (row_idx, row) in tile.iter().enumerate() {
160
160
+
for (col_idx, &pixel) in row.iter().enumerate() {
161
161
+
let pixel_x = tile_x * 8 + col_idx;
162
162
+
let pixel_y = tile_y * 8 + row_idx;
163
163
+
let buffer_index = pixel_y * (map_width * 8) + pixel_x;
164
164
+
165
165
+
if buffer_index < color_buffer.len() {
166
166
+
color_buffer[buffer_index] = pixel.to_gameboy_green();
167
167
+
}
168
168
+
}
169
169
+
}
170
170
+
}
171
171
+
}
172
172
+
173
173
+
color_buffer
174
174
+
}
175
175
+
176
176
+
/// Debug function to print a tile as ASCII art
177
177
+
pub fn print_tile_ascii(&self, tile_index: usize) {
178
178
+
if let Some(tile) = self.get_tile(tile_index) {
179
179
+
// println!("Tile {}:", tile_index);
180
180
+
for row in tile {
181
181
+
for &pixel in row {
182
182
+
let char = match pixel {
183
183
+
TilePixelValue::Zero => '░', // Light
184
184
+
TilePixelValue::One => '▒', // Light gray
185
185
+
TilePixelValue::Two => '▓', // Dark gray
186
186
+
TilePixelValue::Three => '█', // Dark
187
187
+
};
188
188
+
print!("{}", char);
189
189
+
}
190
190
+
// println!();
191
191
+
}
192
192
+
} else {
193
193
+
println!("Tile {} not found", tile_index);
194
194
+
}
195
195
+
}
196
196
+
}