diff --git a/core/src/ds_slot/rom.rs b/core/src/ds_slot/rom.rs index e92e1bb..1232a11 100644 --- a/core/src/ds_slot/rom.rs +++ b/core/src/ds_slot/rom.rs @@ -2,7 +2,7 @@ mod empty; mod key1; pub use empty::Empty; pub mod header; -pub mod icon; +pub mod icon_title; pub mod normal; use super::RomOutputLen; diff --git a/core/src/ds_slot/rom/icon.rs b/core/src/ds_slot/rom/icon.rs deleted file mode 100644 index 7fa6fa9..0000000 --- a/core/src/ds_slot/rom/icon.rs +++ /dev/null @@ -1,43 +0,0 @@ -use super::{header::Header, Contents}; -use crate::utils::mem_prelude::*; - -pub fn decode_to_rgba8( - icon_title_offset: usize, - rom_contents: &mut impl Contents, -) -> Option<[u32; 32 * 32]> { - let mut icon_data = Bytes::new([0; 0x220]); - if icon_title_offset + 0x240 > rom_contents.len() { - return None; - } - rom_contents.read_slice(icon_title_offset + 0x20, &mut *icon_data); - - let mut palette = [0; 16]; - for (i, color) in palette.iter_mut().enumerate().skip(1) { - let raw_color = icon_data.read_le::(0x200 | i << 1) as u32; - let rgb6 = - (raw_color << 1 & 0x3E) | (raw_color << 4 & 0x3E00) | (raw_color << 7 & 0x3E_0000); - *color = 0xFF00_0000 | rgb6 << 2 | (rgb6 >> 4 & 0x03_0303); - } - - let mut pixels = [0; 32 * 32]; - for src_tile_line_base in (0..0x200).step_by(4) { - let src_line = icon_data.read_le::(src_tile_line_base); - let tile_y = src_tile_line_base >> 7; - let tile_x = src_tile_line_base >> 5 & 3; - let y_in_tile = src_tile_line_base >> 2 & 7; - let dst_tile_line_base = tile_y << 8 | y_in_tile << 5 | tile_x << 3; - for x_in_tile in 0..8 { - pixels[dst_tile_line_base | x_in_tile] = - palette[(src_line >> (x_in_tile << 2)) as usize & 0xF]; - } - } - Some(pixels) -} - -pub fn read_header_and_decode_to_rgba8(rom_contents: &mut impl Contents) -> Option<[u32; 32 * 32]> { - let mut header_bytes = Bytes::new([0; 0x170]); - rom_contents.read_header(&mut header_bytes); - let header = Header::new(&*header_bytes)?; - let icon_title_offset = header.icon_title_offset() as usize; - decode_to_rgba8(icon_title_offset, rom_contents) -} diff --git a/core/src/ds_slot/rom/icon_title.rs b/core/src/ds_slot/rom/icon_title.rs new file mode 100644 index 0000000..7300c6f --- /dev/null +++ b/core/src/ds_slot/rom/icon_title.rs @@ -0,0 +1,267 @@ +use super::{header::Header, Contents}; +use crate::utils::{mem_prelude::*, zeroed_box}; +use std::array; + +pub type Palette = [u16; 0x10]; +pub type Pixels = [u8; 0x400]; + +fn decode_palette(offset: usize, rom_contents: &mut (impl Contents + ?Sized)) -> Option { + if offset + 0x20 > rom_contents.len() { + return None; + } + let mut data = Bytes::new([0; 0x20]); + rom_contents.read_slice(offset, &mut *data); + Some(array::from_fn(|i| data.read_le(i << 1))) +} + +fn decode_pixels(offset: usize, rom_contents: &mut (impl Contents + ?Sized)) -> Option { + if offset + 0x200 > rom_contents.len() { + return None; + } + let mut data = zeroed_box::>(); + rom_contents.read_slice(offset, &mut **data); + + let mut pixels = [0; 0x400]; + for src_tile_line_base in (0..0x200).step_by(4) { + let src_line = data.read_le::(src_tile_line_base); + let tile_y = src_tile_line_base >> 7; + let tile_x = src_tile_line_base >> 5 & 3; + let y_in_tile = src_tile_line_base >> 2 & 7; + let dst_tile_line_base = tile_y << 8 | y_in_tile << 5 | tile_x << 3; + for x_in_tile in 0..8 { + pixels[dst_tile_line_base | x_in_tile] = (src_line >> (x_in_tile << 2)) as u8 & 0xF; + } + } + Some(pixels) +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)] +pub enum Version { + Base = 1, + Chinese = 2, + Korean = 3, + AnimatedIcon = 0x103, +} + +pub struct VersionCrcData { + pub version: Version, + pub crc16_v1: u16, + pub crc16_v2: u16, + pub crc16_v3: u16, + pub crc16_v103: u16, +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum DecodeError { + OutOfBounds, + UnknownVersion(u16), +} + +impl VersionCrcData { + pub fn decode_at_offset( + icon_title_offset: usize, + rom_contents: &mut (impl Contents + ?Sized), + ) -> Result { + if icon_title_offset + 20 > rom_contents.len() { + return Err(DecodeError::OutOfBounds); + } + let mut version_crc_data = Bytes::new([0; 0x20]); + rom_contents.read_slice(icon_title_offset, &mut *version_crc_data); + + let version = match version_crc_data.read_le::(0) { + 1 => Version::Base, + 2 => Version::Chinese, + 3 => Version::Korean, + 0x103 => Version::AnimatedIcon, + version => return Err(DecodeError::UnknownVersion(version)), + }; + + let crc16_v1 = version_crc_data.read_le::(2); + let crc16_v2 = version_crc_data.read_le::(4); + let crc16_v3 = version_crc_data.read_le::(6); + let crc16_v103 = version_crc_data.read_le::(8); + + Ok(VersionCrcData { + version, + crc16_v1, + crc16_v2, + crc16_v3, + crc16_v103, + }) + } +} + +pub struct DefaultIcon { + pub palette: Palette, + pub pixels: Pixels, +} + +impl DefaultIcon { + pub fn decode_at_offset( + icon_title_offset: usize, + rom_contents: &mut (impl Contents + ?Sized), + ) -> Option> { + let default_icon = Box::new(DefaultIcon { + palette: decode_palette(icon_title_offset + 0x220, rom_contents)?, + pixels: decode_pixels(icon_title_offset + 0x20, rom_contents)?, + }); + + Some(default_icon) + } +} + +pub type Title = Result>; + +pub struct Titles { + pub japanese: Title, + pub english: Title, + pub french: Title, + pub german: Title, + pub italian: Title, + pub spanish: Title, + pub chinese: Option, + pub korean: Option<Title>, +} + +impl Titles { + pub fn decode_at_offset( + icon_title_offset: usize, + version: Version, + rom_contents: &mut (impl Contents + ?Sized), + ) -> Option<Self> { + macro_rules! title { + ($offset: expr) => {{ + if $offset > rom_contents.len() { + return None; + } + let mut bytes = [0; 0x100]; + rom_contents.read_slice(icon_title_offset + $offset, &mut bytes); + String::from_utf16le(&bytes).map_err(|_| Box::new(bytes)) + }}; + } + + let japanese = title!(0x240); + let english = title!(0x340); + let french = title!(0x440); + let german = title!(0x540); + let italian = title!(0x640); + let spanish = title!(0x740); + let chinese = if version >= Version::Chinese { + Some(title!(0x840)) + } else { + None + }; + let korean = if version >= Version::Korean { + Some(title!(0x940)) + } else { + None + }; + + Some(Titles { + japanese, + english, + french, + german, + italian, + spanish, + chinese, + korean, + }) + } +} + +proc_bitfield::bitfield! { + #[derive(Clone, Copy, PartialEq, Eq)] + pub struct AnimSequenceEntry(pub u16): Debug { + pub frames: u8 @ 0..=7, + pub bitmap: u8 @ 8..=10, + pub palette: u8 @ 11..=13, + pub h_flip: bool @ 14, + pub v_flip: bool @ 15, + } +} + +pub struct AnimatedIcon { + pub palettes: [Box<Palette>; 8], + pub pixels: [Box<Pixels>; 8], + pub anim_sequence: Vec<AnimSequenceEntry>, +} + +impl AnimatedIcon { + pub fn decode_at_offset( + icon_title_offset: usize, + rom_contents: &mut (impl Contents + ?Sized), + ) -> Option<Self> { + let palettes: [Box<Palette>; 8] = array::try_from_fn(|i| { + let offset = icon_title_offset + 0x1240 + i * 0x200; + decode_palette(offset, rom_contents).map(Box::new) + })?; + let pixels: [Box<Pixels>; 8] = array::try_from_fn(|i| { + let offset = icon_title_offset + 0x2240 + i * 0x20; + decode_pixels(offset, rom_contents).map(Box::new) + })?; + + if icon_title_offset + 0x23C0 > rom_contents.len() { + return None; + } + let mut anim_sequence_data = Bytes::new([0; 0x80]); + rom_contents.read_slice(icon_title_offset + 0x2340, &mut *anim_sequence_data); + + let mut anim_sequence = Vec::with_capacity(0x40); + for i in 0..0x40 { + let entry = AnimSequenceEntry(anim_sequence_data.read_le::<u16>(i * 2)); + if entry.frames() == 0 { + break; + } + anim_sequence.push(entry); + } + + Some(AnimatedIcon { + palettes, + pixels, + anim_sequence, + }) + } +} + +pub struct IconTitle { + pub version_crc_data: VersionCrcData, + pub default_icon: Box<DefaultIcon>, + pub titles: Titles, + pub animated_icon: Option<AnimatedIcon>, +} + +impl IconTitle { + pub fn decode_at_offset( + icon_title_offset: usize, + rom_contents: &mut (impl Contents + ?Sized), + ) -> Result<Self, DecodeError> { + let version_crc_data = VersionCrcData::decode_at_offset(icon_title_offset, rom_contents)?; + Ok(IconTitle { + default_icon: DefaultIcon::decode_at_offset(icon_title_offset, rom_contents) + .ok_or(DecodeError::OutOfBounds)?, + titles: Titles::decode_at_offset( + icon_title_offset, + version_crc_data.version, + rom_contents, + ) + .ok_or(DecodeError::OutOfBounds)?, + animated_icon: if version_crc_data.version >= Version::AnimatedIcon { + Some( + AnimatedIcon::decode_at_offset(icon_title_offset, rom_contents) + .ok_or(DecodeError::OutOfBounds)?, + ) + } else { + None + }, + version_crc_data, + }) + } +} + +pub fn read_icon_title_offset(rom_contents: &mut (impl Contents + ?Sized)) -> Option<usize> { + let mut header_bytes = Bytes::new([0; 0x170]); + rom_contents.read_header(&mut header_bytes); + let header = Header::new(&*header_bytes)?; + Some(header.icon_title_offset() as usize) +} diff --git a/core/src/lib.rs b/core/src/lib.rs index 062626e..8dfa114 100644 --- a/core/src/lib.rs +++ b/core/src/lib.rs @@ -7,7 +7,9 @@ maybe_uninit_uninit_array, maybe_uninit_slice, portable_simd, - new_uninit + new_uninit, + str_from_utf16_endian, + array_try_from_fn )] #![warn(clippy::pedantic)] #![allow( diff --git a/frontend/desktop/src/debug_views.rs b/frontend/desktop/src/debug_views.rs index e3cb274..c2a3cac 100644 --- a/frontend/desktop/src/debug_views.rs +++ b/frontend/desktop/src/debug_views.rs @@ -111,14 +111,14 @@ pub trait InstanceableEmuState: EmuState { pub trait BaseView: Sized { const MENU_NAME: &'static str; - - fn new(window: &mut Window) -> Self; - fn destroy(self, _window: &mut Window) {} } pub trait View: BaseView { type EmuState: EmuState; + fn new(window: &mut Window) -> Self; + fn destroy(self, _window: &mut Window) {} + fn emu_state(&self) -> <Self::EmuState as EmuState>::InitData; fn update_from_frame_data( &mut self, @@ -133,7 +133,9 @@ pub trait StaticView: BaseView { type Data: Send; fn fetch_data<E: cpu::Engine>(emu: &mut Emu<E>) -> Self::Data; - fn update_from_data(&mut self, data: Self::Data, window: &mut Window); + + fn new(data: Self::Data, window: &mut Window) -> Self; + fn destroy(self, _window: &mut Window) {} fn draw(&mut self, ui: &imgui::Ui, window: &mut Window); } @@ -166,6 +168,11 @@ pub trait InstanceableView: BaseView { } } +enum StaticViewState<T: StaticView> { + Loading, + Loaded(T), +} + macro_rules! declare_structs { ( [$(( @@ -430,7 +437,7 @@ macro_rules! declare_structs { $i_view_ident: HashMap<ViewKey, (Option<$i_view_ty>, bool)>, )* $( - $sst_view_ident: Option<Option<$sst_view_ty>>, + $sst_view_ident: Option<Option<StaticViewState<$sst_view_ty>>>, )* } @@ -455,8 +462,10 @@ macro_rules! declare_structs { match notif { $( Notification::$sst_reply_message_ident(data) => { - if let Some(Some(view)) = &mut self.$sst_view_ident { - view.update_from_data(data, window); + if let Some(Some(view @ StaticViewState::Loading)) = + &mut self.$sst_view_ident + { + *view = StaticViewState::Loaded(<$sst_view_ty>::new(data, window)); } } )* @@ -499,7 +508,7 @@ macro_rules! declare_structs { )* $( if let Some(view @ None) = &mut self.$sst_view_ident { - *view = Some(<$sst_view_ty>::new(window)); + *view = Some(StaticViewState::Loading); self.messages.push(Message::$sst_fetch_message_ident); } )* @@ -524,7 +533,7 @@ macro_rules! declare_structs { )* $( if let Some(view) = &mut self.$sst_view_ident { - if let Some(view) = view.take() { + if let Some(StaticViewState::Loaded(view)) = view.take() { view.destroy(window); }; } @@ -538,13 +547,13 @@ macro_rules! declare_structs { .build() { if let Some(view) = self.$sst_view_ident.take() { - if let Some(view) = view { + if let Some(StaticViewState::Loaded(view)) = view { view.destroy(window); } } else { self.$sst_view_ident = Some(if emu_running { self.messages.push(Message::$sst_fetch_message_ident); - Some(<$sst_view_ty>::new(window)) + Some(StaticViewState::Loading) } else { None }); @@ -608,6 +617,10 @@ macro_rules! declare_structs { ui.text("Start the emulator to see information."); } + fn draw_loading_view(ui: &imgui::Ui) { + ui.text("Loading..."); + } + pub fn draw<'a>( &'a mut self, ui: &imgui::Ui, @@ -690,15 +703,27 @@ macro_rules! declare_structs { if let Some(view) = &mut self.$sst_view_ident { let mut opened = true; if let Some(view) = view { - let ui_window = view.window(ui).opened(&mut opened); - ui_window.build(|| { - view.draw(ui, window); - }); - if !opened { - let Some(Some(view)) = self.$sst_view_ident.take() else { - unreachable!(); - }; - view.destroy(window); + if let StaticViewState::Loaded(view) = view { + let ui_window = view.window(ui).opened(&mut opened); + ui_window.build(|| { + view.draw(ui, window); + }); + if !opened { + let Some(Some(StaticViewState::Loaded(view))) = + self.$sst_view_ident.take() else { + unreachable!(); + }; + view.destroy(window); + } + } else { + <$sst_view_ty>::window_stopped(ui) + .opened(&mut opened) + .build(|| { + Self::draw_loading_view(ui); + }); + if !opened { + self.$sst_view_ident.take(); + } } } else { <$sst_view_ty>::window_stopped(ui) diff --git a/frontend/desktop/src/debug_views/audio_channels.rs b/frontend/desktop/src/debug_views/audio_channels.rs index 435deaf..937e7c1 100644 --- a/frontend/desktop/src/debug_views/audio_channels.rs +++ b/frontend/desktop/src/debug_views/audio_channels.rs @@ -1,6 +1,6 @@ use super::{ common::regs::{bitfield, BitfieldCommand}, - FrameDataSlot, BaseView, InstanceableEmuState, InstanceableView, Messages, RefreshType, View, + BaseView, FrameDataSlot, InstanceableEmuState, InstanceableView, Messages, RefreshType, View, }; use crate::ui::window::Window; use dust_core::{ @@ -166,6 +166,10 @@ pub struct AudioChannels { impl BaseView for AudioChannels { const MENU_NAME: &'static str = "Audio channels"; +} + +impl View for AudioChannels { + type EmuState = EmuState; fn new(_window: &mut Window) -> Self { const DEFAULT_SAMPLES: u32 = 512 * 8; @@ -181,10 +185,6 @@ impl BaseView for AudioChannels { fft_output_f32_buf: Vec::new(), } } -} - -impl View for AudioChannels { - type EmuState = EmuState; fn emu_state(&self) -> <Self::EmuState as super::EmuState>::InitData { self.cur_channel diff --git a/frontend/desktop/src/debug_views/bg_maps_2d.rs b/frontend/desktop/src/debug_views/bg_maps_2d.rs index 5ae94bb..9bc0d4d 100644 --- a/frontend/desktop/src/debug_views/bg_maps_2d.rs +++ b/frontend/desktop/src/debug_views/bg_maps_2d.rs @@ -1,5 +1,6 @@ use super::{ - common::rgb5_to_rgba8, BaseView, FrameDataSlot, InstanceableEmuState, InstanceableView, Messages, View, + common::rgb5_to_rgba8, BaseView, FrameDataSlot, InstanceableEmuState, InstanceableView, + Messages, View, }; use crate::ui::{ utils::{add2, combo_value, scale_to_fit, sub2s}, @@ -390,6 +391,10 @@ pub struct BgMaps2d { impl BaseView for BgMaps2d { const MENU_NAME: &'static str = "2D BG maps"; +} + +impl View for BgMaps2d { + type EmuState = EmuState; fn new(window: &mut Window) -> Self { let tex_id = window.imgui_gfx.create_and_add_owned_texture( @@ -427,10 +432,6 @@ impl BaseView for BgMaps2d { fn destroy(self, window: &mut Window) { window.imgui_gfx.remove_texture(self.tex_id); } -} - -impl View for BgMaps2d { - type EmuState = EmuState; fn emu_state(&self) -> <Self::EmuState as super::EmuState>::InitData { self.cur_selection diff --git a/frontend/desktop/src/debug_views/cpu_disasm.rs b/frontend/desktop/src/debug_views/cpu_disasm.rs index 36ba603..5dae282 100644 --- a/frontend/desktop/src/debug_views/cpu_disasm.rs +++ b/frontend/desktop/src/debug_views/cpu_disasm.rs @@ -3,7 +3,7 @@ use super::{ disasm::{Addr, DisassemblyView}, RangeInclusive, }, - FrameDataSlot, BaseView, InstanceableEmuState, InstanceableView, Messages, View, + BaseView, FrameDataSlot, InstanceableEmuState, InstanceableView, Messages, View, }; use crate::ui::window::Window; use dust_core::{ @@ -103,6 +103,10 @@ impl<const ARM9: bool> BaseView for CpuDisasm<ARM9> { } else { "ARM7 disassembly" }; +} + +impl<const ARM9: bool> View for CpuDisasm<ARM9> { + type EmuState = EmuState<ARM9>; fn new(_window: &mut Window) -> Self { CpuDisasm { @@ -122,10 +126,6 @@ impl<const ARM9: bool> BaseView for CpuDisasm<ARM9> { }, } } -} - -impl<const ARM9: bool> View for CpuDisasm<ARM9> { - type EmuState = EmuState<ARM9>; fn emu_state(&self) -> <Self::EmuState as super::EmuState>::InitData { self.last_visible_addrs diff --git a/frontend/desktop/src/debug_views/cpu_memory.rs b/frontend/desktop/src/debug_views/cpu_memory.rs index 1d751d8..11afc97 100644 --- a/frontend/desktop/src/debug_views/cpu_memory.rs +++ b/frontend/desktop/src/debug_views/cpu_memory.rs @@ -92,6 +92,10 @@ impl<const ARM9: bool> InstanceableView for CpuMemory<ARM9> { impl<const ARM9: bool> BaseView for CpuMemory<ARM9> { const MENU_NAME: &'static str = if ARM9 { "ARM9 memory" } else { "ARM7 memory" }; +} + +impl<const ARM9: bool> View for CpuMemory<ARM9> { + type EmuState = EmuState<ARM9>; fn new(_window: &mut Window) -> Self { let mut editor = MemoryEditor::new(); @@ -106,10 +110,6 @@ impl<const ARM9: bool> BaseView for CpuMemory<ARM9> { }, } } -} - -impl<const ARM9: bool> View for CpuMemory<ARM9> { - type EmuState = EmuState<ARM9>; fn emu_state(&self) -> <Self::EmuState as super::EmuState>::InitData { self.last_visible_addrs diff --git a/frontend/desktop/src/debug_views/cpu_state.rs b/frontend/desktop/src/debug_views/cpu_state.rs index 41ee089..a53309e 100644 --- a/frontend/desktop/src/debug_views/cpu_state.rs +++ b/frontend/desktop/src/debug_views/cpu_state.rs @@ -115,6 +115,10 @@ impl<const ARM9: bool> SingletonView for CpuState<ARM9> { impl<const ARM9: bool> BaseView for CpuState<ARM9> { const MENU_NAME: &'static str = if ARM9 { "ARM9 state" } else { "ARM7 state" }; +} + +impl<const ARM9: bool> View for CpuState<ARM9> { + type EmuState = EmuState<ARM9>; fn new(_window: &mut Window) -> Self { CpuState { @@ -122,10 +126,6 @@ impl<const ARM9: bool> BaseView for CpuState<ARM9> { reg_bank: None, } } -} - -impl<const ARM9: bool> View for CpuState<ARM9> { - type EmuState = EmuState<ARM9>; fn emu_state(&self) -> <Self::EmuState as super::EmuState>::InitData {} diff --git a/frontend/desktop/src/debug_views/palettes_2d.rs b/frontend/desktop/src/debug_views/palettes_2d.rs index 29c1b7b..af3be54 100644 --- a/frontend/desktop/src/debug_views/palettes_2d.rs +++ b/frontend/desktop/src/debug_views/palettes_2d.rs @@ -173,6 +173,10 @@ pub struct Palettes2d { impl BaseView for Palettes2d { const MENU_NAME: &'static str = "2D engine palettes"; +} + +impl View for Palettes2d { + type EmuState = EmuState; fn new(_window: &mut Window) -> Self { Palettes2d { @@ -185,10 +189,6 @@ impl BaseView for Palettes2d { cur_color: [0.0; 3], } } -} - -impl View for Palettes2d { - type EmuState = EmuState; fn emu_state(&self) -> <Self::EmuState as super::EmuState>::InitData { self.cur_selection diff --git a/frontend/desktop/src/ui/title_menu_bar.rs b/frontend/desktop/src/ui/title_menu_bar.rs index 4c627ba..7c1ae06 100644 --- a/frontend/desktop/src/ui/title_menu_bar.rs +++ b/frontend/desktop/src/ui/title_menu_bar.rs @@ -4,9 +4,10 @@ use crate::config::TitleBarMode; use crate::{ config::{Config, GameIconMode}, emu::ds_slot_rom::DsSlotRom, + utils::icon_data_to_rgba8, }; #[cfg(any(target_os = "macos", target_os = "linux", target_os = "windows"))] -use dust_core::ds_slot::rom::icon; +use dust_core::ds_slot::rom::icon_title; use imgui::Ui; #[cfg(target_os = "macos")] use imgui::{Image, TextureId}; @@ -112,17 +113,12 @@ impl TitleMenuBarState { #[cfg(any(target_os = "macos", target_os = "linux", target_os = "windows"))] { - self.game_icon_rgba8_pixels = ds_slot_rom - .and_then(icon::read_header_and_decode_to_rgba8) - .map(|pixels| { - let mut rgba = unsafe { Box::<[u8; 32 * 32 * 4]>::new_zeroed().assume_init() }; - for (i, pixel) in pixels.iter().enumerate() { - for (j, component) in pixel.to_le_bytes().into_iter().enumerate() { - rgba[i << 2 | j] = component; - } - } - rgba - }); + self.game_icon_rgba8_pixels = ds_slot_rom.and_then(|rom_contents| { + let icon_title_offset = icon_title::read_icon_title_offset(rom_contents)?; + let icon = + icon_title::DefaultIcon::decode_at_offset(icon_title_offset, rom_contents)?; + Some(icon_data_to_rgba8(&icon.palette, &icon.pixels)) + }); #[cfg(target_os = "macos")] { self.game_file_path = _ds_slot_rom_path.map(Path::to_path_buf); diff --git a/frontend/desktop/src/utils.rs b/frontend/desktop/src/utils.rs index e8fe52c..f297402 100644 --- a/frontend/desktop/src/utils.rs +++ b/frontend/desktop/src/utils.rs @@ -1,3 +1,5 @@ +use dust_core::{ds_slot::rom::icon_title, utils::zeroed_box}; +use std::array; use std::{ borrow::Cow, fmt, @@ -229,3 +231,29 @@ pub mod double_option { } } } + +pub fn icon_data_to_rgba8( + palette: &icon_title::Palette, + pixels: &icon_title::Pixels, +) -> Box<[u8; 0x1000]> { + let palette: [u32; 8] = array::from_fn(|i| { + if i == 0 { + return 0; + } + let raw = palette[i] as u32; + let rgb6 = (raw << 1 & 0x3E) | (raw << 4 & 0x3E00) | (raw << 7 & 0x3E_0000); + 0xFF00_0000 | rgb6 << 2 | (rgb6 >> 4 & 0x03_0303) + }); + + let mut rgba = zeroed_box::<[u8; 32 * 32 * 4]>(); + for (i, pixel) in pixels.iter().enumerate() { + for (j, component) in palette[*pixel as usize] + .to_le_bytes() + .into_iter() + .enumerate() + { + rgba[i << 2 | j] = component; + } + } + rgba +}