├── meboy0.png ├── meboy1.png ├── meboy2.png ├── lang ├── index.txt ├── 4.txt ├── 11.txt ├── 15.txt ├── 12.txt ├── 0.txt ├── 13.txt ├── 8.txt ├── 3.txt ├── 20.txt ├── 1.txt ├── 19.txt ├── 14.txt ├── 16.txt ├── 17.txt ├── 2.txt ├── 21.txt ├── 5.txt ├── 10.txt ├── 18.txt ├── 9.txt ├── 6.txt └── 7.txt ├── changelog.txt ├── README.md ├── GraphicsChip.java ├── GPL.txt ├── SimpleGraphicsChip.java ├── AdvancedGraphicsChip.java ├── GBCanvas.java ├── se └── arktos │ └── meboy │ ├── Bluetooth.java │ └── MeBoyBuilder.java ├── Bluetooth.java └── MeBoy.java /meboy0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chijure/meboy/HEAD/meboy0.png -------------------------------------------------------------------------------- /meboy1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chijure/meboy/HEAD/meboy1.png -------------------------------------------------------------------------------- /meboy2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chijure/meboy/HEAD/meboy2.png -------------------------------------------------------------------------------- /lang/index.txt: -------------------------------------------------------------------------------- 1 | l 2 | Česky 3 | u 4 | Dansk 5 | g 6 | Deutsch 7 | c 8 | Ελληνικά 9 | a 10 | English 11 | b 12 | Español 13 | j 14 | Français 15 | n 16 | Hrvatski 17 | i 18 | Indonesian 19 | k 20 | Italiano 21 | r 22 | Latviešu 23 | q 24 | Lietuvių 25 | s 26 | Magyar 27 | h 28 | Nederlands 29 | d 30 | Português (Br) 31 | f 32 | Polski 33 | m 34 | Русский 35 | t 36 | Schwizerdütsch 37 | o 38 | Srpski 39 | v 40 | Türkçe 41 | e 42 | 中文 (Chinese) 43 | p 44 | Українська 45 | -------------------------------------------------------------------------------- /lang/4.txt: -------------------------------------------------------------------------------- 1 | 开始游戏 2 | 继续游戏 3 | 设置 4 | 显示信息 5 | 蓝牙 6 | 关于 7 | 退出 8 | 选择游戏 9 | 删除 10 | 复制 11 | 返回 12 | 未安装游戏无法继续 13 | 跳帧 14 | 屏幕旋转 (x90°) 15 | 画面 16 | 拉伸至全屏 17 | 保持画面比例 18 | 画面增强 19 | 增强扫描模式 20 | 音效 21 | 开启声音 22 | 特征实验 23 | 语言 24 | 其它 25 | 黑白显示效果 26 | 菜单显示信息 27 | 最大游戏容量 kB 28 | 保存 29 | 信息 30 | 所剩空间: 31 | 暂停 32 | 继续 33 | 快速存档 34 | 显示/隐藏帧率 35 | 全屏 36 | 按键设置 37 | 退出 38 | 请选择控制键 39 | 右 40 | 左 41 | 上 42 | 下 43 | A 44 | B 45 | 选择 46 | 开始 47 | MeBoy 错误 48 | 发生错误 49 | 不兼容的存档 50 | 读取游戏错误 51 | 读取已存游戏失败 52 | 无法读取游戏列表 53 | 发送游戏 54 | Cart-RAM 55 | 发送已存游戏 56 | 已存游戏 57 | 接收已存游戏 58 | 取消 59 | 蓝牙传输成功 60 | 蓝牙传输失败 61 | 没发现蓝牙设备 62 | 传输中... 63 | 无法连接MEBOY 64 | 已存游戏无法接收 不匹配 65 | 已接收游戏无法存储 66 | 已安装游戏无法读盘 67 | 正在搜寻其它设备...请确定对方开启已接收模式 68 | 正在连接已选设备... 69 | 正在等待传输... 70 | 不兼容的版本 71 | 选择设备 72 | 正在接收... 73 | 74 | -------------------------------------------------------------------------------- /lang/11.txt: -------------------------------------------------------------------------------- 1 | Spustit hru 2 | Načíst pozici 3 | Nastavení 4 | Zobrazit log 5 | Bluetooth 6 | O MeBoy 7 | Konec 8 | Vybrat Hru 9 | Smazat 10 | Duplidovat 11 | Zpět 12 | Nelze načíst, protože hra není nainstalovaná 13 | Frame skip 14 | Počet otočení o 90° 15 | Grafika 16 | Roztáhnout na obrazovku 17 | Zachovat proporce 18 | Pokročilá grafika 19 | Pokročilá změna rozlišení 20 | Zvuk 21 | Zapnout zvuk 22 | Experimentálnosti 23 | Jazyk 24 | Další 25 | Vypnout barvy 26 | Zobrazit položku Log v menu 27 | Maximální načtená velikost v kB 28 | Uložit 29 | Log 30 | Volná paměť: 31 | Pauza 32 | Obnovit 33 | Uložit 34 | Zobrazit/schovat framerate 35 | Celá obrazovka 36 | Nastavit tlačítka 37 | Ukončit 38 | Stiskněte tlačítko pro: 39 | vpravo 40 | vlevo 41 | nahoru 42 | dolů 43 | A 44 | B 45 | Select 46 | Start 47 | Chyba v MeBoy 48 | Chyba 49 | Nekompatibilní uložená hra. 50 | Načítání hry selhalo 51 | Změna uložené pozice se nezdařila 52 | Nelze načíst seznam her. 53 | Poslat hru s vlastním ukládáním. 54 | Vlastní uložení(C-RAM) 55 | Poslat uloženou pozici 56 | Uložené pozice 57 | Přijmout pozici 58 | Zrušit 59 | Bluetooth přenos proběhl v pořádku. 60 | Bluetooth přenos se nezdařil. 61 | Žádné zařízení nenalezeno. 62 | Odesílání... 63 | MeBoy není na druhém zařízení spuštěn. 64 | Pozici nelze přijmout, protože neodpovídá žádné z instalovaných her. 65 | Přijatou pozici nelze uložit. 66 | Žádná z instalovaných her nemá vlastní ukládání. 67 | Hledání zařízení poblíž... Zkontrolujte, zda je zařízení nastaveno na příjem pozice. 68 | Připojování k Meboy na zvoleném zařízení... 69 | Čekání na pozici k přenosu... 70 | Nekompatibilní verze. 71 | Výběr zařízení 72 | Příjem... 73 | -------------------------------------------------------------------------------- /lang/15.txt: -------------------------------------------------------------------------------- 1 | Почати Гру 2 | Продовжити Гру 3 | Налаштування 4 | Показати Протокол 5 | Bluetooth 6 | Про Програму 7 | Вихід 8 | Вибрати Гру 9 | Видалити 10 | Дублювати 11 | Назад 12 | Не вдалось продовжити гру 13 | Пропуск Кадрів 14 | Поворот Екрану (x90°) 15 | Графіка 16 | Масштабувати 17 | Зберігати Пропорції 18 | Покращена Графіка 19 | Ступінь Покращення 20 | Звук 21 | Активувати Звук 22 | Експерементально 23 | Мова 24 | Інше 25 | Відключити Емуляцію GBC 26 | Відображати Протокол в Меню 27 | Максимально Завантажуваний розмір, kB 28 | Зберегти 29 | Протокол 30 | Вільно пам'яті: 31 | Пауза 32 | Продовжити 33 | Зберегти 34 | Показати FPS 35 | Повний Екран 36 | Призначити Кнопки 37 | Вихід 38 | Натисніть Кнопку 39 | Вправо 40 | Вліво 41 | Вгору 42 | Вниз 43 | A 44 | B 45 | Select 46 | Start 47 | Помилка Емулятора 48 | Виникла Помилка 49 | Несумісне Збереження. 50 | Помилка Завантаження Гри 51 | Помилка Відновлення Гри 52 | Помилка Завантаження списку Ігор. 53 | Відіслати "cart-ram" 54 | "CART-RAM" 55 | Відіслати Збереження Гри 56 | Збереження Ігор 57 | Прийняти Збереження Гри 58 | Відміна 59 | Bluetooth Передача успішно завершена. 60 | Bluetooth Передача не вдалась. 61 | Немає Bluetooth з'єднань. 62 | Передача... 63 | Неможливо з'єднатися с Пристроєм. 64 | Збереження не може бути використане, оскільки у Вас не встановлена Гра. 65 | Отримані Збереження не можуть бути збережені. 66 | Жодна із встановлених ігор не має Збережень "cart-ram". 67 | Пошук Пристрою. Переконайтесь, що в Іншому Пристрої включений режим "Прийняти Збереження". 68 | Спроба з'єднання з Пристроєм... 69 | Почекайте, відбувається з'єднання... 70 | Несумісні версії. 71 | Вибрати Пристрій 72 | Отримання... 73 | -------------------------------------------------------------------------------- /lang/12.txt: -------------------------------------------------------------------------------- 1 | Начать Игру 2 | Продолжить Игру 3 | Настройки 4 | Показать Протокол 5 | Bluetooth 6 | О Программе 7 | Выход 8 | Выбрать Игру 9 | Удалить 10 | Дублировать 11 | Назад 12 | Не удалось продолжить игру 13 | Пропуск Кадров 14 | Поворот Экрана (x90°) 15 | Графика 16 | Масштабировать 17 | Сохранять Пропорции 18 | Улучшенная Графика 19 | Степень Улучшения 20 | Звук 21 | Активировать Звук 22 | Эксперементально 23 | Язык 24 | Прочее 25 | Отключить Эмуляцию GBC 26 | Отображать Протокол в Меню 27 | Максимально Загружаемый размер kB 28 | Сохранить 29 | Протокол 30 | Свободно Памяти: 31 | Пауза 32 | Продолжить 33 | Сохранить 34 | Показать FPS 35 | Полный Экран 36 | Назначить Кнопки 37 | Выход 38 | Нажмите Кнопку 39 | Вправо 40 | Влево 41 | Вверх 42 | Вниз 43 | A 44 | B 45 | Select 46 | Start 47 | Ошибка Эмулятора 48 | Произошла Ошибка 49 | Несовместимое Сохранение. 50 | Ошибка Загрузки Игры 51 | Ошибка Возобновления Игры 52 | Ошибка Загрузки Списка Игр. 53 | Отослать "cart-RAM" 54 | "Cart-RAM" 55 | Отослать Сохранение Игры 56 | Сохранения Игр 57 | Принять Сохранение Игры 58 | Отмена 59 | Bluetooth Передача Успешно Завершена. 60 | Bluetooth Передача не Удалась. 61 | Нет Bluetooth Соединений. 62 | Передача... 63 | Невозможно Соединиться с Устройством. 64 | Сохранение не Может Быть Использованно т.к. у Вас не Установлена Игра. 65 | Полученные Сохранения не Могут быть сохранены. 66 | Ни Одна из установленных Игр не Имеет Сохранений "cart-RAM". 67 | Поиск Устройства. Убедитесь, что в Другом Устройстве Включен Режим "Принять Сохранение". 68 | Попытка Соединения с Устройством... 69 | Подождите, Происходит Соединение... 70 | Несовместимые Версии. 71 | Выбрать Устройство 72 | Получение... 73 | -------------------------------------------------------------------------------- /lang/0.txt: -------------------------------------------------------------------------------- 1 | Start Game 2 | Resume Game 3 | Settings 4 | Show Log 5 | Bluetooth 6 | About MeBoy 7 | Exit 8 | Select Game 9 | Delete 10 | Duplicate 11 | Back 12 | Could not resume since the game is not installed 13 | Frameskip 14 | Screen rotation (x90°) 15 | Graphics 16 | Scale to fit 17 | Keep proportions 18 | Advanced graphics 19 | Advanced scaling mode 20 | Sound 21 | Enable sound 22 | Experimental features 23 | Language 24 | Other 25 | Disable GBC emulation 26 | Show "Log" in menu 27 | Max loaded cart size kB 28 | Save 29 | Log 30 | Free memory: 31 | Pause 32 | Resume 33 | Suspend 34 | Show/hide framerate 35 | Full screen 36 | Set buttons 37 | Exit 38 | Press the button for 39 | right 40 | left 41 | up 42 | down 43 | A 44 | B 45 | Select 46 | Start 47 | MeBoy Error 48 | An error has occurred 49 | Incompatible saved game. 50 | Loading the game failed 51 | Failed to update a suspended game 52 | Could not load the list of carts. 53 | Send cart-RAM 54 | Cart-RAM 55 | Send suspended game 56 | Suspended games 57 | Receive savegame 58 | Cancel 59 | Bluetooth transmission completed successfully. 60 | Bluetooth transmission failed. 61 | No Bluetooth devices found. 62 | Transmitting... 63 | Could not connect to MeBoy on the device. 64 | The savegame could not be received because it does not match any installed game. 65 | The received savegame could not be saved. 66 | None of the installed games have cart-RAM. 67 | Searching for devices in the vicinity. Make sure the device you want to send to is in "receive savegame" mode. 68 | Trying to connect to MeBoy on the selected device... 69 | Waiting for a savegame to be transmitted... 70 | Incompatible version. 71 | Select device 72 | Receiving... 73 | 74 | -------------------------------------------------------------------------------- /lang/13.txt: -------------------------------------------------------------------------------- 1 | Pokreni igru 2 | Učitaj igru 3 | Postavke 4 | Pokaži izvješće 5 | Bluetooth 6 | O programu 7 | Izlaz 8 | Odaberite igru 9 | Izbriši 10 | Kopiraj 11 | Natrag 12 | Ne mogu učitati jer igra nije instalirana 13 | Preskakanje frameova 14 | Zakretanje zaslona (x90°) 15 | Grafika 16 | Prilagodi zaslonu 17 | Zadrži omjer slike 18 | Napredna grafika 19 | Napredni način skaliranja 20 | Zvuk 21 | Omogući zvuk 22 | Eksperimentalne funkcije 23 | Jezik 24 | Ostalo 25 | Onemogući GameBoy boje 26 | Pokaži "Izvješće" u izborniku 27 | Maks.vel.učitane igre kB 28 | Spremi 29 | Izvješće 30 | Slobodna memorija 31 | Pauza 32 | Nastavi 33 | Spremi 34 | Pokaži/ukloni fps 35 | Preko cijelog zaslona 36 | Postavi kontrole 37 | Izađi iz 38 | Pritisnite tipku za 39 | desno 40 | lijevo 41 | gore 42 | dolje 43 | A 44 | B 45 | Select 46 | Start 47 | MeBoy greška 48 | Pojavila se greška 49 | Nekompatibilna save datoteka. 50 | Učitavanje igre nije uspjelo 51 | Obnova spremljene igre nije uspjela 52 | Ne mogu učitati popis igara. 53 | Pošalji cart-RAM 54 | Cart-RAM 55 | Pošalji spremljenu igru 56 | Spremljene igre 57 | Primi save datoteku 58 | Odustani 59 | Bluetooth prijenos uspješno obavljen. 60 | Bluetooth prijenos nije uspio. 61 | Nije pronađen nijedan Bluetooth uređaj. 62 | Slanje... 63 | Ne mogu se spojiti na MeBoy na uređaju. 64 | Save datoteka nije primljena jer ne odgovara nijednoj instaliranoj igri. 65 | Ne mogu spremiti primljenu save datoteku. 66 | Nijedna od instaliranih igara nema cart-RAM. 67 | Tražim uređaje... Provjerite je li na uređaju na koji šaljete odabrano "Primi save datoteku" 68 | Spajanje na MeBoy na odabranom uređaju... 69 | Slanje save datoteke... 70 | Nekompatibilna verzija. 71 | Odaberite uređaj 72 | Primanje... 73 | -------------------------------------------------------------------------------- /lang/8.txt: -------------------------------------------------------------------------------- 1 | Mulai Baru 2 | Lanjutkan 3 | Pengaturan 4 | Lihat catatan 5 | Bluetooth 6 | Tentang Meboy 7 | Keluar 8 | Pilih permainan 9 | Hapus 10 | Duplikat 11 | Kembali 12 | Tidak bisa melanjutkan karena permainannya tidak terinstall 13 | Frameskip 14 | Putar Layar (x90) 15 | Grafis 16 | Selebar layar 17 | Sesuai Skala 18 | Grafis tingkat lanjut 19 | Mode pengukuran tingkat lanjut 20 | Suara 21 | Aktifkan suara 22 | Fitur eksperimental 23 | Bahasa 24 | Lainnya 25 | Nonaktifkan emulasi GBC 26 | Tampilkan "catatan" di menu 27 | Ukuran kb maksimal rom yang dibuka 28 | Simpan 29 | Catatan 30 | Memori bebas: 31 | Istirahat 32 | Lanjutkan 33 | Simpan 34 | Tampilkan/Sembunyikan framerate 35 | Layar penuh 36 | Atur navigasi 37 | Keluar 38 | Tekan tombol untuk 39 | Kanan 40 | Kiri 41 | Atas 42 | Bawah 43 | A 44 | B 45 | Select 46 | Start 47 | Meboy rusak 48 | Kerusakan terjadi 49 | Permainan tidak kompatibel 50 | Permainan gagal dibuka 51 | Gagal melanjutkan permainan 52 | Daftar permainan tidak bisa dibuka 53 | Kirim rom 54 | Rom 55 | Kirim data simpanan 56 | Permainan tersimpan 57 | Menerima data simpanan 58 | Batal 59 | Pengiriman bluetooth sukses 60 | Pengiriman bluetooth gagal 61 | Perangkat bluetooth tidak ditemukan 62 | Sedang Dikirim... 63 | Tidak bisa berhubungan dengan meboy pada perangkat ini 64 | Data simpanan ditolak karena tidak ada permainan yang sesuai 65 | Data simpanan yang doterima tidak bisa disimpan 66 | Semua permainan terinstall tidak memiliki rom 67 | Sedang mencari perangkat terdekat. Pastikan perangkat tujuan berada pada mode "menerima data simpanan." 68 | Mencoba berhubungan dengan Meboy pada perangkat ini 69 | Menunggu data simpanan untuk dikirim 70 | Versinya tidak kompatibel 71 | Pilih perangkat 72 | Menerima... 73 | -------------------------------------------------------------------------------- /lang/3.txt: -------------------------------------------------------------------------------- 1 | Iniciar Jogo 2 | Continuar Jogo 3 | Opções 4 | Mostrar Log 5 | Bluetooth 6 | Sobre MeBoy 7 | Sair 8 | Selecionar Jogo 9 | Deletar 10 | Duplicar 11 | Voltar 12 | Não pôde carregar, pois o jogo não está instalado 13 | Pulo de Frame 14 | Numero de giros de 90° 15 | Gráficos 16 | Ajustar à tela 17 | Manter proporções 18 | Gráficos Avançados 19 | Modo de escala avançado 20 | Som 21 | Ativar Som 22 | Recursos experimentais 23 | Idioma 24 | Outro 25 | Desativar GBC 26 | Mostrar "Log" no menu 27 | Tamanho máximo carregado da ROM kb 28 | Salvar 29 | Log 30 | memória livre: 31 | Pausar 32 | Continuar 33 | Salvar 34 | Mostrar/Ocultar FPS 35 | Tela cheia 36 | Config. Botões 37 | Sair de 38 | press. o botão para 39 | direita 40 | esquerda 41 | cima 42 | baixo 43 | a 44 | b 45 | select 46 | start 47 | Erro no MeBoy 48 | Um erro ocorreu 49 | Jogo salvo incompatível 50 | Carregamento de jogo falhou 51 | Falha ao atualizar jogo salvo 52 | Não podê ser carregada a lista de jogos. 53 | Enviar ROM-RAM 54 | ROM-RAM 55 | Enviar jogo salvo 56 | Jogos salvos 57 | Receber jogo salvo 58 | Cancelar 59 | Transmissão Bluetooth completa com sucesso. 60 | Transmissão Bluetooth falhou. 61 | Nenhum dispositivo Bluetooth encontrado. 62 | Transmitindo... 63 | Não pôde conectar o MeBoy no dispositivo. 64 | O jogo salvo não pôde ser recebido pois não é compatível com os jogos instalados. 65 | O jogo salvo não pôde ser salvo. 66 | Nenhum dos jogos instalados contém 67 | Procurando por dispositivos próximos. Tenha certeza que o dispositivo o qual você deseja enviar esteja em modo de recebimento. 68 | Tentando conectar o MeBoy no dispositivo selecionado... 69 | Esperando pela transferência... 70 | Versão incompatível. 71 | Selecionar dispositivo 72 | Recebendo... 73 | 74 | -------------------------------------------------------------------------------- /lang/20.txt: -------------------------------------------------------------------------------- 1 | Start Spil 2 | Fortsæt Spil 3 | Indstillinger 4 | Vis Log 5 | Bluetooth 6 | Om MeBoy 7 | Afslut 8 | Vælg Spil 9 | Slet 10 | Kopier 11 | Tilbage 12 | Kan ikke fortsætte fordi spillet ikke er installeret 13 | Spring billeder over 14 | Skærm rotation (x90°) 15 | Grafik 16 | Scalér til at passe 17 | Bevar proportioner 18 | Avanceret grafik 19 | Avanceret skalering 20 | Lyd 21 | Aktiver lyd 22 | Eksperimentelle funktioner 23 | Sprog 24 | Andet 25 | Deaktiver GBC emulation 26 | Vis "Log" i menuen 27 | Største cart størrelse i kB 28 | Gem 29 | Log 30 | Tilgængelig hukommelse: 31 | Pause 32 | Forsæt 33 | Gem tilstand 34 | Vis/skjul opdateringshastighed 35 | Fuld skærm 36 | Indstil knapper 37 | Afslut 38 | Tryk på knappen for 39 | højre 40 | venstre 41 | op 42 | ned 43 | A 44 | B 45 | Select 46 | Start 47 | MeBoy Fejl 48 | En fejl er opstået 49 | Inkompatibelt gemt spil. 50 | Indlæsning af spillet fejlede 51 | Kunne ikke opdatere spillets gemte tilstand 52 | Kunne ikke indlæse cart listen. 53 | Send cart-RAM 54 | Cart-RAM 55 | Send gemte spil tilstande 56 | Gemte spil tilstande 57 | Modtag gemt spil 58 | Annuller 59 | Bluetooth overførslen blev udført. 60 | Bluetooth overførslen fejlede. 61 | Ingen Bluetooth enheder fundet. 62 | Overfører... 63 | Kunne ikke forbinde til MeBoy på enheden. 64 | Det gemte spil kunne ikke modtages, fordi det ikke passer til nogen af de installerede spil. 65 | Det modtagne gemte spil kunne ikke blive gemt. 66 | Ingen af de gemte spil har cart-RAM. 67 | Søger efter enheder in nærheden. Kontroller at enhden du vil sende til er i "modtag gemt spil" tilstand. 68 | Forsøger at forbinde til MeBoy på den valgte enhed... 69 | Venter på at det gemte spil bliver overført... 70 | Inkompatibel version. 71 | Vælg enhed 72 | Modtager... -------------------------------------------------------------------------------- /lang/1.txt: -------------------------------------------------------------------------------- 1 | Nuevo Juego 2 | Resumir Juego 3 | Configuración 4 | Registro 5 | Bluetooth 6 | Acerca de MeBoy 7 | Salir 8 | Elegir ROM 9 | Borrar 10 | Duplicar 11 | Atras 12 | No se puede resumir porque el juego no esta instalado 13 | Salto de frames 14 | Numero de giros de 90° 15 | Gráficos 16 | Ajustar a la pantalla 17 | Mantener proporciones 18 | Graficos avanzados 19 | Modo avanzado de escalado 20 | Sonido 21 | Activar sonido 22 | Opciones experimentales 23 | Idioma 24 | Otros 25 | Desactivar GBC 26 | Mostrar "Registro" en el menu 27 | Maximo tamaño a cargar en kb 28 | Guardar 29 | Registro 30 | Memoria libre: 31 | Pausar 32 | Resumir 33 | Suspender 34 | Mostrar/ocultar fps 35 | Maximizar 36 | Definir botones 37 | Salir de 38 | presione el boton para 39 | derecha 40 | izquierda 41 | arriba 42 | abajo 43 | a 44 | b 45 | Select 46 | Start 47 | Error de MeBoy 48 | Ha ocurrido un error 49 | Partida incompatible 50 | Fallo la carga del juego 51 | Fallo el resumen del juego 52 | No se pudo cargar la lista de juegos. 53 | Enviar RAM del juego 54 | RAM del juego 55 | Enviar partida suspendida 56 | Partidas suspendidas 57 | Recibir partidas 58 | Cancelar 59 | Envio Bluetooth completado. 60 | Falló el envio Bluetooth. 61 | No se encontraron dispositivos. 62 | Transmitiendo 63 | No se pudo conectar a MeBoy en el dispositivo. 64 | La partida no se pudo obtener porque que no coincide con ningun juego. 65 | La partida recibida no pudo ser guardada. 66 | Ninguno de los juegos tiene una partida RAM. 67 | Buscando dispositivos próximos. Asegurese que el otro dispositivo esté en modo "Recibir partidas". 68 | Intentando conectar MeBoy en el dispositivo seleccionado... 69 | Esperando la transmision de partidas... 70 | Versión incompatible. 71 | Elegir dispositivo 72 | Recibiendo... 73 | -------------------------------------------------------------------------------- /lang/19.txt: -------------------------------------------------------------------------------- 1 | Spiu startä 2 | Spiu fortsetzä 3 | Ihstelligä 4 | Log ahzeigä 5 | Bluetooth 6 | Übr MeBoy 7 | Beändä 8 | Spiu wählä 9 | Löschä 10 | Dupliziärä 11 | Zrugg 12 | Spiustand cha nid fortgsetzt wärdä, wüu z zuäghörigä Spiu nid installiärt isch. 13 | Frames übrspringä 14 | Biudschirm rotiärä (x90°) 15 | Grafikihstelligä 16 | Uf passendi Grössi skaliärä 17 | Proportionä biphautä 18 | Erwitertä Zeichämodus 19 | Erwitertä Skaliärigsmodus 20 | Sound 21 | Sound aktiviärä 22 | Experimenteui Features 23 | Sprach 24 | Angers 25 | GBC Emulation deaktiviärä 26 | Log im Menü ahzeigä 27 | Maximali gladeni Cartgrössi (kb) 28 | Spicherä 29 | Log 30 | Freier Spicher 31 | Pousä 32 | Fortsetzä 33 | Uswärfä 34 | Framrate ih/usbländä 35 | Voubiud 36 | Tastä bearbeitä 37 | Beändä vo 38 | Drück Tastä für 39 | Rächts 40 | Links 41 | Ufä 42 | Abä 43 | A 44 | B 45 | Select 46 | Start 47 | MeBoy Fähler 48 | Ä Fähler ish ufträtä 49 | Inkompitabler Spicherstand. 50 | Ladä vom Spiustand ish fäugshlagä 51 | Spiustand het nid chönnä gspichert wärdä. 52 | D Listä vo de Carts het nid chönnä gladä wärdä. 53 | Cart-Ram sändä 54 | Cart-Ram 55 | Gspicherti Spiuständ sändä 56 | Gspicherti Spiuständ 57 | Spiustand empfah 58 | Abbrächä 59 | Bluetoothübrtragig erfougrich abgschlossä 60 | Bluetoothübrtragig fäugshlagä 61 | Keni Bluetoothgrät gfungä 62 | Übrtragä... 63 | Vrbindigsfähler mit Meboy uf Ziugrät. 64 | Dr Spiustand het nid chönnä empfangä wärdä, wüu är zu keim installiärtem Spiu passt. 65 | Dr empfangnig Spiustand het nid chönnä gspichert wärdä. 66 | Kenä vo de installiärtä Spilis bsitzt Cart-Ram. 67 | Suächä Grät ir Umgäbig. Steuet sichr ds z Ziugrät im "Spiustand empfah"-Modus befindet. 68 | Vrsuächä MeBoy mitem Ziugrät z vrbindä... 69 | Wartä uf Übrtragig vom Spiustand... 70 | Inkompatibli Version 71 | Grät usählä 72 | Empfah... 73 | 74 | -------------------------------------------------------------------------------- /lang/14.txt: -------------------------------------------------------------------------------- 1 | Pokreni igru 2 | Nastavi igru 3 | Podešavanja 4 | Prikaži evidenciju 5 | Bluetooth 6 | O programu 7 | Izlaz 8 | Odaberite igru 9 | Obriši 10 | Dupliraj 11 | Nazad 12 | Ne može se nastaviti jer igra nije instalirana 13 | Preskakanje sličica 14 | Rotacija ekrana (x90°) 15 | Grafika 16 | Prilagodi ekranu 17 | Zadrži proporcije 18 | Napredna grafika 19 | Napredni mod prilagođavanja 20 | Zvuk 21 | Omogući zvuk 22 | Eksperimentalne funkcije 23 | Jezik 24 | Ostalo 25 | Onemogući GBC emulaciju 26 | Pokaži "Evidencija" u meniju 27 | Maksimalna veličina učitanog kertridža kB 28 | Sačuvaj 29 | Evidencija 30 | Slobodna memorija: 31 | Pauza 32 | Nastavi 33 | Suspenzij 34 | Prikaži/sakrij broj sličica po sekundi 35 | Preko celog ekrana 36 | Podesi kontrole 37 | Izađi iz 38 | Pritisnite dugme za 39 | desno 40 | levo 41 | gore 42 | dole 43 | A 44 | B 45 | Select 46 | Start 47 | MeBoy greška 48 | Pojavila se greška 49 | Nepodržan format sačuvane igre. 50 | Učitavanje ogre nije uspelo 51 | Nastavljanje suspendovane igre nije uspelo 52 | Ne mogu da učitam listu kertridža. 53 | Slanje cart-RAM-a 54 | Cart-RAM 55 | Slanje suspendovane igre 56 | Suspendovane igre 57 | Prijem sačuvane igre 58 | Odustani 59 | Bluetooth prenos uspešan. 60 | Bluetooth prenos neuspešan. 61 | Nijedan Bluetooth uređaj nije nađen. 62 | Slanje... 63 | Ne mogu da se spojim sa MeBoy-em na uređaju. 64 | Datoteka sačuvane igre nije primljena jer se ne poklapa ni sa jednom instalisanom igrom. 65 | Primljena sačuvana igre nije mogla biti sačuvana. 66 | Nijedna od instalisanih igara nema cart-RAM. 67 | Traženje uređaja u blizini... Uverite se da je uređaj na koji šaljete u "Prijem sačuvane igre" modu. 68 | Pokušaj spajanja sa MeBoy-em na odabranom uređaju... 69 | Čeka se na prenos sačuvane igre... 70 | Nepodržana verzija. 71 | Birajte uređaj 72 | Prijem... 73 | -------------------------------------------------------------------------------- /lang/16.txt: -------------------------------------------------------------------------------- 1 | Pradėti Žaidimą 2 | Tęsti Žaidimą 3 | Nustatymai 4 | Rodyti Informaciją 5 | Bluetooth 6 | Apie MeBoy 7 | Išeiti 8 | Pasirinkti Žaidimą 9 | Ištrinti 10 | Padvigubinti 11 | Atgal 12 | Negalima tęsti ,nes žaidimas nėra įdiegtas 13 | Kadrų peršokimas 14 | Ekrano pasukimas (x90°) 15 | Grafika 16 | Sutalpinti ekrane 17 | Palikti proporcijas 18 | Pažangesnė grafika 19 | Pažangesnis sutalpinimas 20 | Garsas 21 | Įjungti garsą 22 | Eksperimentinės savybės 23 | Kalba 24 | Kita 25 | Išjungti GBC nuskaitymą 26 | Rodyti informacija MeBoy menu 27 | Didžiausias pakrauto žaidimo dydis kB 28 | Išsaugoti 29 | Informacija 30 | Laisva atmintis: 31 | Pauzė 32 | Tęsti 33 | Sustabdyti 34 | Rodyti/slėpti kadrus 35 | Per visą ekraną 36 | Nustatyti klavišus 37 | Išeiti 38 | Paspausti klavišą 39 | dešinė 40 | kairė 41 | viršus 42 | apačia 43 | A 44 | B 45 | Select 46 | Start 47 | MeBoy klaida 48 | Įvyko klaida 49 | Nesuderinamas išsaugotas žaidimas 50 | Žaidimo įkrovimas nepavyko 51 | Nepavyko atnaujinti sustabdyto žaidimo 52 | Negali įkrauti žaidimų sarašo 53 | Siųsti žaidimo-RAM 54 | Žaidimo-RAM 55 | Siųsti sustabdytą žaidimą 56 | Sustabdyti žaidimai 57 | Gauti išsaugotą žaidimą 58 | Atšaukti 59 | Bluetooth perdavimas įvyko sekmingai. 60 | Bluetooth perdavimas nepavyko. 61 | Nerasta Bluetooth prietaisų. 62 | Perdavimas... 63 | Negali prisijungti prie MeBoy prietaiso. 64 | Išsaugotasis žaidimas negalėjo būti išsiųstas ,nes neatitiko nei vieno įdiegto žaidimo 65 | Gautas išsaugotasis žaidimas negali būti išsaugotas. 66 | Nei vienas iš įdiegtų žaidimų neturi žaidimo-RAM. 67 | Ieškoma prietaisų. Isitikinkite ,jog prietaisas kuriam siunčiete yra "Gauti išsaugotą žaidimą" rėžime. 68 | Bandoma prisijungti prie MeBoy pasirinktame prietaise... 69 | Laukiama kol išsaugotas žaidimas bus perkeltas... 70 | Neatitinka versija. 71 | Pasirinkite prietaisą 72 | Gaunama... 73 | 74 | -------------------------------------------------------------------------------- /lang/17.txt: -------------------------------------------------------------------------------- 1 | Sākt spēli 2 | Turpināt spēli 3 | Uzstādījumi 4 | Parādīt informāciju 5 | Bluetooth 6 | Par MeBoy 7 | Iziet 8 | Izvēlēties spēli 9 | Izdzēst 10 | Kopēt 11 | Atpakaļ 12 | Nav iespējams turpināt, kopš spēle vairs nav instalēta 13 | Katru izlaišanas skaits 14 | Ekrāna rotācija (x90°) 15 | Grafiska 16 | Mērogot 17 | Saglabāt attiecības 18 | Uzlabotie grafiskie elementi 19 | Uzlabotās mērogošanas režīms 20 | Skaņa 21 | Atļaut skaņu 22 | Ekspermentālie papildinājumi 23 | Valoda 24 | Cits 25 | Neatļaut GBC emulāciju 26 | Rādīt "Informāciju" izvēlnē 27 | Pieļaujamais saglabājamo datu lielums kB 28 | Saglabāt 29 | Informācija 30 | Brīvā atmiņa: 31 | Pauze 32 | Turpināt 33 | Saglabāt 34 | Rādīt/slēpt kadru izlaidumu skaitu 35 | Pilnekrāns 36 | Uzstādīt taustiņus 37 | Iziet 38 | Nospiediet atbilstošo tautiņu 39 | pa labi 40 | pa kreisi 41 | uz augšu 42 | uz leju 43 | A 44 | B 45 | Izvēlēties 46 | Sākt 47 | MeBoy kļūda 48 | Radās kļūda 49 | Nesaderīgs saglabātās spēles fails. 50 | Spēles ielādēšana neizdevās. 51 | Neizdevās atsākt saglabāto spēli 52 | Nav iespējams ielādēt saglabāto datu sarakstu. 53 | Nosūtīt datus 54 | Saglabātais-RAM 55 | Nosūtīt saglabāto spēli 56 | Saglabātās spēles 57 | Saņemt datus 58 | Atcelt 59 | Bluetooth pārraide veiksmīgi pabeigta. 60 | Bluetooth pārraide neizdevās. 61 | Netika atrasta neviena Bluetooth ierīce. 62 | Pārsūta... 63 | Šajā ierīcē nav iespējams savienoties ar MeBoy. 64 | Nav iespējams saņemt datus, jo tie neatbilst nevienai instalētajai spēlei. 65 | Saņemtos datus nav iespējams saglabāt. 66 | Nevienai no instalētajām spēlēm nav atbilstošu datu. 67 | Meklē tuvumā esošās ierīces. Pārliecinieties, ka ierīce, kurai jūs vēlaties nosūtīt datus ir "saņemt datus" režīmā. 68 | Mēģina savienoties ar MeBoy uz izvēlētās ierīces... 69 | Gaida, kamērt dati tiks pārsūtīti... 70 | Nesaderīga versija. 71 | Izvēlēties ierīci 72 | Saņem... 73 | -------------------------------------------------------------------------------- /lang/2.txt: -------------------------------------------------------------------------------- 1 | Νέο Παιχνίδι 2 | Συνέχεια Παιχνιδιού 3 | Ρυθμίσεις 4 | Εμφάνιση log 5 | Bluetooth 6 | Σχετικά με το MeBoy 7 | Έξοδος 8 | Επιλογή Παιχνιδιού 9 | Διαγραφή 10 | Διπλασιασμός 11 | Επιστροφή 12 | Αδυναμία συνέχειας εφόσον το παιχνίδι δεν ειναι εγκατεστημένο 13 | Frame skip 14 | Περιστροφές οθόνης ανά 90° 15 | Γραφικά 16 | Εφαρμοστή κλίμαξη 17 | Συγκράτηση αναλογίας 18 | Γραφικά (Επιπλέον) 19 | Κλίμαξη (Επιπλέον) 20 | Ήχος 21 | Ενεργοποίηση ήχου 22 | Δυνατότητες (Υπό δοκιμή) 23 | Γλώσσα 24 | Άλλο 25 | Απενεργοποίηση GBC 26 | Εμφάνιση "Log" στο menu 27 | Μέγιστο ''loaded cart'' μέγεθος (σε kB) 28 | Αποθήκευση 29 | Log 30 | Ελεύθερη μνήμη: 31 | Παύση 32 | Συνέχεια 33 | Αναστολή 34 | Εμφάνιση/Απόκρυψη framerate 35 | Πλήρης Οθόνη 36 | Ορισμός πλήκτρων 37 | Έξοδος 38 | Πιέστε το πλήκτρο για 39 | δεξιά 40 | αριστερά 41 | πάνω 42 | κάτω 43 | Α 44 | Β 45 | Select 46 | Start 47 | Σφάλμα MeBoy 48 | Εμφανίστηκε σφάλμα 49 | Μή συμβατό savegame. 50 | Η έναρξη του παιχνιδιού απέτυχε 51 | Αποτυχία συνέχειας ενός ''suspended'' παιχνιδιού 52 | Αποτυχία φώρτωσης λίστας παιχνιδιών. 53 | Αποστολή cart-RAM 54 | Cart-RAM 55 | Αποστολή ''suspended'' παιχνιδιού 56 | παιχνίδια ''suspended'' 57 | Λήψη savegame 58 | Άκυρο 59 | Η μεταφορά Bluetooth τερματίστηκε επιτυχώς! 60 | Η μεταφορά Bluetooth τερματίστηκε ανεπιτυχώς. 61 | Καμία συσκευή Bluetooth δεν βρέθηκε. 62 | Μεταφορά... 63 | Αδυναμία σύνδεσης του MeBoy με την συσκευή. 64 | Αδυναμία λήψης του savegame διότι δεν ταιριάζει με κανένα εγκατεστημένο παιχνίδι. 65 | Αδυναμία αποθήκευσης του savegame που ελήφθη. 66 | Κανένα απο τα εγκατεστημένα παιχνίδια δεν έχουν cart-RAM. 67 | Αναζήτηση συσκευών. Σιγουρευτείτε οτί οτί η συσκευή αποστολής είναι σε καταάσταση ''Λήψη savegame''. 68 | Προσπάθεια σύνδεσης του MeBoy στην επιλεγμένη συσκευή... 69 | Αναμονή μεταφοράς savegame... 70 | Μη-συμβατή έκδοση. 71 | Επιλογή Συσκευής 72 | Λήψη... 73 | -------------------------------------------------------------------------------- /lang/21.txt: -------------------------------------------------------------------------------- 1 | Oyuna Baþla 2 | Oyuna Devam Et 3 | Ayarlar 4 | Kütüðü Göster 5 | Bluetooth 6 | MeBoy Hakkýnda 7 | Çýkýþ 8 | Oyun Oyna 9 | Sil 10 | Çoðalt 11 | Geri 12 | Bir oyun kurmadan devam edemezsiniz. 13 | Kare atlama 14 | Ekraný çevir (x90°) 15 | Grafik Ayarlarý 16 | Ekran boyuna göre küçült 17 | Oranlarý muhafaza et 18 | Geliþmiþ grafikler 19 | Geliþmiþ ölçekleme modu 20 | Ses 21 | Ses açýk 22 | Geliþmiþ özellikler 23 | Dil 24 | Diðer 25 | GBC emülasyonunu devre dýþý býrak 26 | Menüde "Kütük" seçeneðini göster 27 | Maks. yüklenen kartuþ boyutu kB 28 | Kaydet 29 | Kütük 30 | Kalan hafýza: 31 | Duraklat 32 | Devam Et 33 | Ara ver 34 | Kare Oranýný Göster/Gizle 35 | Tam ekran 36 | Tuþlarý ayarla 37 | Çýkýþ: 38 | Aþaðýda belirtilen buton için bir tuþa basýnýz: 39 | Sað 40 | Sol 41 | Yukarý 42 | Aþaðý 43 | A 44 | B 45 | Select 46 | Start 47 | MeBoy Hatasý 48 | Bir hata meydana geldi. 49 | Bu, desteklenmeyen bir kayýt dosyasýdýr. 50 | Oyun yükleme baþarýsýz. 51 | Ara verilmiþ bir oyunun güncellenmesinde problem çýktý. 52 | Kartuþ (oyun) listesi bulunamadý. 53 | Cart-RAM Yolla 54 | Cart-RAM 55 | Ara verilmiþ bir oyun yolla 56 | Ara verilmiþ oyunlar 57 | Karþýdan kaydedilmiþ oyun al 58 | Ýptal 59 | Bluetooth aktarýmý baþarýyla tamamlandý. 60 | Bluetooth aktarýmý baþarýsýzlýkla sonuçlandý. 61 | Bluetooth aygýtý bulunamadý. 62 | Aktarýlýyor... 63 | MeBoy ile baðlantý kurulamadý. 64 | Kaydedilmiþ oyun dosyasý alýnamadý çünkü sizde kurulu olan oyunlardan hiçbirisi ile eþleþtirilemedi. 65 | Alýnan oyun kayýt dosyasý kaydedilemedi. 66 | Yüklenen oyunlarýn hiçbirinde cart-RAM bulunmuyor. 67 | Çevrenizdeki açýk aygýtlar aranýyor. Karþýdaki aygýtýn da "Karþýdan oyun kaydý al" modunda olduðuna emin olun. 68 | Seçtiðiniz aygýttaki MeBoy ile baðlantý kurulmaya çalýþýlýyor... 69 | Bir kayýt dosyasý almak için bekleniyor... 70 | Desteklenmeyen versiyon. 71 | Aygýt seç 72 | Alýnýyor... 73 | 74 | 75 | -------------------------------------------------------------------------------- /lang/5.txt: -------------------------------------------------------------------------------- 1 | Nowa Gra 2 | Wznów Grę 3 | Ustawienia 4 | Pokaż log 5 | Bluetooth 6 | O Programie 7 | Wyjscie 8 | Wybierz Grę 9 | Usuń 10 | Skopiuj 11 | Powrót 12 | Nie można wznowić gry ponieważ nie jest ona zainstalowana 13 | Przeskok klatek 14 | Ilośc obrotów o 90° 15 | Grafika 16 | Dopasuj do ekranu 17 | Zachowaj proporcje 18 | Zaawansowana emulacja grafiki 19 | Zaawansowane skalowanie 20 | Dźwięk 21 | Włącz dźwięk 22 | Eksperymentalne usprawnienia 23 | Język 24 | Inne 25 | Wyłącz emulację GBC 26 | Pokazuj "Log" w menu 27 | Max. wielkość wczytanego fragmentu gry[kB] 28 | Zapisz 29 | Log 30 | Wolna pamięc: 31 | Pauza 32 | Wznów 33 | Zapisz 34 | Pokaż/ukryj ilość klatek na sek. 35 | Pełny Ekran 36 | Sterowanie 37 | Wyłącz 38 | Wciśnij 39 | prawo 40 | lewo 41 | góra 42 | dół 43 | A 44 | B 45 | Select 46 | Start 47 | Błąd MeBoy'a 48 | Wystąpił błąd 49 | Niekompatybilny zapis stanu gry 50 | Wczytanie gry nie powiodło się 51 | Aktualizacja zapisu stanu gry nie powiodła się 52 | Wczytanie listy gier nie powiodło się 53 | Wyślij zawartość wewnętrznego RAMu gry 54 | Wewnętrzny RAM gier 55 | Wyślij zapis stanu gry 56 | Zapisy stanów gier 57 | Odbierz zapis stanu gry 58 | Anuluj 59 | Transmisja Bluetooth zakończona sukcesem. 60 | Transmisja Bluetooth nie powiodła się. 61 | Nie wykryto urządzeń z Bluetooth. 62 | Przesyłam... 63 | Połączenie z MeBoy'em na wybranym urządzeniu nie powiodło się. 64 | Nie można odebrać zapisu stanu gry, ponieważ nie pasuje on do żadnej z zainstalowanych gier. 65 | Zapisanie odebranego stanu gry nie powiodło się. 66 | Żadna z zainstalowanych gier nie posiada wewnętrznego zapisu stanu. 67 | Szukam urządzeń. Upewnij się, że wybrane urządzenie docelowe jest w trybie "Odbierz zapis stanu gry". 68 | Próba połączenia z MeBoy'em na wybranym urządzeniu... 69 | Oczekiwanie na transmisje zapisanego stanu gry... 70 | Niekompatybilna wersja 71 | Wybierz urządzenie 72 | Odbieram... 73 | -------------------------------------------------------------------------------- /lang/10.txt: -------------------------------------------------------------------------------- 1 | Nuova partita 2 | Riprendi partita 3 | Impostazioni 4 | Mostra log 5 | Bluetooth 6 | About MeBoy 7 | Esci 8 | Seleziona Gioco 9 | Elimina 10 | Duplica 11 | Indietro 12 | Impossibile ripristinare il gioco poichè non installato 13 | # frame da saltare 14 | # di rotazioni di 90° 15 | Grafica 16 | Adatta schermo 17 | Mantieni proporzioni 18 | Grafica avanzata 19 | Modalità scaling avanzate 20 | Sonoro 21 | Abilita sonoro 22 | Caratteristiche avanzate 23 | Lingua 24 | Altro 25 | Disabilita emulazione GBC 26 | Mostra "Log" nel menu 27 | Dimensione massima cartuccia (kB) 28 | Salva 29 | Log 30 | Memoria libera: 31 | Pausa 32 | Riprendi 33 | Sospendi 34 | Mostra/Nascondi framerate 35 | Schermo intero 36 | Imposta pulsanti 37 | Esci da 38 | Premi il pulsante per 39 | Destra 40 | Sinistra 41 | Sopra 42 | Sotto 43 | A 44 | B 45 | Seleziona 46 | Inizia 47 | Errore di MeBoy 48 | Si è verificato un errore 49 | Gioco salvato incompatibile. 50 | Errore durante il caricamento 51 | Impossibile aggiornare il gioco sospeso 52 | Impossibile caricare la lista dei giochi. 53 | Invia Cart-RAM 54 | Cart-RAM 55 | Invia gioco sospeso 56 | Giochi sospesi 57 | Ricevi salvataggi 58 | Annulla 59 | Trasmissione Bluetooth completata correttamente. 60 | Trasmissione Bluetooth fallita. 61 | Nessun dispositivo Bluetooth trovato. 62 | Trasmissione in corso... 63 | Impossibile connettersi a MeBoy del dispositivo. 64 | Il salvataggio non può essere ricevuto poiché non corrisponde ad alcun gioco installato. 65 | Il salvataggio ricevuto non può essere salvato. 66 | Nessuono dei giochi installati ha un Cart-RAM. 67 | Ricerca di dispositivi nelle vicinanze. Assicurarsi che in dispositivo a cui si vuole inviare sia nella modalità "Ricevi salvataggi". 68 | Tentativo di connessione a MeBoy sul dispositivo selezionato... 69 | In attesa della trasmissione di un salvataggio... 70 | Versione incompatibile. 71 | Seleziona dispositivo 72 | Ricezione in corso... 73 | -------------------------------------------------------------------------------- /lang/18.txt: -------------------------------------------------------------------------------- 1 | Játék indítása 2 | Játék folytatása 3 | Beállítások 4 | Log fájl mutatása 5 | Bluetooth 6 | A MeBoy-ról 7 | Kilépés 8 | Játék kiválasztása 9 | Törlés 10 | Másolás 11 | Vissza 12 | Nem lehet folytatni, mert a mentéshez tartozó játék nincs telepítve 13 | Képkockák átugrása 14 | Képernyő elforgatása (x90°) 15 | Grafika 16 | Skálázás az illeszkedéshez 17 | Arányok megtartása 18 | Fejlettebb grafika 19 | Fejlettebb skálázási mód 20 | Hangok 21 | Hangok engedélyezése 22 | Kísérleti jellemzők 23 | Nyelv 24 | Egyebek 25 | GBC emuláció tiltása 26 | "Log" mutatása a menüben 27 | Legnagyobb betölthetö játék mérete kB-ban 28 | Mentés 29 | Log 30 | Szabad memória: 31 | Szünet 32 | Folytatás 33 | Mentés 34 | Képkockaszám mutatása/rejtése 35 | Teljes képernyő 36 | Gombok beállítása 37 | Kilépés ebből: 38 | Nyomja meg a(z) 39 | Jobbra gombot 40 | Balra gombot 41 | Fel gombot 42 | Le gombot 43 | A gombot 44 | B gombot 45 | Kiválasztó gombot 46 | Start gombot 47 | MeBoy Hiba 48 | Hiba történt 49 | Nem kompatibilis mentésfájl. 50 | Nem sikerült betölteni a játékot 51 | Nem sikerült frissíteni egy mentett játékot 52 | Nem lehet betölteni a játékok listáját. 53 | Játék-RAM küldése 54 | Játék-RAM 55 | Mentett játék küldése 56 | Mentett játékok 57 | Mentésfájl fogadása 58 | Mégse 59 | A Bluetooth-os küldés sikeresen befejeződött. 60 | A Bluetooth-os küldés sikertelen. 61 | Nem található Bluetooth eszköz. 62 | Küldés... 63 | Nem lehet kapcsolódni az eszközön található MeBoy-hoz. 64 | A mentésfájl nem fogadható, mert nem illik egyetlen telepített játékhoz sem. 65 | A fogadott mentésfájlt nem lehet elmenteni. 66 | A telepített játékok egyikének sincs játék-RAM-ja. 67 | Eszközök keresése a közelben. Bizonyosodjon meg róla, hogy a fogadó eszköz "mentésfájl fogadása" módban van-e! 68 | Kapcsolódás a kiválasztott eszközön található MeBoy-hoz... 69 | Várakozás mentésfájl küldésére... 70 | Nem kompatibilis verzió. 71 | Eszköz kiválasztása 72 | Fogadás... 73 | 74 | -------------------------------------------------------------------------------- /lang/9.txt: -------------------------------------------------------------------------------- 1 | Nouveau jeu 2 | Reprendre une partie 3 | Paramètres 4 | Afficher le journal 5 | Bluetooth 6 | A propos de MeBoy 7 | Quitter 8 | Sélectionner le jeu 9 | Supprimer 10 | Dupliquer 11 | Retour 12 | Impossible de reprendre car le jeu n'est pas installé 13 | Saut d'images 14 | Nombre de rotations 90° 15 | Graphismes 16 | Adapter l'image à l'écran 17 | Conserver les proportions 18 | Graphismes avancés 19 | Adaptation personnalisée 20 | Son 21 | Activer le son 22 | Fonctionnalités expérimentales 23 | Langue 24 | Autres 25 | Désactiver GBC 26 | Afficher "Journal" dans le menu 27 | Taille de cartouche max kB 28 | Enregistrer 29 | Journal 30 | Mémoire libre: 31 | Pause 32 | Reprendre 33 | Suspendre 34 | Afficher/masquer images par seconde 35 | Plein écran 36 | Configurer les touches 37 | Quitter 38 | Choisissez une touche pour 39 | droite 40 | gauche 41 | haut 42 | bas 43 | A 44 | B 45 | Select 46 | Start 47 | Erreur de MeBoy 48 | Une erreur est survenue 49 | Sauvegarde incompatible 50 | Echec du chargement du jeu 51 | Echec de mise à jour du jeu sauvegardé 52 | Impossible de charger la liste des jeux 53 | Envoyer une sauvegarde RAM 54 | Sauvegarde RAM 55 | Envoyer partie suspendue 56 | Parties suspendues 57 | Sauvegardes reçues 58 | Annuler 59 | Transmission par Bluetooth terminée avec succès. 60 | Echec de la transmission par Bluetooth. 61 | Aucun périphérique bluetooth détecté. 62 | Transfert en cours... 63 | Impossible de se connecter à MeBoy sur le périphérique 64 | Impossible de recevoir la sauvegarde car elle ne correspond à aucun jeu installé. 65 | Impossible d'enregistrer la sauvegarde reçue. 66 | Aucun des jeux installés n'a de sauvegarde RAM. 67 | Recherche des périphériques à portée. Vérifiez que le périphérique récepteur de la sauvegarde est bien en mode "réception de sauvegarde". 68 | Tentative de connexion de MeBoy au périphérique sélectionné... 69 | Attente du transfert d'une sauvegarde... 70 | Version incompatible. 71 | Sélectionner un périphérique 72 | Réception... 73 | -------------------------------------------------------------------------------- /lang/6.txt: -------------------------------------------------------------------------------- 1 | Spiel starten 2 | Spiel fortsetzen 3 | Optionen 4 | Log anzeigen 5 | Bluetooth 6 | Über MeBoy 7 | Beenden 8 | Spiel wählen 9 | Löschen 10 | Duplizieren 11 | Zurück 12 | Spielstand kann nicht fortgesetzt werden, weil dass zugehörige Spiel nicht installiert ist 13 | Frames überspringen 14 | Bildschirm rotieren (x90°) 15 | Grafikeinstellungen 16 | Auf passende Größe skalieren 17 | Proportionen beibehalten 18 | Erweiterter Zeichenmodus (langsamer) 19 | Erweiterter Skalierungsmodus 20 | Sound 21 | Sound aktivieren 22 | Experimentelle Features 23 | Sprache 24 | Anderes 25 | GBC Emulation deaktivieren 26 | "Log" im Menü anzeigen 27 | Maximal geladene Cartgröße (kB) 28 | Speichern 29 | Log 30 | Freier Speicher: 31 | Pause 32 | Fortsetzen 33 | Auswerfen 34 | Framerate ein/ausblenden 35 | Vollbild 36 | Tasten bearbeiten 37 | Beenden von 38 | Drücke die Taste für 39 | Rechts 40 | Links 41 | Hoch 42 | Runter 43 | A 44 | B 45 | Select 46 | Start 47 | MeBoy Fehler 48 | Ein Fehler ist aufgetreten 49 | Imkompatibler Speicherstand. 50 | Laden des Spielstandes fehlgeschlagen 51 | Spielstand konnte nicht gespeichert werden 52 | Liste der Carts konnte nicht geladen werden 53 | Sende cart-RAM 54 | Cart-RAM 55 | Sende gespeichertes Spiel 56 | Spielstände 57 | Empfange Spielstand 58 | Abbrechen 59 | Bluetoothübertragung erfolgreich abgeschlossen. 60 | Bluetoothübertragung fehlgeschlagen 61 | Keine Bluetoothdevices gefunden 62 | Übertrage... 63 | Verbindungsfehler mit MeBoy auf Zielgerät. 64 | Der Spielstand konnte nicht empfangen werden, weil er zu keinem installiertem Spiel passt. 65 | Der empfangene Spielstand konnte nicht gespeichert werden. 66 | Keines der installierten Spiele besitzt cart-RAM. 67 | Suche nach Geräten in der Umgebung... Stellen sie sicher, dass sich das Zielgerät im "Spielstand empfangen"-Modus befindet. 68 | Versuche zu MeBoy auf dem Zielgerät zu verbinden... 69 | Warten auf Übertragung des Spielstandes... 70 | Inkompatible Version. 71 | Gerät auswählen 72 | Empfange... 73 | 74 | -------------------------------------------------------------------------------- /lang/7.txt: -------------------------------------------------------------------------------- 1 | Nieuw Spel 2 | Momentopname Hervatten 3 | Instellingen 4 | Logboek tonen 5 | Bluetooth 6 | Over MeBoy 7 | Afsluiten 8 | Selecteer Spel 9 | Verwijderen 10 | Kopiëren 11 | Terug 12 | Kan niet hervatten omdat het spel niet geïnstalleerd is 13 | Frames overslaan 14 | Scherm roteren (x90°) 15 | Grafische weergave 16 | Op het scherm laten passen 17 | In verhoudingen houden 18 | Geavanceerde grafische weergave 19 | Geavanceerde schalings modus 20 | Geluid 21 | Geluid aanzetten 22 | Experimentele functies 23 | Taal 24 | Anders 25 | GBC emulatie uitschakelen 26 | Laat logboek zien in menu 27 | Maximaal geheugengebruik spel in kB 28 | Sla op 29 | Logboek 30 | Vrij geheugen: 31 | Pauze 32 | Hervatten 33 | Momentopname opslaan 34 | Framerate weergeven/verbergen 35 | Volledig scherm 36 | Besturing instellen 37 | Afsluiten 38 | Druk op de knop voor 39 | rechts 40 | links 41 | omhoog 42 | omlaag 43 | A 44 | B 45 | Select 46 | Start 47 | Meboy Fout 48 | Er heeft een fout plaatsgevonden 49 | Ongeschikt opgeslagen spel. 50 | Laden van het spel mislukt 51 | Updaten momentopname spel mislukt 52 | Kan de lijst met spellen niet laden. 53 | Verstuur opgeslagen spel 54 | Opgeslagen spellen 55 | Verstuur momentopname spel 56 | Momentopnames spellen 57 | Opgeslagen spel ontvangen 58 | Annuleren 59 | Bluetooth overdracht voltooid. 60 | Bluetooth overdracht mislukt. 61 | Geen Bluetooth apparaten gevonden. 62 | Versturen... 63 | Kan geen verbinding maken met MeBoy op het apparaat. 64 | Het opgeslagen spel kon niet ontvangen worden, omdat het bijbehorende spel niet geïnstalleerd is. 65 | Het ontvangen opgeslagen spel kon niet opgeslagen worden. 66 | Geen van de geïnstalleerde spellen ontdersteunt opgeslagen spellen. 67 | Zoekend naar naarbije apparaten. Ontvangend apparaat moet in "Opgeslagen spel ontvangen" modus staan. 68 | Probeert verbinding te maken met Meboy op het geselecteerde apparaat... 69 | Wachten op het opgeslagen spel om verstuurd te worden... 70 | Ongeschikte versie. 71 | Selecteer apparaat 72 | Ontvangen... 73 | -------------------------------------------------------------------------------- /changelog.txt: -------------------------------------------------------------------------------- 1 | MeBoy Change Log: 2 | ----------------- 3 | 4 | 5 | version 2.2: 1 Feb 2009 6 | ----------------------- 7 | 8 | - Improved speed of writes to graphics memory 9 | - Improved screen redraw syncing, especially for Advanced Graphics mode 10 | - Improved speed of doubled sprites (8x16 pixel) by drawing one large image 11 | instead of two small 12 | - Improved speed of decoding images in Advanced Graphics mode 13 | - Tweaked the sprite drawing loop to be slightly faster and better reflect the 14 | order sprites should be drawn, especially in Gameboy Color games 15 | - Simple Graphics mode now crops images that have transparent borders 16 | - Simple Graphics detects solid images, and creates Images without alpha 17 | channel 18 | - Tweaked decoding of images when scaling the screen 19 | - Fixed flicker when the LCD screen was turned off 20 | - Fixed bug when decoding Gameboy Color palettes (Pokemon trading card game 21 | starts now) 22 | - Fixed HDMA bug when interrupts are disabled (Donkey Kong Country starts now) 23 | - Fixed speed-switch bug where read-only bits could be written to (Conker's 24 | Pocket Tales starts now) 25 | - Tweaked handling of cart-RAM (hopefully improving compatibility, but it's 26 | hard to tell) 27 | - Emulation is now paused when setting keys 28 | 29 | 30 | version 2.1: 6 Dec 2008 31 | ----------------------- 32 | 33 | - Fixed bug where canceling a Bluetooth transmission would not completely 34 | stop the operation, causing errors when retrying 35 | - Fixed graphics bug where sprites in some games (R-Type, Kirby's Dream Land 36 | 2...) were missing in regular graphics mode 37 | - Fixed bug that caused the real-time clock to not update 38 | - Fixed sound bug where the volumes of notes were not updated correctly 39 | - Fixed hiding of Bluetooth menu item for phones lacking JSR 82 support 40 | - Added new MeBoy icon, in several sizes 41 | - MeBoyBuilder: Added option for user to choose MeBoy icon size, or a custom 42 | icon. 43 | 44 | 45 | version 2.0: 23 Nov 2008 46 | ------------------------ 47 | 48 | - Added sound support 49 | - Added support for sending cart-RAM and suspended games via Bluetooth 50 | - Added "Advanced graphics" mode with slow but (almost) pixel-perfect emulation 51 | - Added icon 52 | - Added "Pause" overlay when paused 53 | - Improved drawing compatibility ("locked" graphics should be more rare now) 54 | - Improved interrupt timing emulation 55 | - Improved instruction timing emulation 56 | - Improved error messages 57 | - Reordered menu items to put most frequently used items first 58 | - Dropped backwards compatibility for 1.1/1.2-era savegames 59 | - Completely redesigned MeBoyBuilder UI: 60 | - Added support for renaming/removing games 61 | - Added support for sending/receiving savegames via Bluetooth 62 | - Added "select location" dialog for the MeBoy.jar file 63 | - Added support for renaming the .jar file (and thus the application) 64 | - Improved error messages 65 | 66 | 67 | version 1.6: 3 Mar 2008 68 | ----------------------- 69 | 70 | - Fixed "Scale to fit" bug for very large screens 71 | - Fixed bug causing some games to crash after hours of gameplay (Pokemon 72 | games should run better now) 73 | - Fixed HDMA transfer status (Lemmings works now) 74 | - Spanish translation by Pendor (http://www.pendor.com.ar/) 75 | 76 | 77 | version 1.5: 18 Sept 2007 78 | ------------------------- 79 | 80 | - Added "Scale to fit" for phones with large screens 81 | - Added preference for starting games in full screen mode 82 | - Added preference for disabling Gameboy Color support 83 | - Optimized drawing code, some games (like the early Pokemon games) run faster 84 | - Fixed bug where GBC games would show incorrect colors after resuming a 85 | suspended game 86 | - Fixed several minor timing bugs 87 | - Improved memory deallocation code, some phones should no longer get 88 | "out-of-memory" messages (Fix by Alberto Simon) 89 | 90 | 91 | version 1.4: 22 Jun 2007 92 | ------------------------ 93 | 94 | - Added "Shrink to fit" for phones with small screens 95 | - Fixed potential stack handling bug for GBC games 96 | - Fixed several register initialization bugs 97 | - Fixed several GBC palette reading, writing and initialization bugs 98 | - Improved performance for GB games by using separate GB and GBC classes 99 | - Improved performance when an entire ROM does not fit in memory 100 | - Slightly improved performance when initializing images (still really slow 101 | though) 102 | 103 | 104 | version 1.3.1: 29 May 2007 105 | -------------------------- 106 | 107 | - Fixed cartridge mapping bug, improves game compatibility (e.g. Catwoman) 108 | - Fixed sound status register bug, improves game compatibility (e.g. Legend of 109 | Zelda - Oracle of Ages) 110 | - MeBoyBuilder: added option for transferring saved games from version 1.1/1.2 111 | 112 | 113 | version 1.3: 24 May 2007 114 | ------------------------ 115 | 116 | - Added support for Gameboy Color games 117 | - Added support for multiple saved (suspended) games at once 118 | - Added support for partial loading of very large ROM files (for phones with 119 | limited RAM) 120 | - Added support for realtime clock (used by Pokemon Gold etc) 121 | - In addition to frameskip, number of frames per second is displayed 122 | - Fixed minor keyboard handling bugs, Japanese Pokemon games should work better 123 | now 124 | - Fixed overflow bug that could occur after 2^32 instructions 125 | - Fixed minor windowing bug, Dragonball Z 2 should work better now 126 | - ROM files are split into smaller files, which reduces RAM requirement 127 | - MeBoyBuilder: Now supports zipped ROM files (one ROM per zip file) 128 | - MeBoyBuilder: Now tries to verify that the selected files are valid Gameboy 129 | carts 130 | - MeBoyBuilder: Can now (optionally) automatically check for updates 131 | 132 | 133 | version 1.2: 4 Mar 2007 134 | ----------------------- 135 | 136 | - Added support for rotating the screen (with a slight performance penalty) 137 | - Added speed throttling in the unlikely event that MeBoy exceeds 60 fps 138 | - Added support for rarely used "LCD control operation" for turning off the LCD 139 | - Fixed flag handling bug for increment/decrement instructions, other minor 140 | flag handling bugs 141 | - Fixed timing of screen update interrupts, to better match JavaBoy and 142 | hopefully Gameboy. Grabbing items while spinning in Metroid 2 no longer 143 | causes freeze 144 | - Fixed minor keyboard handling bug 145 | - Fixed palette bug (some sprites had incorrect colors, such as Bugs Bunny and 146 | some enemies in Super Mario Land 2) 147 | - Fixed version bug (1.1 identifies itself as 1.0) 148 | - MeBoyBuilder: Now has a simple UI with some instructions and information 149 | - MeBoyBuilder: Non-ascii characters in filenames now handled 150 | - MeBoyBuilder: Warning when selecting large ROM files and Gameboy Color games 151 | - MeBoyBuilder: MeBoy.jad file is produced, since certain phones seem to 152 | require it 153 | 154 | 155 | version 1.1: 2 Nov 2005 156 | ----------------------- 157 | 158 | - Fixed spriteenabled (Balloon Kid sort of works now) 159 | - Fixed battery RAM 160 | - Fixed "suspend" and "resume" 161 | - Added full screen mode 162 | - Added "unload cart" 163 | - Added the source for MeBoyBuilder 164 | - Added settings for frameskip 165 | - Some small bug fixes and performance improvements 166 | 167 | 168 | Version 1.0: 22 Jul 2005 169 | ------------------------ 170 | 171 | Initial release 172 | 173 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # meboy 2 | Gameboy / GAmeboy Color emulator for Java MIDP 2.0 3 | 4 | License 5 | - 6 | Meboy is distributed under [GPLv2 license](https://github.com/chijure/meboy/blob/master/LICENSE) 7 | LEGAL: This product is not affiliated with, nor authorized, endorsed or licensed in any way by Nintendo Corporation, its affiliates or subsidiaries. 8 | 9 | Credits 10 | - 11 | * Neil Millstone [millstone] - JavaBoy, the original java based GameBoy emulator behind MeBoy 12 | * Björn Carlin [arktos] - Creator of the original MeBoy for J2ME 13 | 14 | MeBoy Change Log 15 | - 16 | 17 | version 2.2: 1 Feb 2009 18 | - 19 | 20 | * Improved speed of writes to graphics memory 21 | * Improved screen redraw syncing, especially for Advanced Graphics mode 22 | * Improved speed of doubled sprites (8x16 pixel) by drawing one large image instead of two small 23 | * Improved speed of decoding images in Advanced Graphics mode 24 | * Tweaked the sprite drawing loop to be slightly faster and better reflect the order sprites should be drawn, especially in Gameboy Color games 25 | * Simple Graphics mode now crops images that have transparent borders 26 | * Simple Graphics detects solid images, and creates Images without alpha channel 27 | * Tweaked decoding of images when scaling the screen to 75% 28 | * Fixed flicker when the LCD screen was turned off 29 | * Fixed bug when decoding Gameboy Color palettes (Pokemon trading card game starts now) 30 | * Fixed HDMA bug when interrupts are disabled (Donkey Kong Country starts now) 31 | * Fixed speed-switch bug where read-only bits could be written to (Conker's Pocket Tales starts now) 32 | * Tweaked handling of cart-RAM (hopefully improving compatibility, but it's hard to tell) 33 | * Emulation is now paused when setting keys 34 | 35 | 36 | version 2.1: 6 Dec 2008 37 | - 38 | 39 | * Fixed bug where canceling a Bluetooth transmission would not completely stop the operation, causing errors when retrying 40 | * Fixed graphics bug where sprites in some games (R-Type, Kirby's Dream Land 2...) were missing in regular graphics mode 41 | * Fixed bug that caused the real-time clock to not update 42 | * Fixed sound bug where the volumes of notes were not updated correctly 43 | * Fixed hiding of Bluetooth menu item for phones lacking JSR 82 support 44 | * Added new MeBoy icon, in several sizes 45 | * MeBoyBuilder: Added option for user to choose MeBoy icon size, or a custom icon 46 | 47 | version 2.0: 23 Nov 2008 48 | - 49 | 50 | * Added sound support 51 | * Added support for sending cart-RAM and suspended games via Bluetooth 52 | * Added "Advanced graphics" mode with slow but (almost) pixel-perfect emulation 53 | * Added icon 54 | * Added "Pause" overlay when paused 55 | * Improved drawing compatibility ("locked" graphics should be more rare now) 56 | * Improved interrupt timing emulation 57 | * Improved instruction timing emulation 58 | * Improved error messages 59 | * Dropped backwards compatibility for 1.1/1.2-era savegames 60 | * Reordered menu items to put most frequently used items first 61 | * Completely redesigned MeBoyBuilder UI: 62 | * Added support for renaming/removing savegames via Bluetooth 63 | * Added support for sending/receiving savegames via Bluettoth 64 | * Added "select location" dialog for the MeBoy.jar file 65 | * Added support for renaming the .jar file (and thus the application) 66 | * Improved error messages 67 | 68 | version 1.6: 3 Mar 2008 69 | - 70 | 71 | * Fixed "Scale to fit" bug for very large screens 72 | * Fixed bug causing some games to crash after hours of gameplay (Pokemon games should run butter now) 73 | * Fixed HDMA transfer status (Lemmings works now) 74 | * Spanish translation by Pendor (www.pendor.com.ar) 75 | 76 | version 1.5: 10 Sept 2007 77 | - 78 | 79 | * Added "Scale to fit" for phones with large screens 80 | * Added preference for starting games in full screen mode 81 | * Added preference for disabling Gameboy Color support 82 | * Optimized drawing code, some games (like the early Pokemon games) run faster 83 | * Fixed bug where GBC games would show incorrect colors after resuming a suspended game 84 | * Fixed several minor timing bugs 85 | * Improed memory deallocation code, some phones should no longer get "out-of-memory" messages (Fix by Alberto Simon) 86 | 87 | version 1.4: 22 Jun 2007 88 | - 89 | 90 | * Added "Shrink to fit" for phones with small screens 91 | * Fixed potential stack handling bug for GBC games 92 | * Fixed several register initialization bugs 93 | * Fixed several GBC palette reading, writing and initialization bugs 94 | * Improved performance for GB games by using separate GB and GBC classes 95 | * Improved performance when an entire ROM does not fit in memory 96 | * Slightly improved performance when initializing images (still readlly slow though) 97 | 98 | version 1.3.1: 29 May 2007 99 | - 100 | 101 | * Fixed cartridge mapping bug, improves game compatibility (e.g. Catwoman) 102 | * Fixed sound status register bug, improves game compatibility (e.g. Legend of Zelda - Oracle of Ages) 103 | * MeBoyBuilder: added option for transferring saved games from version 1.1/1.2 104 | 105 | version 1.3: 24 May 2007 106 | - 107 | 108 | * Added support for Gameboy Color games 109 | * Added support for multiple saved (suspended) games at once 110 | * Added support for partial loading of very large ROM files (for phones with 111 | limited RAM) 112 | * Added support for realtime clock (used by Pokemon Gold etc) 113 | * In addition to frameskip, number of frames per second is displayed 114 | * Fixed minor keyboard handling bugs, Japanese Pokemon games should work better 115 | now 116 | * Fixed overflow bug that could occur after 2^32 instructions 117 | * Fixed minor windowing bug, Dragonball Z 2 should work better now 118 | * ROM files are split into smaller files, which reduces RAM requirement 119 | * MeBoyBuilder: Now supports zipped ROM files (one ROM per zip file) 120 | * MeBoyBuilder: Now tries to verify that the selected files are valid Gameboy 121 | carts 122 | * MeBoyBuilder: Can now (optionally) automatically check for updates 123 | 124 | version 1.2: 4 Mar 2007 125 | - 126 | * Added support for rotating the screen (with a slight performance penalty) 127 | * Added speed throttling in the unlikely event that MeBoy exceeds 60 fps 128 | * Added support for rarely used "LCD control operation" for turning off the LCD 129 | * Fixed flag handling bug for increment/decrement instructions, other minor 130 | flag handling bugs 131 | * Fixed timing of screen update interrupts, to better match JavaBoy and 132 | hopefully Gameboy. Grabbing items while spinning in Metroid 2 no longer 133 | causes freeze 134 | * Fixed minor keyboard handling bug 135 | * Fixed palette bug (some sprites had incorrect colors, such as Bugs Bunny and 136 | some enemies in Super Mario Land 2) 137 | * Fixed version bug (1.1 identifies itself as 1.0) 138 | * MeBoyBuilder: Now has a simple UI with some instructions and information 139 | * MeBoyBuilder: Non-ascii characters in filenames now handled 140 | * MeBoyBuilder: Warning when selecting large ROM files and Gameboy Color games 141 | * MeBoyBuilder: MeBoy.jad file is produced, since certain phones seem to 142 | require it 143 | 144 | version 1.1: 2 Nov 2005 145 | - 146 | 147 | * fixed spriteenabled (Balloon Kid sort of works now) 148 | * fixed battery RAM 149 | * fixed "suspend" and "resume" 150 | * added full screen mode 151 | * added "unload cart" 152 | * added the source for MeBoyBuilder 153 | * added settings for frameskip 154 | * some small bug fixes and performance improvements 155 | 156 | 157 | version 1.0: 22 Jul 2005 158 | - 159 | 160 | * initial release 161 | 162 | 163 | 164 | [millstone]: 165 | http://www.millstone.demon.co.uk/download/javaboy/ 166 | 167 | [arktos]: http://arktos.se/meboy 168 | 169 | -------------------------------------------------------------------------------- /GraphicsChip.java: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | MeBoy 4 | 5 | Copyright 2005-2009 Bjorn Carlin 6 | http://www.arktos.se/ 7 | 8 | Based on JavaBoy, COPYRIGHT (C) 2001 Neil Millstone and The Victoria 9 | University of Manchester. Bluetooth support based on code contributed by 10 | Martin Neumann. 11 | 12 | This program is free software; you can redistribute it and/or modify it 13 | under the terms of the GNU General Public License as published by the Free 14 | Software Foundation; either version 2 of the License, or (at your option) 15 | any later version. 16 | 17 | This program is distributed in the hope that it will be useful, but WITHOUT 18 | ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 19 | FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for 20 | more details. 21 | 22 | 23 | You should have received a copy of the GNU General Public License along with 24 | this program; if not, write to the Free Software Foundation, Inc., 59 Temple 25 | Place - Suite 330, Boston, MA 02111-1307, USA. 26 | 27 | */ 28 | 29 | import javax.microedition.lcdui.*; 30 | 31 | 32 | public abstract class GraphicsChip { 33 | protected final int MS_PER_FRAME = 17; 34 | 35 | /** Tile is flipped horizontally */ 36 | protected final int TILE_FLIPX = 1; // 0x20 in oam attributes 37 | 38 | /** Tile is flipped vertically */ 39 | protected final int TILE_FLIPY = 2; // 0x40 in oam attributes 40 | 41 | /** The current contents of the video memory, mapped in at 0x8000 - 0x9FFF */ 42 | protected byte[] videoRam; 43 | 44 | protected byte[][] videoRamBanks; // one for gb, two for gbc 45 | 46 | /** RGB color values */ 47 | protected int[] colors; // gb color palette 48 | protected int[] gbPalette = new int[12]; 49 | protected int[] gbcRawPalette = new int[128]; 50 | protected int[] gbcPalette = new int[64]; 51 | protected int gbcMask; // 0xff000000 for simple, 0x80000000 for advanced 52 | protected int transparentCutoff; // min "attrib" value where transparency can occur 53 | 54 | boolean bgEnabled = true; 55 | boolean winEnabled = true; 56 | boolean spritesEnabled = true; 57 | boolean lcdEnabled = true; 58 | boolean spritePriorityEnabled = true; 59 | 60 | // skipping, timing: 61 | public int timer; 62 | protected boolean skipping = true; // until graphics is set 63 | protected int frameCount; 64 | protected int skipCount; 65 | 66 | // some statistics 67 | int lastSkipCount; 68 | 69 | /** Selection of one of two addresses for the BG and Window tile data areas */ 70 | boolean bgWindowDataSelect = true; 71 | 72 | /** If true, 8x16 sprites are being used. Otherwise, 8x8. */ 73 | boolean doubledSprites = false; 74 | 75 | /** Selection of one of two address for the BG/win tile map. */ 76 | boolean hiBgTileMapAddress = false; 77 | boolean hiWinTileMapAddress = false; 78 | protected int tileOffset; // 384 when in vram bank 1 in gbc mode 79 | protected int tileCount; // 384 for gb, 384*2 for gbc 80 | protected int colorCount; // number of "logical" colors = palette indices, 12 for gb, 64 for gbc 81 | 82 | protected boolean scale; 83 | public int scaledWidth = 160; 84 | public int scaledHeight = 144; 85 | 86 | private boolean frameDone = true; // ugly framebuffer "mutex" 87 | 88 | protected Dmgcpu cpu; 89 | 90 | public Image frameBufferImage; 91 | 92 | // lookup table for fast image decoding 93 | protected static int[] weaveLookup = new int[256]; 94 | static { 95 | for (int i = 1; i < 256; i++) { 96 | for (int d = 0; d < 8; d++) 97 | weaveLookup[i] += ((i >> d) & 1) << (d * 2); 98 | } 99 | } 100 | 101 | 102 | /** Create a new GraphicsChip connected to the specified CPU */ 103 | public GraphicsChip(Dmgcpu d) { 104 | cpu = d; 105 | 106 | if (cpu.gbcFeatures) { 107 | videoRamBanks = new byte[2][0x2000]; 108 | tileCount = 384*2; 109 | colorCount = 64; 110 | // 1: image 111 | // * 384: all images 112 | // * 2: memory banks 113 | // * 4: mirrored images 114 | // * 16: all palettes 115 | } else { 116 | videoRamBanks = new byte[1][0x2000]; 117 | tileCount = 384; 118 | colorCount = 12; 119 | // 1: image 120 | // * 384: all images 121 | // * 4: mirrored images 122 | // * 3: all palettes 123 | } 124 | 125 | videoRam = videoRamBanks[0]; 126 | cpu.memory[4] = videoRam; 127 | 128 | scale = false; 129 | 130 | for (int i = 0; i < gbcRawPalette.length; i++) 131 | gbcRawPalette[i] = -1000; // non-initialized 132 | for (int i = 0; i < (gbcPalette.length >> 1); i++) 133 | gbcPalette[i] = -1; // white 134 | for (int i = (gbcPalette.length >> 1); i < gbcPalette.length; i++) 135 | gbcPalette[i] = 0; // transparent 136 | } 137 | 138 | public int unflatten(byte[] flatState, int offset) { 139 | for (int i = 0; i < videoRamBanks.length; i++) { 140 | System.arraycopy(flatState, offset, videoRamBanks[i], 0, 0x2000); 141 | offset += 0x2000; 142 | } 143 | 144 | for (int i = 0; i < 12; i++) { 145 | if ((i & 3) == 0) 146 | gbPalette[i] = 0x00ffffff & GBCanvas.getInt(flatState, offset); 147 | else 148 | gbPalette[i] = 0xff000000 | GBCanvas.getInt(flatState, offset); 149 | offset += 4; 150 | } 151 | 152 | UpdateLCDCFlags(cpu.registers[0x40]); 153 | 154 | if (cpu.gbcFeatures) { 155 | setVRamBank(flatState[offset++] & 0xff); // updates tileOffset 156 | for (int i = 0; i < 128; i++) { 157 | setGBCPalette(i, flatState[offset++] & 0xff); 158 | } 159 | } else { 160 | invalidateAll(0); 161 | invalidateAll(1); 162 | invalidateAll(2); 163 | } 164 | 165 | return offset; 166 | } 167 | 168 | public int flatten(byte[] flatState, int offset) { 169 | for (int i = 0; i < videoRamBanks.length; i++) { 170 | System.arraycopy(videoRamBanks[i], 0, flatState, offset, 0x2000); 171 | offset += 0x2000; 172 | } 173 | 174 | for (int j = 0; j < 12; j++) { 175 | GBCanvas.setInt(flatState, offset, gbPalette[j]); 176 | offset += 4; 177 | } 178 | 179 | if (cpu.gbcFeatures) { 180 | flatState[offset++] = (byte) ((tileOffset != 0) ? 1 : 0); 181 | for (int i = 0; i < 128; i++) { 182 | flatState[offset++] = (byte) getGBCPalette(i); 183 | } 184 | } 185 | 186 | return offset; 187 | } 188 | 189 | public void UpdateLCDCFlags(int data) { 190 | bgEnabled = true; 191 | 192 | // BIT 7 193 | lcdEnabled = ((data & 0x80) != 0); 194 | 195 | // BIT 6 196 | hiWinTileMapAddress = ((data & 0x40) != 0); 197 | 198 | // BIT 5 199 | winEnabled = ((data & 0x20) != 0); 200 | 201 | // BIT 4 202 | bgWindowDataSelect = ((data & 0x10) != 0); 203 | 204 | // BIT 3 205 | hiBgTileMapAddress = ((data & 0x08) != 0); 206 | 207 | // BIT 2 208 | doubledSprites = ((data & 0x04) != 0); 209 | 210 | // BIT 1 211 | spritesEnabled = ((data & 0x02) != 0); 212 | 213 | // BIT 0 214 | if (cpu.gbcFeatures) { 215 | spritePriorityEnabled = ((data & 0x01) != 0); 216 | } else { 217 | if ((data & 0x01) == 0) { 218 | // this emulates the gbc-in-gb-mode, not the original gb-mode 219 | bgEnabled = false; 220 | winEnabled = false; 221 | } 222 | } 223 | } 224 | 225 | public final void vBlank() { 226 | timer += MS_PER_FRAME; 227 | 228 | frameCount++; 229 | 230 | if (skipping) { 231 | skipCount++; 232 | if (skipCount >= MeBoy.maxFrameSkip) { 233 | // can't keep up, force draw next frame and reset timer (if lagging) 234 | skipping = false; 235 | int lag = (int) System.currentTimeMillis() - timer; 236 | 237 | if (lag > MS_PER_FRAME) 238 | timer += lag - MS_PER_FRAME; 239 | } else 240 | skipping = (timer - ((int) System.currentTimeMillis()) < 0); 241 | return; 242 | } 243 | 244 | lastSkipCount = skipCount; 245 | frameDone = false; 246 | cpu.screen.redrawSmall(); 247 | 248 | int now = (int) System.currentTimeMillis(); 249 | 250 | if (MeBoy.maxFrameSkip == 0) 251 | skipping = false; 252 | else 253 | skipping = timer - now < 0; 254 | 255 | // sleep if too far ahead 256 | try { 257 | while (timer > now + MS_PER_FRAME) { 258 | Thread.sleep(1); 259 | now = (int) System.currentTimeMillis(); 260 | } 261 | } catch (InterruptedException e) { 262 | // e.printStackTrace(); 263 | } 264 | 265 | while (!frameDone && !cpu.terminate) { 266 | Thread.yield(); 267 | } 268 | skipCount = 0; 269 | } 270 | 271 | /** Set the palette from the internal Gameboy format */ 272 | public void decodePalette(int startIndex, int data) { 273 | for (int i = 0; i < 4; i++) 274 | gbPalette[startIndex + i] = colors[((data >> (2 * i)) & 0x03)]; 275 | gbPalette[startIndex] &= 0x00ffffff; // color 0: transparent 276 | } 277 | 278 | public void setGBCPalette(int index, int data) { 279 | if (gbcRawPalette[index] == data) 280 | return; 281 | 282 | gbcRawPalette[index] = data; 283 | if (index >= 0x40 && (index & 0x6) == 0) { 284 | // stay transparent 285 | return; 286 | } 287 | 288 | int value = (gbcRawPalette[index | 1] << 8) + gbcRawPalette[index & -2]; 289 | 290 | gbcPalette[index >> 1] = gbcMask + ((value & 0x001F) << 19) + ((value & 0x03E0) << 6) + ((value & 0x7C00) >> 7); 291 | 292 | invalidateAll(index >> 3); 293 | } 294 | 295 | public int getGBCPalette(int index) { 296 | return gbcRawPalette[index]; 297 | } 298 | 299 | public void setVRamBank(int value) { 300 | tileOffset = value * 384; 301 | videoRam = videoRamBanks[value]; 302 | cpu.memory[4] = videoRam; 303 | } 304 | 305 | public final void notifyRepainted() { 306 | frameDone = true; 307 | } 308 | 309 | public void stopWindowFromLine() {} 310 | 311 | public abstract void setScale(int screenWidth, int screenHeight); 312 | 313 | /** Writes data to the specified video RAM address */ 314 | public abstract void addressWrite(int addr, byte data); 315 | 316 | /** Invalidate all tiles in the tile cache for the given palette */ 317 | public abstract void invalidateAll(int pal); 318 | 319 | /** This must be called by the CPU for each scanline drawn by the display hardware. 320 | */ 321 | public abstract void notifyScanline(int line); 322 | 323 | } 324 | -------------------------------------------------------------------------------- /GPL.txt: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 2, June 1991 3 | 4 | Copyright (C) 1989, 1991 Free Software Foundation, Inc. 5 | 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA 6 | 7 | Everyone is permitted to copy and distribute verbatim copies 8 | of this license document, but changing it is not allowed. 9 | 10 | Preamble 11 | The licenses for most software are designed to take away your freedom to share and change it. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change free software--to make sure the software is free for all its users. This General Public License applies to most of the Free Software Foundation's software and to any other program whose authors commit to using it. (Some other Free Software Foundation software is covered by the GNU Library General Public License instead.) You can apply it to your programs, too. 12 | 13 | When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for this service if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs; and that you know you can do these things. 14 | 15 | To protect your rights, we need to make restrictions that forbid anyone to deny you these rights or to ask you to surrender the rights. These restrictions translate to certain responsibilities for you if you distribute copies of the software, or if you modify it. 16 | 17 | For example, if you distribute copies of such a program, whether gratis or for a fee, you must give the recipients all the rights that you have. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights. 18 | 19 | We protect your rights with two steps: (1) copyright the software, and (2) offer you this license which gives you legal permission to copy, distribute and/or modify the software. 20 | 21 | Also, for each author's protection and ours, we want to make certain that everyone understands that there is no warranty for this free software. If the software is modified by someone else and passed on, we want its recipients to know that what they have is not the original, so that any problems introduced by others will not reflect on the original authors' reputations. 22 | 23 | Finally, any free program is threatened constantly by software patents. We wish to avoid the danger that redistributors of a free program will individually obtain patent licenses, in effect making the program proprietary. To prevent this, we have made it clear that any patent must be licensed for everyone's free use or not licensed at all. 24 | 25 | The precise terms and conditions for copying, distribution and modification follow. 26 | 27 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 28 | 0. This License applies to any program or other work which contains a notice placed by the copyright holder saying it may be distributed under the terms of this General Public License. The "Program", below, refers to any such program or work, and a "work based on the Program" means either the Program or any derivative work under copyright law: that is to say, a work containing the Program or a portion of it, either verbatim or with modifications and/or translated into another language. (Hereinafter, translation is included without limitation in the term "modification".) Each licensee is addressed as "you". 29 | 30 | Activities other than copying, distribution and modification are not covered by this License; they are outside its scope. The act of running the Program is not restricted, and the output from the Program is covered only if its contents constitute a work based on the Program (independent of having been made by running the Program). Whether that is true depends on what the Program does. 31 | 32 | 1. You may copy and distribute verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice and disclaimer of warranty; keep intact all the notices that refer to this License and to the absence of any warranty; and give any other recipients of the Program a copy of this License along with the Program. 33 | 34 | You may charge a fee for the physical act of transferring a copy, and you may at your option offer warranty protection in exchange for a fee. 35 | 36 | 2. You may modify your copy or copies of the Program or any portion of it, thus forming a work based on the Program, and copy and distribute such modifications or work under the terms of Section 1 above, provided that you also meet all of these conditions: 37 | 38 | 39 | a) You must cause the modified files to carry prominent notices stating that you changed the files and the date of any change. 40 | 41 | b) You must cause any work that you distribute or publish, that in whole or in part contains or is derived from the Program or any part thereof, to be licensed as a whole at no charge to all third parties under the terms of this License. 42 | 43 | c) If the modified program normally reads commands interactively when run, you must cause it, when started running for such interactive use in the most ordinary way, to print or display an announcement including an appropriate copyright notice and a notice that there is no warranty (or else, saying that you provide a warranty) and that users may redistribute the program under these conditions, and telling the user how to view a copy of this License. (Exception: if the Program itself is interactive but does not normally print such an announcement, your work based on the Program is not required to print an announcement.) 44 | These requirements apply to the modified work as a whole. If identifiable sections of that work are not derived from the Program, and can be reasonably considered independent and separate works in themselves, then this License, and its terms, do not apply to those sections when you distribute them as separate works. But when you distribute the same sections as part of a whole which is a work based on the Program, the distribution of the whole must be on the terms of this License, whose permissions for other licensees extend to the entire whole, and thus to each and every part regardless of who wrote it. 45 | Thus, it is not the intent of this section to claim rights or contest your rights to work written entirely by you; rather, the intent is to exercise the right to control the distribution of derivative or collective works based on the Program. 46 | 47 | In addition, mere aggregation of another work not based on the Program with the Program (or with a work based on the Program) on a volume of a storage or distribution medium does not bring the other work under the scope of this License. 48 | 49 | 3. You may copy and distribute the Program (or a work based on it, under Section 2) in object code or executable form under the terms of Sections 1 and 2 above provided that you also do one of the following: 50 | 51 | a) Accompany it with the complete corresponding machine-readable source code, which must be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, 52 | 53 | b) Accompany it with a written offer, valid for at least three years, to give any third party, for a charge no more than your cost of physically performing source distribution, a complete machine-readable copy of the corresponding source code, to be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, 54 | 55 | c) Accompany it with the information you received as to the offer to distribute corresponding source code. (This alternative is allowed only for noncommercial distribution and only if you received the program in object code or executable form with such an offer, in accord with Subsection b above.) 56 | The source code for a work means the preferred form of the work for making modifications to it. For an executable work, complete source code means all the source code for all modules it contains, plus any associated interface definition files, plus the scripts used to control compilation and installation of the executable. However, as a special exception, the source code distributed need not include anything that is normally distributed (in either source or binary form) with the major components (compiler, kernel, and so on) of the operating system on which the executable runs, unless that component itself accompanies the executable. 57 | If distribution of executable or object code is made by offering access to copy from a designated place, then offering equivalent access to copy the source code from the same place counts as distribution of the source code, even though third parties are not compelled to copy the source along with the object code. 58 | 59 | 4. You may not copy, modify, sublicense, or distribute the Program except as expressly provided under this License. Any attempt otherwise to copy, modify, sublicense or distribute the Program is void, and will automatically terminate your rights under this License. However, parties who have received copies, or rights, from you under this License will not have their licenses terminated so long as such parties remain in full compliance. 60 | 61 | 5. You are not required to accept this License, since you have not signed it. However, nothing else grants you permission to modify or distribute the Program or its derivative works. These actions are prohibited by law if you do not accept this License. Therefore, by modifying or distributing the Program (or any work based on the Program), you indicate your acceptance of this License to do so, and all its terms and conditions for copying, distributing or modifying the Program or works based on it. 62 | 63 | 6. Each time you redistribute the Program (or any work based on the Program), the recipient automatically receives a license from the original licensor to copy, distribute or modify the Program subject to these terms and conditions. You may not impose any further restrictions on the recipients' exercise of the rights granted herein. You are not responsible for enforcing compliance by third parties to this License. 64 | 65 | 7. If, as a consequence of a court judgment or allegation of patent infringement or for any other reason (not limited to patent issues), conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot distribute so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not distribute the Program at all. For example, if a patent license would not permit royalty-free redistribution of the Program by all those who receive copies directly or indirectly through you, then the only way you could satisfy both it and this License would be to refrain entirely from distribution of the Program. 66 | 67 | If any portion of this section is held invalid or unenforceable under any particular circumstance, the balance of the section is intended to apply and the section as a whole is intended to apply in other circumstances. 68 | 69 | It is not the purpose of this section to induce you to infringe any patents or other property right claims or to contest validity of any such claims; this section has the sole purpose of protecting the integrity of the free software distribution system, which is implemented by public license practices. Many people have made generous contributions to the wide range of software distributed through that system in reliance on consistent application of that system; it is up to the author/donor to decide if he or she is willing to distribute software through any other system and a licensee cannot impose that choice. 70 | 71 | This section is intended to make thoroughly clear what is believed to be a consequence of the rest of this License. 72 | 73 | 8. If the distribution and/or use of the Program is restricted in certain countries either by patents or by copyrighted interfaces, the original copyright holder who places the Program under this License may add an explicit geographical distribution limitation excluding those countries, so that distribution is permitted only in or among countries not thus excluded. In such case, this License incorporates the limitation as if written in the body of this License. 74 | 75 | 9. The Free Software Foundation may publish revised and/or new versions of the General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. 76 | 77 | Each version is given a distinguishing version number. If the Program specifies a version number of this License which applies to it and "any later version", you have the option of following the terms and conditions either of that version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of this License, you may choose any version ever published by the Free Software Foundation. 78 | 79 | 10. If you wish to incorporate parts of the Program into other free programs whose distribution conditions are different, write to the author to ask for permission. For software which is copyrighted by the Free Software Foundation, write to the Free Software Foundation; we sometimes make exceptions for this. Our decision will be guided by the two goals of preserving the free status of all derivatives of our free software and of promoting the sharing and reuse of software generally. 80 | 81 | NO WARRANTY 82 | 83 | 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 84 | 85 | 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. 86 | 87 | 88 | END OF TERMS AND CONDITIONS 89 | -------------------------------------------------------------------------------- /SimpleGraphicsChip.java: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | MeBoy 4 | 5 | Copyright 2005-2009 Bjorn Carlin 6 | http://www.arktos.se/ 7 | 8 | Based on JavaBoy, COPYRIGHT (C) 2001 Neil Millstone and The Victoria 9 | University of Manchester. Bluetooth support based on code contributed by 10 | Martin Neumann. 11 | 12 | This program is free software; you can redistribute it and/or modify it 13 | under the terms of the GNU General Public License as published by the Free 14 | Software Foundation; either version 2 of the License, or (at your option) 15 | any later version. 16 | 17 | This program is distributed in the hope that it will be useful, but WITHOUT 18 | ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 19 | FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for 20 | more details. 21 | 22 | 23 | You should have received a copy of the GNU General Public License along with 24 | this program; if not, write to the Free Software Foundation, Inc., 59 Temple 25 | Place - Suite 330, Boston, MA 02111-1307, USA. 26 | 27 | */ 28 | 29 | import javax.microedition.lcdui.Graphics; 30 | import javax.microedition.lcdui.Image; 31 | 32 | 33 | public class SimpleGraphicsChip extends GraphicsChip { 34 | boolean winEnabledThisFrame = true; 35 | boolean spritesEnabledThisFrame = true; 36 | private boolean screenFilled; 37 | private boolean windowStopped; 38 | private boolean winEnabledThisLine = false; 39 | private int windowStopLine = 144; 40 | private int windowStopX; 41 | private int windowStopY; 42 | 43 | // Hacks to allow some raster effects to work. Or at least not to break as badly. 44 | boolean savedWindowDataSelect = false; 45 | 46 | // tiles & image cache 47 | private Image transparentImage; 48 | private Image[] tileImage; 49 | private boolean[] tileReadState; // true if there are any images to be invalidated 50 | private int[][] imageBounds; 51 | 52 | private int[] tempPix; 53 | 54 | int tileWidth = 8; 55 | int tileHeight = 8; 56 | int imageHeight = 8; // either tileHeight or tileHeight * 2 (when processing doubled sprites). 57 | 58 | private Graphics graphics; 59 | 60 | 61 | public SimpleGraphicsChip(Dmgcpu d) { 62 | super(d); 63 | 64 | colors = new int[] { 0xffffffff, 0xffaaaaaa, 0xff555555, 0xff000000}; 65 | gbcMask = 0xff000000; 66 | transparentCutoff = cpu.gbcFeatures ? 32 : 0; 67 | 68 | tileImage = new Image[tileCount * colorCount]; 69 | imageBounds = new int[tileCount][]; 70 | tileReadState = new boolean[tileCount]; 71 | 72 | cpu.memory[4] = videoRam; 73 | 74 | tempPix = new int[tileWidth * tileHeight * 2]; 75 | transparentImage = Image.createRGBImage(tempPix, tileWidth, tileHeight, true); 76 | 77 | frameBufferImage = Image.createImage(scaledWidth, scaledHeight); 78 | graphics = frameBufferImage.getGraphics(); 79 | } 80 | 81 | public void UpdateLCDCFlags(int data) { 82 | if (doubledSprites != ((data & 0x04) != 0)) { 83 | invalidateAll(1); 84 | invalidateAll(2); 85 | } 86 | super.UpdateLCDCFlags(data); 87 | spritesEnabledThisFrame |= spritesEnabled; 88 | } 89 | 90 | /** Writes data to the specified video RAM address */ 91 | public final void addressWrite(int addr, byte data) { 92 | if (videoRam[addr] == data) 93 | return; 94 | 95 | if (addr < 0x1800) { // Bkg Tile data area 96 | int tileIndex = (addr >> 4) + tileOffset; 97 | 98 | if (tileReadState[tileIndex]) { 99 | int r = tileImage.length - tileCount + tileIndex; 100 | 101 | do { 102 | tileImage[r] = null; 103 | r -= tileCount; 104 | } while (r >= 0); 105 | imageBounds[tileIndex] = null; 106 | tileReadState[tileIndex] = false; 107 | } 108 | } 109 | videoRam[addr] = data; 110 | } 111 | 112 | /** Invalidate all tiles in the tile cache for the given palette */ 113 | public final void invalidateAll(int pal) { 114 | int start = pal * tileCount * 4; 115 | int stop = (pal + 1) * tileCount * 4; 116 | 117 | for (int r = start; r < stop; r++) { 118 | tileImage[r] = null; 119 | } 120 | } 121 | 122 | // called by notifyscanline, and draw 123 | private final void drawSprites(int priorityFlag) { 124 | if (!spritesEnabledThisFrame) 125 | return; 126 | 127 | int tileNumMask = 0xff; 128 | if (doubledSprites) { 129 | tileNumMask = 0xfe; // last bit treated as 0 130 | imageHeight = tileHeight * 2; 131 | } 132 | 133 | for (int i = 156; i >= 0; i -= 4) { 134 | int attributes = 0xff & cpu.oam[i + 3]; 135 | 136 | if ((attributes & 0x80) == priorityFlag) { 137 | int spriteX = (0xff & cpu.oam[i + 1]) - 8; 138 | int spriteY = (0xff & cpu.oam[i]) - 16; 139 | 140 | if (spriteX >= 160 || spriteY >= 144 || spriteY == -16) 141 | continue; 142 | 143 | int tileNum = (tileNumMask & cpu.oam[i + 2]); 144 | int spriteAttrib = (attributes >> 5) & 0x03; // flipx: from bit 0x20 to 0x01, flipy: from bit 0x40 to 0x02 145 | 146 | if (cpu.gbcFeatures) { 147 | spriteAttrib += 0x20 + ((attributes & 0x07) << 2); // palette 148 | tileNum += (384>>3) * (attributes & 0x08); // tile vram bank 149 | } else { 150 | // attributes 0x10: 0x00 = OBJ1 palette, 0x10 = OBJ2 palette 151 | // spriteAttrib: 0x04: OBJ1 palette, 0x08: OBJ2 palette 152 | spriteAttrib += 4 + ((attributes & 0x10) >> 2); 153 | } 154 | 155 | draw(tileNum, spriteX, spriteY, spriteAttrib); 156 | } 157 | } 158 | imageHeight = tileHeight; 159 | } 160 | 161 | /** This must be called by the CPU for each scanline drawn by the display hardware. It 162 | * handles drawing of the background layer 163 | */ 164 | public final void notifyScanline(int line) { 165 | if (skipping) { 166 | return; 167 | } 168 | 169 | if (line == 0) { 170 | if (!cpu.gbcFeatures) { 171 | graphics.setColor(gbPalette[0]); 172 | graphics.fillRect(0, 0, scaledWidth, scaledHeight); 173 | 174 | // for SimpleGraphics, sprite prio is enabled iff !gbcFeatures 175 | drawSprites(0x80); 176 | } 177 | 178 | windowStopLine = 144; 179 | winEnabledThisFrame = winEnabled; 180 | winEnabledThisLine = winEnabled; 181 | screenFilled = false; 182 | windowStopped = false; 183 | } 184 | 185 | if (winEnabledThisLine && !winEnabled) { 186 | windowStopLine = line & 0xff; 187 | winEnabledThisLine = false; 188 | } 189 | 190 | // Fix to screwed up status bars. Record which data area is selected on the 191 | // first line the window is to be displayed. Will work unless this is changed 192 | // after window is started 193 | // NOTE: Still no real support for hblank effects on window/sprites 194 | if (line == (cpu.registers[0x4A] & 0xff) + 1) { // Compare against WY reg 195 | savedWindowDataSelect = bgWindowDataSelect; 196 | } 197 | 198 | if (!bgEnabled) 199 | return; 200 | 201 | if (winEnabledThisLine && 202 | !windowStopped && 203 | (cpu.registers[0x4B] & 0xff) - 7 == 0 && // at left edge 204 | (cpu.registers[0x4A] & 0xff) <= line - 7) { // starts at or above top line of this row of tiles 205 | 206 | // the entire row is covered by the window, so it can be safely skipped 207 | int yPixelOfs = cpu.registers[0x42] & 7; 208 | int screenY = (line & 0xf8) - yPixelOfs; 209 | if (screenY >= 136) 210 | screenFilled = true; 211 | } else if ((((cpu.registers[0x42] + line) & 7) == 7) || (line == 144)) { 212 | int xPixelOfs = cpu.registers[0x43] & 7; 213 | int yPixelOfs = cpu.registers[0x42] & 7; 214 | int xTileOfs = (cpu.registers[0x43] & 0xff) >> 3; 215 | int yTileOfs = (cpu.registers[0x42] & 0xff) >> 3; 216 | 217 | int bgStartAddress = hiBgTileMapAddress ? 0x1c00 : 0x1800; 218 | int tileNum; 219 | 220 | int screenY = (line & 0xf8) - yPixelOfs; 221 | int screenX = -xPixelOfs; 222 | int screenRight = 160; 223 | 224 | int tileY = (line >> 3) + yTileOfs; 225 | int tileX = xTileOfs; 226 | 227 | int memStart = bgStartAddress + ((tileY & 0x1f) << 5); 228 | 229 | while (screenX < screenRight) { 230 | if (bgWindowDataSelect) { 231 | tileNum = videoRamBanks[0][memStart + (tileX & 0x1f)] & 0xff; 232 | } else { 233 | tileNum = 256 + videoRamBanks[0][memStart + (tileX & 0x1f)]; 234 | } 235 | 236 | int tileAttrib = 0; 237 | if (cpu.gbcFeatures) { 238 | int mapAttrib = videoRamBanks[1][memStart + (tileX & 0x1f)]; 239 | tileAttrib += (mapAttrib & 0x07) << 2; // palette 240 | tileAttrib += (mapAttrib >> 5) & 0x03; // mirroring 241 | tileNum += 384 * ((mapAttrib >> 3) & 0x01); // tile vram bank 242 | // bit 7 (priority) is ignored 243 | } 244 | tileX++; 245 | 246 | draw(tileNum, screenX, screenY, tileAttrib); 247 | 248 | screenX += 8; 249 | } 250 | 251 | if (screenY >= 136) 252 | screenFilled = true; 253 | } 254 | if (line == 143) { 255 | if (!screenFilled) 256 | notifyScanline(144); // fudge to update last part of screen when scrolling in y direction 257 | updateFrameBufferImage(); 258 | } 259 | } 260 | 261 | private final void updateFrameBufferImage() { 262 | if (lcdEnabled) { 263 | /* Draw window */ 264 | if (winEnabledThisFrame) { 265 | int wx, wy; 266 | 267 | int windowStartAddress = hiWinTileMapAddress ? 0x1c00 : 0x1800; 268 | 269 | if (windowStopped) { 270 | wx = windowStopX; 271 | wy = windowStopY; 272 | } else { 273 | wx = (cpu.registers[0x4B] & 0xff) - 7; 274 | wy = (cpu.registers[0x4A] & 0xff); 275 | } 276 | 277 | if (!cpu.gbcFeatures) { 278 | graphics.setColor(gbPalette[0]); 279 | int h = windowStopLine - wy; 280 | int w = 160 - wx; 281 | graphics.fillRect(((wx * tileWidth) >> 3), 282 | ((wy * tileHeight) >> 3), 283 | (w * tileWidth) >> 3, 284 | (h * tileHeight) >> 3); 285 | } 286 | 287 | int tileNum, tileAddress; 288 | int screenY = wy; 289 | 290 | int maxy = 19 - (wy >> 3); 291 | for (int y = 0; y < maxy; y++) { 292 | if (wy + y * 8 >= windowStopLine) 293 | break; 294 | 295 | tileAddress = windowStartAddress + (y * 32); 296 | 297 | for (int screenX = wx; screenX < 160; tileAddress++) { 298 | if (savedWindowDataSelect) { 299 | tileNum = videoRamBanks[0][tileAddress] & 0xff; 300 | } else { 301 | tileNum = 256 + videoRamBanks[0][tileAddress]; 302 | } 303 | 304 | int tileAttrib = 0; 305 | if (cpu.gbcFeatures) { 306 | int mapAttrib = videoRamBanks[1][tileAddress]; 307 | tileAttrib += (mapAttrib & 0x07) << 2; // palette 308 | tileAttrib += (mapAttrib >> 5) & 0x03; // mirroring 309 | tileNum += 384 * ((mapAttrib >> 3) & 0x01); // tile vram bank 310 | // bit 7 (priority) is ignored 311 | } 312 | 313 | draw(tileNum, screenX, screenY, tileAttrib); 314 | screenX += 8; 315 | } 316 | screenY += 8; 317 | } 318 | } 319 | 320 | if (cpu.gbcFeatures) { 321 | // for SimpleGraphics, sprite prio is enabled iff !gbcFeatures 322 | drawSprites(0x80); 323 | } 324 | drawSprites(0); 325 | } else { 326 | // lcd disabled 327 | graphics.setColor(cpu.gbcFeatures ? -1 : gbPalette[0]); 328 | graphics.fillRect(0, 0, scaledWidth, scaledHeight); 329 | } 330 | 331 | spritesEnabledThisFrame = spritesEnabled; 332 | } 333 | 334 | /** Create the image of a tile in the tile cache by reading the relevant data from video 335 | * memory 336 | */ 337 | private final Image updateImage(int tileIndex, int attribs) { 338 | int index = tileIndex + tileCount * attribs; 339 | 340 | boolean otherBank = (tileIndex >= 384); 341 | 342 | int offset = otherBank ? ((tileIndex - 384) << 4) : (tileIndex << 4); 343 | 344 | int paletteStart = attribs & 0xfc; 345 | 346 | byte[] vram = otherBank ? videoRamBanks[1] : videoRamBanks[0]; 347 | int[] palette = cpu.gbcFeatures ? gbcPalette : gbPalette; 348 | boolean transparentPossible = attribs >= transparentCutoff; 349 | 350 | int x2c, y2c, x2cstart; 351 | int croppedWidth, croppedHeight; 352 | int preshift = 0; 353 | 354 | if (!transparentPossible) { 355 | croppedWidth = tileWidth; 356 | croppedHeight = imageHeight; 357 | x2cstart = 4 - tileWidth; 358 | y2c = 4 - tileHeight; 359 | } else if (imageBounds[tileIndex] != null) { 360 | int[] bounds = imageBounds[tileIndex]; 361 | 362 | croppedWidth = bounds[4]; 363 | croppedHeight = bounds[5]; 364 | y2c = bounds[6]; 365 | x2cstart = bounds[7]; 366 | offset += bounds[8]; 367 | preshift = bounds[9]; 368 | } else { 369 | int[] bounds = new int[10]; 370 | 371 | bounds[0] = tileWidth; 372 | bounds[1] = imageHeight; 373 | 374 | int preoffset = offset; 375 | int mask = 0; 376 | y2c = 4 - tileHeight; 377 | for (int y = 0; y < imageHeight; y++) { 378 | int num = vram[preoffset] | vram[preoffset + 1]; 379 | if (num != 0) { 380 | bounds[1] = Math.min(bounds[1], y); 381 | bounds[3] = y + 1; 382 | } 383 | 384 | mask |= num; 385 | 386 | y2c += 8; 387 | while (y2c > 0) { 388 | y2c -= tileHeight; 389 | preoffset += 2; 390 | } 391 | } 392 | x2c = 4 - tileWidth; 393 | for (int x = tileWidth; --x >= 0; ) { 394 | if ((mask & 1) != 0) { 395 | bounds[0] = x; 396 | bounds[2] = Math.max(bounds[2], x + 1); 397 | } 398 | 399 | x2c += 8; 400 | while (x2c > 0) { 401 | x2c -= tileWidth; 402 | mask >>= 1; 403 | } 404 | } 405 | 406 | if (bounds[0] == tileWidth) { 407 | tileImage[index] = transparentImage; 408 | tileReadState[tileIndex] = true; 409 | return tileImage[index]; 410 | } 411 | 412 | imageBounds[tileIndex] = bounds; 413 | 414 | bounds[2] = tileWidth - bounds[2]; 415 | bounds[3] = imageHeight - bounds[3]; 416 | bounds[4] = croppedWidth = tileWidth - bounds[2] - bounds[0]; 417 | bounds[5] = croppedHeight = imageHeight - bounds[3] - bounds[1]; 418 | 419 | // precrop top/left 420 | x2cstart = 4 - tileWidth + (bounds[2] << 3); 421 | y2c = 4 - tileHeight + (bounds[1] << 3); 422 | while (y2c > 0) { 423 | y2c -= tileHeight; 424 | bounds[8] += 2; 425 | } 426 | while (x2cstart > 0) { 427 | x2cstart -= tileWidth; 428 | preshift += 2; 429 | } 430 | 431 | bounds[6] = y2c; 432 | bounds[7] = x2cstart; 433 | offset += bounds[8]; 434 | bounds[9] = preshift; 435 | } 436 | 437 | // apply flips 438 | int pixix = 0; 439 | int pixixdx = 1; 440 | int pixixdy = 0; 441 | 442 | if ((attribs & TILE_FLIPY) != 0) { 443 | pixixdy = -croppedWidth << 1; 444 | pixix = croppedWidth * (croppedHeight - 1); 445 | } 446 | 447 | if ((attribs & TILE_FLIPX) == 0) { 448 | pixixdx = -1; 449 | pixix += croppedWidth - 1; 450 | pixixdy += croppedWidth << 1; 451 | } 452 | 453 | int holemask = 0; 454 | for (int y = croppedHeight; --y >= 0; ) { 455 | int num = (weaveLookup[vram[offset] & 0xff] + (weaveLookup[vram[offset + 1] & 0xff] << 1)) >> preshift; 456 | 457 | x2c = x2cstart; 458 | for (int x = croppedWidth; --x >= 0; ) { 459 | tempPix[pixix] = palette[paletteStart + (num & 3)]; 460 | 461 | pixix += pixixdx; 462 | 463 | x2c += 8; 464 | while (x2c > 0) { 465 | x2c -= tileWidth; 466 | num >>= 2; 467 | } 468 | } 469 | pixix += pixixdy; 470 | 471 | y2c += 8; 472 | while (y2c > 0) { 473 | y2c -= tileHeight; 474 | holemask |= ~(vram[offset] | vram[offset + 1]); 475 | offset += 2; 476 | } 477 | } 478 | 479 | if (holemask >> (preshift >> 1) == 0) { 480 | transparentPossible = false; 481 | } 482 | 483 | tileImage[index] = Image.createRGBImage(tempPix, croppedWidth, croppedHeight, transparentPossible); 484 | 485 | tileReadState[tileIndex] = true; 486 | 487 | return tileImage[index]; 488 | } 489 | 490 | private final void draw(int tileIndex, int x, int y, int attribs) { 491 | int ix = tileIndex + tileCount * attribs; 492 | 493 | Image im = tileImage[ix]; 494 | 495 | if (im == null) { 496 | im = updateImage(tileIndex, attribs); 497 | } 498 | 499 | if (im == transparentImage) { 500 | return; 501 | } 502 | 503 | if (scale) { 504 | y = (y * tileHeight) >> 3; 505 | x = (x * tileWidth) >> 3; 506 | } 507 | 508 | if (attribs >= transparentCutoff) { 509 | int[] bounds = imageBounds[tileIndex]; 510 | graphics.drawImage(im, x + bounds[(attribs & 1) << 1], y + bounds[1 + (attribs & 2)], 20); 511 | } else { 512 | graphics.drawImage(im, x, y, 20); 513 | } 514 | } 515 | 516 | public final void stopWindowFromLine() { 517 | windowStopped = true; 518 | windowStopLine = (cpu.registers[0x44] & 0xff); 519 | windowStopX = (cpu.registers[0x4B] & 0xff) - 7; 520 | windowStopY = (cpu.registers[0x4A] & 0xff); 521 | } 522 | 523 | public void setScale(int screenWidth, int screenHeight) { 524 | int oldTW = tileWidth; 525 | int oldTH = tileHeight; 526 | 527 | tileWidth = screenWidth / 20; 528 | tileHeight = screenHeight / 18; 529 | 530 | if (MeBoy.keepProportions) 531 | if (tileWidth < tileHeight) 532 | tileHeight = tileWidth; 533 | else 534 | tileWidth = tileHeight; 535 | 536 | scale = tileWidth != 8 || tileHeight != 8; 537 | 538 | scaledWidth = tileWidth * 20; 539 | scaledHeight = tileHeight * 18; 540 | 541 | if (tileWidth != oldTW || tileHeight != oldTH) { 542 | // invalidate cache 543 | for (int r = 0; r < tileImage.length; r++) 544 | tileImage[r] = null; 545 | 546 | for (int r = 0; r < tileReadState.length; r++) { 547 | tileReadState[r] = false; 548 | imageBounds[r] = null; 549 | } 550 | } 551 | 552 | imageHeight = tileHeight; 553 | tempPix = new int[tileWidth * tileHeight * 2]; 554 | frameBufferImage = Image.createImage(scaledWidth, scaledHeight); 555 | graphics = frameBufferImage.getGraphics(); 556 | } 557 | } 558 | -------------------------------------------------------------------------------- /AdvancedGraphicsChip.java: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | MeBoy 4 | 5 | Copyright 2005-2009 Bjorn Carlin 6 | http://www.arktos.se/ 7 | 8 | Based on JavaBoy, COPYRIGHT (C) 2001 Neil Millstone and The Victoria 9 | University of Manchester. Bluetooth support based on code contributed by 10 | Martin Neumann. 11 | 12 | This program is free software; you can redistribute it and/or modify it 13 | under the terms of the GNU General Public License as published by the Free 14 | Software Foundation; either version 2 of the License, or (at your option) 15 | any later version. 16 | 17 | This program is distributed in the hope that it will be useful, but WITHOUT 18 | ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 19 | FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for 20 | more details. 21 | 22 | 23 | You should have received a copy of the GNU General Public License along with 24 | this program; if not, write to the Free Software Foundation, Inc., 59 Temple 25 | Place - Suite 330, Boston, MA 02111-1307, USA. 26 | 27 | */ 28 | 29 | import javax.microedition.lcdui.*; 30 | 31 | 32 | public class AdvancedGraphicsChip extends GraphicsChip { 33 | protected int[] frameBuffer; 34 | protected int[] scaledBuffer; 35 | 36 | // tiles & image cache 37 | protected int[] transparentImage = new int[0]; 38 | protected int[][] tileImage; 39 | protected boolean[] tileReadState; // true if there are any images to be invalidated 40 | 41 | protected int[] tempPix; 42 | 43 | protected int windowSourceLine; 44 | 45 | 46 | public AdvancedGraphicsChip(Dmgcpu d) { 47 | super(d); 48 | 49 | colors = new int[] { 0x80F8F8F8, 0x80A8A8A8, 0x80505050, 0x80000000}; 50 | gbcMask = 0x80000000; 51 | transparentCutoff = cpu.gbcFeatures ? 32 : 4; 52 | 53 | tileImage = new int[tileCount * colorCount][]; 54 | tileReadState = new boolean[tileCount]; 55 | tempPix = new int[8 * 8]; 56 | frameBuffer = new int[8 * 8 * 20*18]; 57 | } 58 | 59 | /** Writes data to the specified video RAM address */ 60 | public final void addressWrite(int addr, byte data) { 61 | if (videoRam[addr] == data) 62 | return; 63 | 64 | if (addr < 0x1800) { // Bkg Tile data area 65 | int tileIndex = (addr >> 4) + tileOffset; 66 | 67 | if (tileReadState[tileIndex]) { 68 | int r = tileImage.length - tileCount + tileIndex; 69 | 70 | do { 71 | tileImage[r] = null; 72 | r -= tileCount; 73 | } while (r >= 0); 74 | tileReadState[tileIndex] = false; 75 | } 76 | } 77 | videoRam[addr] = data; 78 | } 79 | 80 | /** Invalidate all tiles in the tile cache for the given palette */ 81 | public final void invalidateAll(int pal) { 82 | int start = pal * tileCount * 4; 83 | int stop = (pal + 1) * tileCount * 4; 84 | 85 | for (int r = start; r < stop; r++) { 86 | tileImage[r] = null; 87 | } 88 | } 89 | 90 | protected final void drawSpritesForLine(int line) { 91 | if (!spritesEnabled) 92 | return; 93 | 94 | int minSpriteY = doubledSprites ? line-15 : line -7; 95 | 96 | // either only do priorityFlag == 0 (all foreground), 97 | // or first 0x80 (background) and then 0 (foreground) 98 | int priorityFlag = spritePriorityEnabled ? 0x80 : 0; 99 | 100 | for (; priorityFlag >= 0; priorityFlag -= 0x80) { 101 | int oamIx = 159; 102 | 103 | while (oamIx >= 0) { 104 | int attributes = 0xff & cpu.oam[oamIx--]; 105 | 106 | if ((attributes & 0x80) == priorityFlag || !spritePriorityEnabled) { 107 | int tileNum = (0xff & cpu.oam[oamIx--]); 108 | int spriteX = (0xff & cpu.oam[oamIx--]) - 8; 109 | int spriteY = (0xff & cpu.oam[oamIx--]) - 16; 110 | 111 | int offset = line - spriteY; 112 | if (spriteX >= 160 || spriteY < minSpriteY || offset < 0) 113 | continue; 114 | 115 | if (doubledSprites) { 116 | tileNum &= 0xFE; 117 | } 118 | 119 | int spriteAttrib = (attributes >> 5) & 0x03; // flipx: from bit 0x20 to 0x01, flipy: from bit 0x40 to 0x02 120 | 121 | if (cpu.gbcFeatures) { 122 | spriteAttrib += 0x20 + ((attributes & 0x07) << 2); // palette 123 | tileNum += (384>>3) * (attributes & 0x08); // tile vram bank 124 | } else { 125 | // attributes 0x10: 0x00 = OBJ1 palette, 0x10 = OBJ2 palette 126 | // spriteAttrib: 0x04: OBJ1 palette, 0x08: OBJ2 palette 127 | spriteAttrib += 4 + ((attributes & 0x10) >> 2); 128 | } 129 | 130 | if (priorityFlag == 0x80) { 131 | // background 132 | if (doubledSprites) { 133 | if ((spriteAttrib & TILE_FLIPY) != 0) { 134 | drawPartBgSprite((tileNum | 1) - (offset >> 3), spriteX, line, offset & 7, spriteAttrib); 135 | } else { 136 | drawPartBgSprite((tileNum & -2) + (offset >> 3), spriteX, line, offset & 7, spriteAttrib); 137 | } 138 | } else { 139 | drawPartBgSprite(tileNum, spriteX, line, offset, spriteAttrib); 140 | } 141 | } else { 142 | // foreground 143 | if (doubledSprites) { 144 | if ((spriteAttrib & TILE_FLIPY) != 0) { 145 | drawPartFgSprite((tileNum | 1) - (offset >> 3), spriteX, line, offset & 7, spriteAttrib); 146 | } else { 147 | drawPartFgSprite((tileNum & -2) + (offset >> 3), spriteX, line, offset & 7, spriteAttrib); 148 | } 149 | } else { 150 | drawPartFgSprite(tileNum, spriteX, line, offset, spriteAttrib); 151 | } 152 | } 153 | } else { 154 | oamIx -= 3; 155 | } 156 | } 157 | } 158 | } 159 | 160 | protected boolean drawBackgroundForLine(int line, int windowLeft, int priority) { 161 | boolean skippedTile = false; 162 | 163 | int sourceY = line + (cpu.registers[0x42] & 0xff); 164 | int sourceImageLine = sourceY & 7; 165 | 166 | int tileNum; 167 | int tileX = (cpu.registers[0x43] & 0xff) >> 3; 168 | int memStart = (hiBgTileMapAddress ? 0x1c00 : 0x1800) + ((sourceY & 0xf8) << 2); 169 | 170 | int screenX = -(cpu.registers[0x43] & 7); 171 | for (; screenX < windowLeft; tileX++, screenX += 8) { 172 | if (bgWindowDataSelect) { 173 | tileNum = videoRamBanks[0][memStart + (tileX & 0x1f)] & 0xff; 174 | } else { 175 | tileNum = 256 + videoRamBanks[0][memStart + (tileX & 0x1f)]; 176 | } 177 | 178 | int tileAttrib = 0; 179 | 180 | if (cpu.gbcFeatures) { 181 | int mapAttrib = videoRamBanks[1][memStart + (tileX & 0x1f)]; 182 | 183 | if ((mapAttrib & 0x80) != priority) { 184 | skippedTile = true; 185 | continue; 186 | } 187 | 188 | tileAttrib += (mapAttrib & 0x07) << 2; // palette 189 | tileAttrib += (mapAttrib >> 5) & 0x03; // mirroring 190 | tileNum += 384 * ((mapAttrib >> 3) & 0x01); // tile vram bank 191 | } 192 | 193 | drawPartCopy(tileNum, screenX, line, sourceImageLine, tileAttrib); 194 | } 195 | 196 | if (windowLeft < 160) { 197 | // window! 198 | int windowStartAddress = hiWinTileMapAddress ? 0x1c00 : 0x1800; 199 | 200 | int tileAddress; 201 | 202 | int windowSourceTileY = windowSourceLine >> 3; 203 | int windowSourceTileLine = windowSourceLine & 7; 204 | 205 | tileAddress = windowStartAddress + (windowSourceTileY * 32); 206 | 207 | for (screenX = windowLeft; screenX < 160; tileAddress++, screenX += 8) { 208 | if (bgWindowDataSelect) { 209 | tileNum = videoRamBanks[0][tileAddress] & 0xff; 210 | } else { 211 | tileNum = 256 + videoRamBanks[0][tileAddress]; 212 | } 213 | 214 | int tileAttrib = 0; 215 | 216 | if (cpu.gbcFeatures) { 217 | int mapAttrib = videoRamBanks[1][tileAddress]; 218 | 219 | if ((mapAttrib & 0x80) != priority) { 220 | skippedTile = true; 221 | continue; 222 | } 223 | 224 | tileAttrib += (mapAttrib & 0x07) << 2; // palette 225 | tileAttrib += (mapAttrib >> 5) & 0x03; // mirroring 226 | tileNum += 384 * ((mapAttrib >> 3) & 0x01); // tile vram bank 227 | } 228 | 229 | drawPartCopy(tileNum, screenX, line, windowSourceTileLine, tileAttrib); 230 | } 231 | } 232 | return skippedTile; 233 | } 234 | 235 | /** This must be called by the CPU for each scanline drawn by the display hardware. 236 | */ 237 | public final void notifyScanline(int line) { 238 | if (skipping || line >= 144) { 239 | return; 240 | } 241 | 242 | if (line == 0) { 243 | windowSourceLine = 0; 244 | } 245 | 246 | // determine the left edge of the window (160 if window is inactive) 247 | int windowLeft; 248 | if (winEnabled && (cpu.registers[0x4A] & 0xff) <= line) { 249 | windowLeft = (cpu.registers[0x4B] & 0xff) - 7; 250 | if (windowLeft > 160) 251 | windowLeft = 160; 252 | } else 253 | windowLeft = 160; 254 | 255 | // step 1: background+window 256 | boolean skippedAnything = drawBackgroundForLine(line, windowLeft, 0); 257 | 258 | // At this point, the high (alpha) byte in the frameBuffer is 0xff for colors 1,2,3 and 259 | // 0x00 for color 0. Foreground sprites draw on all colors, background sprites draw on 260 | // top of color 0 only. 261 | 262 | // step 2: sprites 263 | drawSpritesForLine(line); 264 | 265 | // step 3: prio tiles+window 266 | if (skippedAnything) { 267 | drawBackgroundForLine(line, windowLeft, 0x80); 268 | } 269 | 270 | if (windowLeft < 160) 271 | windowSourceLine++; 272 | 273 | // step 4: to buffer (only last line) 274 | if (line == 143) { 275 | updateFrameBufferImage(); 276 | } 277 | } 278 | 279 | protected final void updateFrameBufferImage() { 280 | if (!lcdEnabled) { 281 | int[] buffer = scale ? scaledBuffer : frameBuffer; 282 | for (int i = 0; i < buffer.length; i++) 283 | buffer[i] = -1; 284 | frameBufferImage = Image.createRGBImage(buffer, scaledWidth, scaledHeight, false); 285 | return; 286 | } 287 | 288 | if (scale) { 289 | if (MeBoy.scalingMode == 0) { 290 | // nearest neighbor horizontal and vertical 291 | 292 | int deltaX = (((160 << 10) - 1) / scaledWidth); 293 | int deltaY = (((144 << 10) - 1) / scaledHeight); 294 | 295 | int sy = 0; 296 | int ry = deltaY >> 1; 297 | int dst = 0; 298 | int dstStop = scaledWidth; 299 | 300 | for (int y = 0; y < scaledHeight; y++) { 301 | int rx = deltaX >> 1; 302 | int src = sy * 160; 303 | 304 | while (dst < dstStop) { 305 | scaledBuffer[dst++] = frameBuffer[src]; 306 | 307 | rx = (rx & 1023) + deltaX; 308 | src += rx >> 10; 309 | } 310 | 311 | ry = (ry & 1023) + deltaY; 312 | sy += ry >> 10; 313 | dstStop += scaledWidth; 314 | } 315 | } else if (MeBoy.scalingMode == 1) { 316 | // linear horizontal, nearest neighbor vertical 317 | 318 | int deltaX = (((159 << 10) - 1) / scaledWidth); 319 | int deltaY = (((144 << 10) - 1) / scaledHeight); 320 | 321 | int sy = 0; 322 | int ry = deltaY >> 1; 323 | int dst = 0; 324 | int dstStop = scaledWidth; 325 | 326 | for (int y = 0; y < scaledHeight; y++) { 327 | int rx = deltaX >> 1; 328 | int src = sy * 160; 329 | 330 | while (dst < dstStop) { 331 | int rightPart = rx >> 7; 332 | int leftPart = 8 - rightPart; 333 | 334 | scaledBuffer[dst++] = (leftPart * frameBuffer[src] + rightPart * frameBuffer[src+1]) >> 3; 335 | 336 | rx += deltaX; 337 | src += rx >> 10; 338 | rx &= 1023; 339 | } 340 | 341 | ry = (ry & 1023) + deltaY; 342 | sy += ry >> 10; 343 | dstStop += scaledWidth; 344 | } 345 | } else if (MeBoy.scalingMode == 2) { 346 | // nearest neighbor horizontal, linear vertical 347 | 348 | int deltaX = (((160 << 10) - 1) / scaledWidth); 349 | int deltaY = (((143 << 10) - 1) / scaledHeight); 350 | 351 | int sy = 0; 352 | int ry = deltaY >> 1; 353 | int dst = 0; 354 | int dstStop = scaledWidth; 355 | 356 | for (int y = 0; y < scaledHeight; y++) { 357 | int rx = deltaX >> 1; 358 | int src = sy * 160; 359 | 360 | while (dst < dstStop) { 361 | int bottomPart = ry >> 7; 362 | int topPart = 8 - bottomPart; 363 | 364 | scaledBuffer[dst++] = (topPart * frameBuffer[src] + bottomPart * frameBuffer[src+160]) >> 3; 365 | 366 | rx += deltaX; 367 | src += rx >> 10; 368 | rx &= 1023; 369 | } 370 | 371 | ry += deltaY; 372 | sy += ry >> 10; 373 | ry &= 1023; 374 | dstStop += scaledWidth; 375 | } 376 | } else if (MeBoy.scalingMode == 3) { 377 | // linear horizontal and vertical 378 | 379 | int deltaX = (((159 << 10) - 1) / scaledWidth); 380 | int deltaY = (((143 << 10) - 1) / scaledHeight); 381 | 382 | int sy = 0; 383 | int ry = deltaY >> 1; 384 | int dst = 0; 385 | int dstStop = scaledWidth; 386 | 387 | for (int y = 0; y < scaledHeight; y++) { 388 | int bottomPart = ry >> 7; // 0-7 389 | int topPart = 8 - bottomPart; // 1-8 390 | 391 | int rx = deltaX >> 1; 392 | int src = sy * 160; 393 | 394 | while (dst < dstStop) { 395 | int topRightPart = (rx * topPart) >> 10; 396 | int topLeftPart = topPart - topRightPart; 397 | 398 | int bottomRightPart = (rx * bottomPart) >> 10; 399 | int bottomLeftPart = bottomPart - bottomRightPart; 400 | 401 | scaledBuffer[dst++] = (topLeftPart * frameBuffer[src] + topRightPart * frameBuffer[src+1] + 402 | bottomLeftPart * frameBuffer[src+160] + bottomRightPart * frameBuffer[src+161]) >> 3; 403 | 404 | rx += deltaX; 405 | src += rx >> 10; 406 | rx &= 1023; 407 | } 408 | 409 | ry += deltaY; 410 | sy += ry >> 10; 411 | ry &= 1023; 412 | dstStop += scaledWidth; 413 | } 414 | } 415 | 416 | frameBufferImage = Image.createRGBImage(scaledBuffer, scaledWidth, scaledHeight, false); 417 | } else { 418 | frameBufferImage = Image.createRGBImage(frameBuffer, 160, 144, false); 419 | } 420 | } 421 | 422 | /** Create the image of a tile in the tile cache by reading the relevant data from video 423 | * memory 424 | */ 425 | protected final int[] updateImage(int tileIndex, int attribs) { 426 | int index = tileIndex + tileCount * attribs; 427 | 428 | boolean otherBank = (tileIndex >= 384); 429 | 430 | int offset = otherBank ? ((tileIndex - 384) << 4) : (tileIndex << 4); 431 | 432 | int paletteStart = attribs & 0xfc; 433 | 434 | byte[] vram = otherBank ? videoRamBanks[1] : videoRamBanks[0]; 435 | int[] palette = cpu.gbcFeatures ? gbcPalette : gbPalette; 436 | boolean transparent = attribs >= transparentCutoff; 437 | 438 | int pixix = 0; 439 | int pixixdx = 1; 440 | int pixixdy = 0; 441 | 442 | if ((attribs & TILE_FLIPY) != 0) { 443 | pixixdy = -2*8; 444 | pixix = 8 * (8 - 1); 445 | } 446 | if ((attribs & TILE_FLIPX) == 0) { 447 | pixixdx = -1; 448 | pixix += 8 - 1; 449 | pixixdy += 8 * 2; 450 | } 451 | 452 | for (int y = 8; --y >= 0; ) { 453 | int num = weaveLookup[vram[offset++] & 0xff] + 454 | (weaveLookup[vram[offset++] & 0xff] << 1); 455 | if (num != 0) 456 | transparent = false; 457 | 458 | for (int x = 8; --x >= 0; ) { 459 | tempPix[pixix] = palette[paletteStart + (num & 3)]; 460 | pixix += pixixdx; 461 | 462 | num >>= 2; 463 | } 464 | pixix += pixixdy; 465 | } 466 | 467 | if (transparent) { 468 | tileImage[index] = transparentImage; 469 | } else { 470 | tileImage[index] = tempPix; 471 | tempPix = new int[8 * 8]; 472 | } 473 | 474 | tileReadState[tileIndex] = true; 475 | 476 | return tileImage[index]; 477 | } 478 | 479 | // draws one scanline of the block 480 | // ignores alpha byte, just copies pixels 481 | protected final void drawPartCopy(int tileIndex, int x, int y, int sourceLine, int attribs) { 482 | int ix = tileIndex + tileCount * attribs; 483 | int[] im = tileImage[ix]; 484 | 485 | if (im == null) { 486 | im = updateImage(tileIndex, attribs); 487 | } 488 | 489 | int dst = x + y * 160; 490 | int src = sourceLine * 8; 491 | int dstEnd = (x + 8 > 160) ? ((y+1) * 160) : (dst + 8); 492 | 493 | if (x < 0) { // adjust left 494 | dst -= x; 495 | src -= x; 496 | } 497 | 498 | while (dst < dstEnd) 499 | frameBuffer[dst++] = im[src++]; 500 | } 501 | 502 | // draws one scanline of the block 503 | // overwrites background when source pixel is opaque 504 | protected final void drawPartFgSprite(int tileIndex, int x, int y, int sourceLine, int attribs) { 505 | int ix = tileIndex + tileCount * attribs; 506 | int[] im = tileImage[ix]; 507 | 508 | if (im == null) { 509 | im = updateImage(tileIndex, attribs); 510 | } 511 | 512 | if (im == transparentImage) { 513 | return; 514 | } 515 | 516 | int dst = x + y * 160; 517 | int src = sourceLine * 8; 518 | int dstEnd = (x + 8 > 160) ? ((y+1) * 160) : (dst + 8); 519 | 520 | if (x < 0) { // adjust left 521 | dst -= x; 522 | src -= x; 523 | } 524 | 525 | while (dst < dstEnd) { 526 | if (im[src] < 0) // fast check for 0xff in high byte 527 | frameBuffer[dst] = im[src]; 528 | 529 | dst++; 530 | src++; 531 | } 532 | } 533 | 534 | // draws one scanline of the block 535 | // overwrites background when source pixel is opaque and background is transparent 536 | protected final void drawPartBgSprite(int tileIndex, int x, int y, int sourceLine, int attribs) { 537 | int ix = tileIndex + tileCount * attribs; 538 | int[] im = tileImage[ix]; 539 | 540 | if (im == null) { 541 | im = updateImage(tileIndex, attribs); 542 | } 543 | 544 | if (im == transparentImage) { 545 | return; 546 | } 547 | 548 | int dst = x + y * 160; 549 | int src = sourceLine * 8; 550 | int dstEnd = (x + 8 > 160) ? ((y+1) * 160) : (dst + 8); 551 | 552 | if (x < 0) { // adjust left 553 | dst -= x; 554 | src -= x; 555 | } 556 | 557 | while (dst < dstEnd) { 558 | if (im[src] < 0 && frameBuffer[dst] >= 0) // fast check for 0xff and 0x00 in high byte 559 | frameBuffer[dst] = im[src]; 560 | 561 | dst++; 562 | src++; 563 | } 564 | } 565 | 566 | public void setScale(int screenWidth, int screenHeight) { 567 | if (MeBoy.keepProportions) { 568 | if (screenWidth * 18 > screenHeight * 20) 569 | screenWidth = screenHeight * 20 / 18; 570 | else 571 | screenHeight = screenWidth * 18 / 20; 572 | } 573 | 574 | if (screenWidth == scaledWidth && screenHeight == scaledHeight) 575 | return; 576 | 577 | scale = screenWidth != 160 || screenHeight != 144; 578 | 579 | scaledWidth = screenWidth; 580 | scaledHeight = screenHeight; 581 | 582 | if (scale) { 583 | scaledBuffer = new int[scaledWidth * scaledHeight]; 584 | } else { 585 | scaledBuffer = null; 586 | } 587 | } 588 | 589 | public void setGBCPalette(int index, int data) { 590 | super.setGBCPalette(index, data); 591 | 592 | if ((index & 0x6) == 0) { 593 | gbcPalette[index >> 1] &= 0x00ffffff; 594 | } 595 | } 596 | } 597 | -------------------------------------------------------------------------------- /GBCanvas.java: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | MeBoy 4 | 5 | Copyright 2005-2009 Bjorn Carlin 6 | http://www.arktos.se/ 7 | 8 | Based on JavaBoy, COPYRIGHT (C) 2001 Neil Millstone and The Victoria 9 | University of Manchester. Bluetooth support based on code contributed by 10 | Martin Neumann. 11 | 12 | This program is free software; you can redistribute it and/or modify it 13 | under the terms of the GNU General Public License as published by the Free 14 | Software Foundation; either version 2 of the License, or (at your option) 15 | any later version. 16 | 17 | This program is distributed in the hope that it will be useful, but WITHOUT 18 | ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 19 | FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for 20 | more details. 21 | 22 | 23 | You should have received a copy of the GNU General Public License along with 24 | this program; if not, write to the Free Software Foundation, Inc., 59 Temple 25 | Place - Suite 330, Boston, MA 02111-1307, USA. 26 | 27 | */ 28 | 29 | import javax.microedition.lcdui.*; 30 | import javax.microedition.rms.*; 31 | 32 | 33 | public class GBCanvas extends Canvas implements CommandListener { 34 | public MeBoy parent; 35 | private Dmgcpu cpu; 36 | private int w, h, l, t; 37 | private int sw, sh, trans; // translation, and screen size (on screen, i.e. scaled and rotated if applicable) 38 | private int ssw, ssh; // source screen width and height (possibly scaled, not rotated) 39 | private int clipHeight; // maybe including fps 40 | private int[] previousTime = new int[16]; 41 | private int previousTimeIx; 42 | 43 | private Command pauseCommand = new Command(MeBoy.literal[30], Command.SCREEN, 0); 44 | private Command resumeCommand = new Command(MeBoy.literal[31], Command.SCREEN, 0); 45 | private Command saveCommand = new Command(MeBoy.literal[32], Command.SCREEN, 1); 46 | private Command showFpsCommand = new Command(MeBoy.literal[33], Command.SCREEN, 3); 47 | private Command fullScreenCommand = new Command(MeBoy.literal[34], Command.SCREEN, 4); 48 | private Command setButtonsCommand = new Command(MeBoy.literal[35], Command.SCREEN, 5); 49 | private Command exitCommand; 50 | 51 | private static int[] key = new int[] {KEY_NUM6, KEY_NUM4, KEY_NUM2, KEY_NUM8, KEY_NUM7, KEY_NUM9, KEY_POUND, KEY_STAR}; 52 | private String[] keyName = { 53 | MeBoy.literal[38], 54 | MeBoy.literal[39], 55 | MeBoy.literal[40], 56 | MeBoy.literal[41], 57 | MeBoy.literal[42], 58 | MeBoy.literal[43], 59 | MeBoy.literal[44], 60 | MeBoy.literal[45]}; 61 | private int keySetCounter; 62 | private boolean settingKeys; 63 | private boolean paused; 64 | 65 | private String cartDisplayName; 66 | private String cartID; 67 | private String suspendName; 68 | 69 | private Thread cpuThread; 70 | 71 | 72 | // Common constructor 73 | private GBCanvas(MeBoy p, String cartID, String cartDisplayName) { 74 | parent = p; 75 | this.cartID = cartID; 76 | this.cartDisplayName = cartDisplayName; 77 | 78 | exitCommand = new Command(MeBoy.literal[36] + " " + cartDisplayName, Command.SCREEN, 6); 79 | setCommandListener(this); 80 | 81 | updateCommands(); 82 | 83 | setFullScreenMode(MeBoy.fullScreen); 84 | } 85 | 86 | // Constructor for loading suspended games 87 | public GBCanvas(String cartID, MeBoy p, String cartDisplayName, 88 | String suspendName, byte[] suspendState) { 89 | this(p, cartID, cartDisplayName); 90 | 91 | this.suspendName = suspendName; 92 | 93 | cpu = new Dmgcpu(cartID, this, suspendState); 94 | setDimensions(); 95 | 96 | cpuThread = new Thread(cpu); 97 | cpuThread.start(); 98 | } 99 | 100 | // Constructor for new games 101 | public GBCanvas(String cartID, MeBoy p, String cartDisplayName) { 102 | this(p, cartID, cartDisplayName); 103 | 104 | cpu = new Dmgcpu(cartID, this); 105 | 106 | if (cpu.hasBattery()) 107 | loadCartRam(); 108 | 109 | setDimensions(); 110 | 111 | cpuThread = new Thread(cpu); 112 | cpuThread.start(); 113 | } 114 | 115 | private void updateCommands() { 116 | // remove and add all commands, to prevent pause/resume to end up last 117 | removeCommand(pauseCommand); 118 | removeCommand(resumeCommand); 119 | removeCommand(saveCommand); 120 | removeCommand(showFpsCommand); 121 | removeCommand(fullScreenCommand); 122 | removeCommand(setButtonsCommand); 123 | removeCommand(exitCommand); 124 | 125 | if (paused) 126 | addCommand(resumeCommand); 127 | else 128 | addCommand(pauseCommand); 129 | 130 | addCommand(saveCommand); 131 | addCommand(showFpsCommand); 132 | addCommand(fullScreenCommand); 133 | addCommand(setButtonsCommand); 134 | addCommand(exitCommand); 135 | } 136 | 137 | public void setDimensions() { 138 | w = getWidth(); 139 | h = getHeight(); 140 | 141 | int[] transs = new int[] {0, 5, 3, 6}; 142 | trans = transs[MeBoy.rotations]; 143 | boolean rotate = (MeBoy.rotations & 1) != 0; 144 | 145 | if (MeBoy.enableScaling) { 146 | int deltah = MeBoy.showFps ? -16: 0; 147 | cpu.setScale(rotate ? (h+deltah) : w, rotate ? w : (h+deltah)); 148 | } 149 | ssw = sw = cpu.graphicsChip.scaledWidth; 150 | clipHeight = ssh = sh = cpu.graphicsChip.scaledHeight; 151 | 152 | if (rotate) { 153 | sw = ssh; 154 | clipHeight = sh = ssw; 155 | } 156 | 157 | l = (w - sw) / 2; 158 | t = (h - sh) / 2; 159 | if (MeBoy.showFps) { 160 | t -= 8; 161 | clipHeight += 16; 162 | } 163 | 164 | if (l < 0) 165 | l = 0; 166 | if (t < 0) 167 | t = 0; 168 | 169 | //cpu.setTranslation(trans == 0 ? l : 0, trans == 0 ? t : 0); 170 | } 171 | 172 | public void keyReleased(int keyCode) { 173 | for (int i = 0; i < 8; i++) { 174 | if (keyCode == key[i]) { 175 | cpu.buttonUp(i); 176 | } 177 | } 178 | } 179 | 180 | public void keyPressed(int keyCode) { 181 | if (settingKeys) { 182 | key[keySetCounter++] = keyCode; 183 | if (keySetCounter == 8) { 184 | writeSettings(); 185 | settingKeys = false; 186 | } 187 | repaint(); 188 | return; 189 | } 190 | 191 | for (int i = 0; i < 8; i++) { 192 | if (keyCode == key[i]) { 193 | cpu.buttonDown(i); 194 | } 195 | } 196 | } 197 | 198 | public void commandAction(Command c, Displayable s) { 199 | try { 200 | String label = c.getLabel(); 201 | if (label.startsWith(MeBoy.literal[36])) { 202 | if (cpu.hasBattery()) 203 | saveCartRam(); 204 | 205 | parent.unloadCart(); 206 | Runtime.getRuntime().gc(); 207 | } else if (label == MeBoy.literal[30]) { 208 | pause(); 209 | } else if (label == MeBoy.literal[31] && !settingKeys) { 210 | paused = false; 211 | updateCommands(); 212 | 213 | cpuThread = new Thread(cpu); 214 | cpuThread.start(); 215 | } else if (label == MeBoy.literal[33]) { 216 | MeBoy.showFps = !MeBoy.showFps; 217 | setDimensions(); 218 | } else if (label == MeBoy.literal[35] && !settingKeys) { 219 | pause(); 220 | settingKeys = true; 221 | keySetCounter = 0; 222 | } else if (label == MeBoy.literal[32] && !settingKeys) { 223 | if (!cpu.isTerminated()) { 224 | cpu.terminate(); 225 | while(cpuThread.isAlive()) { 226 | Thread.yield(); 227 | } 228 | suspend(); 229 | 230 | cpuThread = new Thread(cpu); 231 | cpuThread.start(); 232 | } else { 233 | suspend(); 234 | } 235 | } else if (label == MeBoy.literal[34] && !settingKeys) { 236 | MeBoy.fullScreen = !MeBoy.fullScreen; 237 | setFullScreenMode(MeBoy.fullScreen); 238 | 239 | setDimensions(); 240 | } 241 | } catch (Throwable th) { 242 | MeBoy.log(th.toString()); 243 | MeBoy.showLog(); 244 | if (MeBoy.debug) 245 | th.printStackTrace(); 246 | } 247 | repaint(); 248 | } 249 | 250 | public final void pause() { 251 | if (cpuThread == null) 252 | return; 253 | 254 | paused = true; 255 | updateCommands(); 256 | 257 | cpu.terminate(); 258 | while(cpuThread.isAlive()) { 259 | Thread.yield(); 260 | } 261 | cpuThread = null; 262 | } 263 | 264 | public final void redrawSmall() { 265 | repaint(l, t, sw, clipHeight); 266 | } 267 | 268 | public final void paintFps(Graphics g) { 269 | g.setClip(l, t+sh, sw, 16); 270 | g.setColor(0x999999); 271 | g.fillRect(l, t+sh, sw, 16); 272 | g.setColor(0); 273 | 274 | int now = (int) System.currentTimeMillis(); 275 | // calculate moving-average fps 276 | // 17 ms * 60 fps * 2*16 seconds = 32640 ms 277 | int estfps = ((32640 + now - previousTime[previousTimeIx]) / (now - previousTime[previousTimeIx])) >> 1; 278 | previousTime[previousTimeIx] = now; 279 | previousTimeIx = (previousTimeIx + 1) & 0x0F; 280 | 281 | g.drawString(estfps + " fps * " + (cpu.getLastSkipCount() + 1), l+1, t+sh, 20); 282 | } 283 | 284 | public final void paint(Graphics g) { 285 | if (cpu == null) 286 | return; 287 | 288 | if (settingKeys) { 289 | g.setColor(0x446688); 290 | g.fillRect(0, 0, w, h); 291 | g.setColor(-1); 292 | int cut = MeBoy.literal[37].indexOf(' ', MeBoy.literal[37].length() * 2/5); 293 | if (cut == -1) 294 | cut = 0; 295 | g.drawString(MeBoy.literal[37].substring(0, cut), w/2, h/2-18, 65); 296 | g.drawString(MeBoy.literal[37].substring(cut + 1), w/2, h/2, 65); 297 | g.drawString(keyName[keySetCounter], w/2, h/2+18, 65); 298 | return; 299 | } 300 | 301 | if (g.getClipWidth() != sw || g.getClipHeight() != clipHeight) { 302 | g.setColor(0x666666); 303 | g.fillRect(0, 0, w, h); 304 | } 305 | 306 | if (MeBoy.showFps) { 307 | paintFps(g); 308 | } 309 | 310 | g.setClip(l, t, sw, sh); 311 | if (cpu.graphicsChip.frameBufferImage == null) { 312 | g.setColor(0xaaaaaa); 313 | g.fillRect(l, t, sw, sh); 314 | } else if (trans == 0) { 315 | g.drawImage(cpu.graphicsChip.frameBufferImage, l, t, 20); 316 | } else { 317 | g.drawRegion(cpu.graphicsChip.frameBufferImage, 0, 0, ssw, ssh, trans, l, t, 20); 318 | } 319 | cpu.graphicsChip.notifyRepainted(); 320 | 321 | if (paused) { 322 | g.setColor(0); 323 | g.drawString(MeBoy.literal[30], w/2-1, h/2, 65); 324 | g.drawString(MeBoy.literal[30], w/2, h/2-1, 65); 325 | g.drawString(MeBoy.literal[30], w/2+1, h/2, 65); 326 | g.drawString(MeBoy.literal[30], w/2, h/2+1, 65); 327 | g.setColor(0xffffff); 328 | g.drawString(MeBoy.literal[30], w/2, h/2, 65); 329 | } 330 | } 331 | 332 | public static final void setInt(byte[] b, int i, int v) { 333 | b[i++] = (byte) (v >> 24); 334 | b[i++] = (byte) (v >> 16); 335 | b[i++] = (byte) (v >> 8); 336 | b[i++] = (byte) (v); 337 | } 338 | 339 | public static final int getInt(byte[] b, int i) { 340 | int r = b[i++] & 0xFF; 341 | r = (r << 8) + (b[i++] & 0xFF); 342 | r = (r << 8) + (b[i++] & 0xFF); 343 | return (r << 8) + (b[i++] & 0xFF); 344 | } 345 | 346 | public static final void writeSettings() { 347 | try { 348 | RecordStore rs = RecordStore.openRecordStore("set", true); 349 | 350 | int bLength = 55; 351 | for (int i = 0; i < MeBoy.suspendName10.length; i++) 352 | bLength += 1 + MeBoy.suspendName10[i].length(); 353 | for (int i = 0; i < MeBoy.suspendName20.length; i++) 354 | bLength += 1 + MeBoy.suspendName20[i].length() * 2; 355 | bLength++; 356 | 357 | byte[] b = new byte[bLength]; 358 | 359 | for (int i = 0; i < 8; i++) 360 | setInt(b, i * 4, key[i]); 361 | setInt(b, 32, MeBoy.maxFrameSkip); 362 | setInt(b, 36, MeBoy.rotations); 363 | setInt(b, 40, MeBoy.lazyLoadingThreshold); 364 | setInt(b, 44, MeBoy.suspendCounter); 365 | setInt(b, 48, MeBoy.suspendName10.length); 366 | 367 | int index = 52; 368 | for (int i = 0; i < MeBoy.suspendName10.length; i++) { 369 | String s = MeBoy.suspendName10[i]; 370 | b[index++] = (byte) (s.length()); 371 | for (int j = 0; j < s.length(); j++) 372 | b[index++] = (byte) s.charAt(j); 373 | } 374 | 375 | b[index++] = (byte) ((MeBoy.enableScaling ? 1 : 0) + (MeBoy.keepProportions ? 2 : 0) + (MeBoy.fullScreen 376 | ? 4 : 0) + (MeBoy.disableColor ? 8 : 0) + (MeBoy.enableSound ? 32 : 0) + (MeBoy.advancedSound 377 | ? 64 : 0) + (MeBoy.advancedGraphics ? 128 : 0)); 378 | b[index++] = (byte) MeBoy.language; 379 | b[index++] = (byte) ((MeBoy.showFps ? 1 : 0) + (MeBoy.showLogItem ? 2 : 0)); 380 | 381 | b[index++] = (byte) MeBoy.suspendName20.length; 382 | for (int i = 0; i < MeBoy.suspendName20.length; i++) { 383 | // Manual UTF-16, since String.getBytes(encoding) is not well-supported: 384 | char[] chars = MeBoy.suspendName20[i].toCharArray(); 385 | b[index++] = (byte) (chars.length); 386 | 387 | for (int j = 0; j < chars.length; j++) { 388 | b[index++] = (byte) (chars[j] >> 8); 389 | b[index++] = (byte) (chars[j]); 390 | } 391 | } 392 | 393 | if (rs.getNumRecords() == 0) { 394 | rs.addRecord(b, 0, bLength); 395 | } else { 396 | rs.setRecord(1, b, 0, bLength); 397 | } 398 | rs.closeRecordStore(); 399 | } catch (Exception e) { 400 | if (MeBoy.debug) { 401 | e.printStackTrace(); 402 | } 403 | MeBoy.log(e.toString()); 404 | } 405 | } 406 | 407 | public static final void readSettings() { 408 | try { 409 | MeBoy.suspendName10 = new String[0]; 410 | MeBoy.suspendName20 = new String[0]; 411 | 412 | RecordStore rs = RecordStore.openRecordStore("set", true); 413 | if (rs.getNumRecords() > 0) { 414 | byte[] b = rs.getRecord(1); 415 | 416 | for (int i = 0; i < 8; i++) 417 | key[i] = getInt(b, i * 4); 418 | if (b.length >= 36) 419 | MeBoy.maxFrameSkip = getInt(b, 32); 420 | if (b.length >= 40) 421 | MeBoy.rotations = getInt(b, 36); 422 | if (b.length >= 44) 423 | MeBoy.lazyLoadingThreshold = getInt(b, 40); 424 | 425 | int index = 44; 426 | if (b.length > index) { 427 | // suspended games index 428 | MeBoy.suspendCounter = getInt(b, index); 429 | index += 4; 430 | MeBoy.suspendName10 = new String[getInt(b, index)]; 431 | index += 4; 432 | for (int i = 0; i < MeBoy.suspendName10.length; i++) { 433 | int slen = b[index++] & 0xff; 434 | 435 | MeBoy.suspendName10[i] = new String(b, index, slen); 436 | index += slen; 437 | } 438 | } 439 | 440 | if (b.length > index) { 441 | // settings, part 1 442 | MeBoy.enableScaling = (b[index] & 1) != 0; 443 | MeBoy.keepProportions = (b[index] & 2) != 0; 444 | MeBoy.fullScreen = (b[index] & 4) != 0; 445 | MeBoy.disableColor = (b[index] & 8) != 0; 446 | MeBoy.language = (b[index] & 16) != 0 ? 1 : 0; 447 | MeBoy.enableSound = (b[index] & 32) != 0; 448 | MeBoy.advancedSound = (b[index] & 64) != 0; 449 | MeBoy.advancedGraphics = (b[index] & 128) != 0; 450 | index++; 451 | } 452 | 453 | if (b.length > index) { 454 | MeBoy.language = b[index++]; 455 | } 456 | 457 | if (b.length > index) { 458 | // settings, part 2 459 | MeBoy.showFps = (b[index] & 1) != 0; 460 | MeBoy.showLogItem = (b[index] & 2) != 0; 461 | index++; 462 | } 463 | 464 | if (b.length > index) { 465 | MeBoy.suspendName20 = new String[b[index++]]; 466 | for (int i = 0; i < MeBoy.suspendName20.length; i++) { 467 | // Manual UTF-16, since new String(..., encoding) is not well-supported: 468 | int slen = b[index++] & 0xff; 469 | char[] chars = new char[slen]; 470 | for (int j = 0; j < slen; j++) { 471 | chars[j] += (b[index++] & 0xff) << 8; 472 | chars[j] += (b[index++] & 0xff); 473 | } 474 | MeBoy.suspendName20[i] = new String(chars); 475 | } 476 | } 477 | } else { 478 | writeSettings(); 479 | } 480 | rs.closeRecordStore(); 481 | } catch (Exception e) { 482 | if (MeBoy.debug) 483 | e.printStackTrace(); 484 | MeBoy.log(e.toString()); 485 | } 486 | } 487 | 488 | private final void saveCartRam() { 489 | try { 490 | RecordStore rs = RecordStore.openRecordStore("20R_" + cartID, true); 491 | 492 | byte[][] ram = cpu.getCartRam(); 493 | 494 | int bankCount = ram.length; 495 | int bankSize = ram[0].length; 496 | int size = bankCount * bankSize + 13; 497 | 498 | byte[] b = new byte[size]; 499 | 500 | for (int i = 0; i < bankCount; i++) 501 | System.arraycopy(ram[i], 0, b, i * bankSize, bankSize); 502 | 503 | System.arraycopy(cpu.getRtcReg(), 0, b, bankCount * bankSize, 5); 504 | long now = System.currentTimeMillis(); 505 | setInt(b, bankCount * bankSize + 5, (int) (now >> 32)); 506 | setInt(b, bankCount * bankSize + 9, (int) now); 507 | 508 | if (rs.getNumRecords() == 0) { 509 | rs.addRecord(b, 0, size); 510 | } else { 511 | rs.setRecord(1, b, 0, size); 512 | } 513 | 514 | rs.closeRecordStore(); 515 | } catch (Exception e) { 516 | if (MeBoy.debug) 517 | e.printStackTrace(); 518 | } 519 | } 520 | 521 | private final void loadCartRam() { 522 | try { 523 | RecordStore rs = RecordStore.openRecordStore("20R_" + cartID, true); 524 | 525 | if (rs.getNumRecords() > 0) { 526 | byte[][] ram = cpu.getCartRam(); 527 | int bankCount = ram.length; 528 | int bankSize = ram[0].length; 529 | 530 | byte[] b = rs.getRecord(1); 531 | 532 | for (int i = 0; i < bankCount; i++) 533 | System.arraycopy(b, i * bankSize, ram[i], 0, bankSize); 534 | 535 | if (b.length == bankCount * bankSize + 13) { 536 | // load real time clock 537 | System.arraycopy(b, bankCount * bankSize, cpu.getRtcReg(), 0, 5); 538 | long time = getInt(b, bankCount * bankSize + 5); 539 | time = (time << 32) + ((long) getInt(b, bankCount * bankSize + 9) & 0xffffffffL); 540 | time = System.currentTimeMillis() - time; 541 | cpu.rtcSkip((int) (time / 1000)); 542 | } 543 | } 544 | rs.closeRecordStore(); 545 | } catch (Exception e) { 546 | if (MeBoy.debug) 547 | e.printStackTrace(); 548 | MeBoy.log(e.toString()); 549 | } 550 | } 551 | 552 | private final void suspend() { 553 | try { 554 | boolean insertIndex = false; 555 | if (suspendName == null) { 556 | suspendName = (MeBoy.suspendCounter++) + ": " + cartDisplayName; 557 | insertIndex = true; 558 | } 559 | RecordStore rs = RecordStore.openRecordStore("20S_" + suspendName, true); 560 | 561 | byte[] b = cpu.flatten(); 562 | 563 | if (rs.getNumRecords() == 0) { 564 | rs.addRecord(cartID.getBytes(), 0, cartID.length()); 565 | rs.addRecord(b, 0, b.length); 566 | } else { 567 | rs.setRecord(1, cartID.getBytes(), 0, cartID.length()); 568 | rs.setRecord(2, b, 0, b.length); 569 | } 570 | 571 | rs.closeRecordStore(); 572 | if (insertIndex) 573 | MeBoy.addSuspendedGame(suspendName); 574 | } catch (Exception e) { 575 | MeBoy.showError(null, "error#10", e); 576 | } 577 | } 578 | 579 | public void releaseReferences() { 580 | // This code helps the garbage collector on some platforms. 581 | // (contributed by Alberto Simon) 582 | cpu.terminate(); 583 | while(cpuThread != null && cpuThread.isAlive()) { 584 | Thread.yield(); 585 | } 586 | cpu.releaseReferences(); 587 | cpu = null; 588 | 589 | cartID = null; 590 | cartDisplayName = null; 591 | previousTime = null; 592 | parent = null; 593 | System.gc(); 594 | } 595 | } 596 | 597 | -------------------------------------------------------------------------------- /se/arktos/meboy/Bluetooth.java: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | MeBoy 4 | 5 | Copyright 2005-2008 Bjorn Carlin 6 | http://www.arktos.se/ 7 | 8 | Based on JavaBoy, COPYRIGHT (C) 2001 Neil Millstone and The Victoria 9 | University of Manchester. Bluetooth support based on code contributed by 10 | Martin Neumann. 11 | 12 | This program is free software; you can redistribute it and/or modify it 13 | under the terms of the GNU General Public License as published by the Free 14 | Software Foundation; either version 2 of the License, or (at your option) 15 | any later version. 16 | 17 | This program is distributed in the hope that it will be useful, but WITHOUT 18 | ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 19 | FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for 20 | more details. 21 | 22 | 23 | You should have received a copy of the GNU General Public License along with 24 | this program; if not, write to the Free Software Foundation, Inc., 59 Temple 25 | Place - Suite 330, Boston, MA 02111-1307, USA. 26 | 27 | */ 28 | 29 | package se.arktos.meboy; 30 | 31 | import java.awt.*; 32 | import java.awt.event.*; 33 | import java.io.*; 34 | import java.util.*; 35 | 36 | import javax.bluetooth.*; 37 | import javax.bluetooth.UUID; 38 | import javax.microedition.io.Connector; 39 | import javax.microedition.io.StreamConnection; 40 | import javax.microedition.io.StreamConnectionNotifier; 41 | import javax.swing.*; 42 | import javax.swing.border.*; 43 | import javax.swing.event.*; 44 | 45 | 46 | public class Bluetooth implements Runnable, DiscoveryListener, WindowListener, ActionListener, 47 | ListSelectionListener { 48 | private static final UUID MeBoyUUID = new UUID("ceb15f4a19eb4db28d1c40eb0f091607", false); 49 | // 0x1101 is the UUID for the Serial Port Profile 50 | private static final UUID[] uuidSet = { new UUID(0x1101)}; 51 | // 0x0100 is the attribute for the service name element in the service record 52 | private static final int[] attrSet = { 0x0100}; 53 | 54 | private String connectionURL; 55 | private JFileChooser chooser = new JFileChooser(); 56 | 57 | JFrame frame = new JFrame("MeBoyBuilder - Bluetooth"); 58 | 59 | private JProgressBar receiveProgress = new JProgressBar(); 60 | private JLabel receiveProgressLabel = new JLabel(""); 61 | 62 | private JProgressBar sendProgress = new JProgressBar(); 63 | private JLabel sendProgressLabel = new JLabel("Scanning for devices..."); 64 | 65 | private ArrayList remoteDevices = new ArrayList(); 66 | private JList deviceList = new JList(); 67 | private JButton sendButton = new JButton("Send savegame"); 68 | private boolean readyToSend = false; 69 | private byte[] sendBuffer; 70 | private String sendFilename; 71 | 72 | private int threadCounter; 73 | private Thread receiverThread; 74 | private DiscoveryAgent discoveryAgent; 75 | private boolean discoveryAgentIsInquiring; 76 | private boolean discoveryAgentIsScanning; 77 | private int discoveryAgentId; 78 | 79 | 80 | public Bluetooth() { 81 | frame.setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE); 82 | 83 | JPanel contents = new JPanel(new BorderLayout(4, 4)); 84 | frame.getContentPane().add(contents); 85 | contents.setBorder(new EmptyBorder(4, 4, 4, 4)); 86 | 87 | JPanel receivePanel = new JPanel(new BorderLayout(4, 4)); 88 | contents.add(receivePanel, BorderLayout.NORTH); 89 | 90 | receivePanel.add(receiveProgress, BorderLayout.WEST); 91 | receiveProgress.setIndeterminate(true); 92 | receivePanel.add(receiveProgressLabel); 93 | 94 | JPanel sendPanel = new JPanel(new BorderLayout(4, 4)); 95 | contents.add(sendPanel); 96 | 97 | JPanel sendStatusPanel = new JPanel(new BorderLayout(4, 4)); 98 | sendPanel.add(sendStatusPanel, BorderLayout.NORTH); 99 | 100 | sendStatusPanel.add(sendProgress, BorderLayout.WEST); 101 | sendProgress.setIndeterminate(true); 102 | sendStatusPanel.add(sendProgressLabel); 103 | 104 | sendPanel.add(deviceList, BorderLayout.CENTER); 105 | deviceList.addListSelectionListener(this); 106 | 107 | JPanel buttonPanel = new JPanel(new FlowLayout(FlowLayout.LEFT, 4, 4)); 108 | sendPanel.add(buttonPanel, BorderLayout.EAST); 109 | sendButton.addActionListener(this); 110 | sendButton.setEnabled(false); 111 | buttonPanel.add(sendButton); 112 | 113 | frame.pack(); 114 | frame.setBounds(300, 180, 500, 300); 115 | frame.setVisible(true); 116 | frame.addWindowListener(this); 117 | MeBoyBuilder.bluetoothInstance = this; 118 | 119 | new Thread(this).start(); 120 | } 121 | 122 | public void run() { 123 | threadCounter++; 124 | if (threadCounter == 1) { 125 | new Thread(this).start(); 126 | 127 | try { 128 | LocalDevice localDevice = LocalDevice.getLocalDevice(); 129 | discoveryAgent = localDevice.getDiscoveryAgent(); 130 | discoveryAgent.startInquiry(DiscoveryAgent.GIAC, this); 131 | discoveryAgentIsInquiring = true; 132 | } catch (Exception e) { 133 | MeBoyBuilder.nonFatalException(e, "MeBoyBuilder failed scanning for Bluetooth devices."); 134 | sendProgress.setIndeterminate(false); 135 | sendProgressLabel.setText("Failed scanning for Bluetooth devices."); 136 | } 137 | } else if (threadCounter == 2) { 138 | receiverThread = Thread.currentThread(); 139 | receiveMainloop(); 140 | } else { 141 | send(); 142 | } 143 | } 144 | 145 | private void send() { 146 | try { 147 | sendProgress.setValue(0); 148 | sendProgress.setMaximum(100); 149 | sendProgress.setIndeterminate(false); 150 | 151 | boolean signatureMatch = true; 152 | String signature = "MeBoy20S"; 153 | for (int i = 0; i < signature.length(); i++) 154 | if (signature.charAt(i) != sendBuffer[i]) 155 | signatureMatch = false; 156 | 157 | if (!signatureMatch && (sendBuffer.length % 0x2000 != 0)) { 158 | MeBoyBuilder.nonFatalException(new IOException(), "The file you selected is not a valid savegame file."); 159 | readyToSend = true; 160 | sendButton.setEnabled(deviceList.getSelectedValue() != null); 161 | sendProgressLabel.setText("Could not send."); 162 | return; 163 | } 164 | 165 | StreamConnection connection = (StreamConnection) Connector.open(connectionURL); 166 | DataInputStream is = connection.openDataInputStream(); 167 | DataOutputStream os = connection.openDataOutputStream(); 168 | os.writeUTF("MeBoy20"); 169 | 170 | int ix = 0; 171 | if (signatureMatch) { 172 | os.writeUTF("s"); // suspended game 173 | os.flush(); 174 | String r = is.readUTF(); 175 | if (!r.equals("go")) { 176 | // weird answer 177 | throw new IOException("Received unexpected data: " + r); 178 | } 179 | String displayName = sendFilename; 180 | if (displayName.endsWith(".meboy")) 181 | displayName = displayName.substring(0, displayName.length() - 6); 182 | String cartID = new String(sendBuffer, 9, sendBuffer[8]); // 8 bytes are signature 183 | 184 | os.writeUTF(displayName); 185 | os.writeUTF(""); // filename unknown 186 | os.writeUTF(cartID); 187 | os.writeInt(sendBuffer.length - cartID.length() - 9); 188 | ix = cartID.length() + 9; // skip signature + cartID when sending bulk 189 | } else { 190 | os.writeUTF("r"); // cart ram 191 | os.flush(); 192 | String r = is.readUTF(); 193 | if (!r.equals("go")) { 194 | // weird answer 195 | throw new IOException("Received unexpected data: " + r); 196 | } 197 | os.writeUTF(""); // display name unknown 198 | os.writeUTF(sendFilename); 199 | os.writeUTF(""); // cartid unknown 200 | os.writeInt(sendBuffer.length); 201 | } 202 | while (ix < sendBuffer.length) { 203 | int length = Math.min(sendBuffer.length - ix, 512); 204 | os.write(sendBuffer, ix, length); 205 | ix += length; 206 | sendProgress.setValue(95 * ix / sendBuffer.length); 207 | } 208 | os.flush(); 209 | String r = is.readUTF(); 210 | if (!r.equals("ok")) { 211 | // something went wrong :( 212 | throw new IOException("Received unexpected data: " + r); 213 | } 214 | is.close(); 215 | os.close(); 216 | connection.close(); 217 | readyToSend = true; 218 | sendButton.setEnabled(deviceList.getSelectedValue() != null); 219 | sendProgressLabel.setText("Finished sending."); 220 | sendProgress.setValue(100); 221 | } catch (Exception ex) { 222 | MeBoyBuilder.nonFatalException(ex, "MeBoyBuilder failed to send the selected file."); 223 | readyToSend = true; 224 | sendButton.setEnabled(deviceList.getSelectedValue() != null); 225 | sendProgressLabel.setText("Could not send."); 226 | } 227 | } 228 | 229 | public void receiveMainloop() { 230 | StreamConnectionNotifier notifier; 231 | DataInputStream is; 232 | DataOutputStream os; 233 | StreamConnection connection = null; 234 | 235 | String serviceURL = "btspp://localhost:" + MeBoyUUID + ";authenticate=false;encrypt=false;name=MeBoy"; 236 | 237 | try { 238 | // create a server connection 239 | notifier = (StreamConnectionNotifier) Connector.open(serviceURL); 240 | while (true) { 241 | receiveProgressLabel.setText("Ready to receive savegame..."); 242 | receiveProgress.setIndeterminate(true); 243 | 244 | // accept client connections 245 | connection = notifier.acceptAndOpen(); // blocking 246 | 247 | // we have a client when notifier.acceptAndOpen(); returns 248 | receiveProgressLabel.setText("Connecting..."); 249 | is = connection.openDataInputStream(); 250 | os = connection.openDataOutputStream(); 251 | String protocol = is.readUTF(); 252 | if (!protocol.equals("MeBoy20")) { 253 | throw new IOException("Received unexpected data: " + protocol); 254 | } 255 | os.writeUTF("go"); 256 | os.flush(); 257 | String mode = is.readUTF(); 258 | if (!mode.equals("r") && !mode.equals("s")) { 259 | throw new IOException("Received unexpected data: " + mode); 260 | } 261 | String displayName = is.readUTF(); // display name 262 | System.out.println("Received displayname: " + displayName); 263 | String gameFileName = is.readUTF(); 264 | System.out.println("Received gameFileName: " + gameFileName); 265 | String cartID = is.readUTF(); // cart id 266 | System.out.println("Received cartID: " + cartID); 267 | int length = is.readInt(); 268 | byte[] savegame = new byte[length]; 269 | 270 | receiveProgress.setValue(0); 271 | receiveProgress.setMaximum(length); 272 | receiveProgress.setIndeterminate(false); 273 | int a; 274 | int offset = 0; 275 | while (offset < length && (a = is.read(savegame, offset, length - offset)) != -1) { 276 | offset += a; 277 | receiveProgress.setValue(offset); 278 | receiveProgressLabel.setText("Receiving... " + offset / length * 100 + "%"); 279 | } 280 | 281 | if (offset == length) { 282 | os.writeUTF("ok"); 283 | os.flush(); 284 | 285 | if (mode.equals("r")) { 286 | if (gameFileName.toLowerCase().endsWith(".gb")) 287 | gameFileName = gameFileName.substring(0, gameFileName.length()-2) + "sav"; 288 | else if (gameFileName.toLowerCase().endsWith(".gbc")) 289 | gameFileName = gameFileName.substring(0, gameFileName.length()-3) + "sav"; 290 | else if (!gameFileName.toLowerCase().endsWith(".sav")) 291 | gameFileName += ".sav"; 292 | chooser.setSelectedFile(new File(gameFileName)); 293 | int returnVal = chooser.showSaveDialog(frame); 294 | if (returnVal == JFileChooser.APPROVE_OPTION) { 295 | File savegameFile = chooser.getSelectedFile(); 296 | FileOutputStream fos = new FileOutputStream(savegameFile); 297 | fos.write(savegame, 0, savegame.length & 0xFF000); // strip trailing RTCregs 298 | fos.close(); 299 | } 300 | } else { 301 | chooser.setSelectedFile(new File(displayName + ".meboy")); 302 | int returnVal = chooser.showSaveDialog(frame); 303 | if (returnVal == JFileChooser.APPROVE_OPTION) { 304 | File savegameFile = chooser.getSelectedFile(); 305 | FileOutputStream fos = new FileOutputStream(savegameFile); 306 | fos.write("MeBoy20S".getBytes()); 307 | fos.write(cartID.length()); 308 | fos.write(cartID.getBytes()); 309 | fos.write(savegame); 310 | fos.close(); 311 | } 312 | } 313 | } else { 314 | throw new IOException("Received unexpected data: " + offset + "/" + length); 315 | } 316 | is.close(); 317 | os.close(); 318 | connection.close(); 319 | } 320 | } catch (InterruptedIOException iioex) { 321 | // window closing 322 | } catch (Exception ex) { 323 | MeBoyBuilder.nonFatalException(ex, "MeBoyBuilder failed to receive a savegame."); 324 | receiveProgressLabel.setText("Could not receive."); 325 | receiveProgress.setValue(0); 326 | } 327 | } 328 | 329 | public void deviceDiscovered(RemoteDevice device, DeviceClass dummy) { 330 | remoteDevices.add(device); 331 | sendProgressLabel.setText("Scanning for devices... (" + remoteDevices.size() + " found)"); 332 | } 333 | 334 | public void inquiryCompleted(int discType) { 335 | discoveryAgentIsInquiring = false; 336 | if (discType != INQUIRY_COMPLETED || remoteDevices.size() == 0) { 337 | sendProgressLabel.setText("Did not find any Bluetooth devices."); 338 | sendProgress.setIndeterminate(false); 339 | return; 340 | } 341 | 342 | // The discovery process was a success 343 | Object[] data = new Object[remoteDevices.size()]; 344 | for (int i = 0; i < remoteDevices.size(); i++) { 345 | String device; 346 | try { 347 | device = (remoteDevices.get(i)).getFriendlyName(true); 348 | } catch (Exception ex) { 349 | device = (remoteDevices.get(i)).getBluetoothAddress(); 350 | } 351 | data[i] = device; 352 | } 353 | deviceList.setListData(data); 354 | sendProgress.setIndeterminate(false); 355 | sendProgressLabel.setText("Found " + data.length + " device" 356 | + (data.length == 1 ? "" : "s") + "."); 357 | readyToSend = true; 358 | } 359 | 360 | public void serviceSearchCompleted(int transID, int respCode) { 361 | discoveryAgentIsScanning = false; 362 | if (connectionURL != null && respCode == DiscoveryListener.SERVICE_SEARCH_COMPLETED) { 363 | sendProgressLabel.setText("MeBoy found, sending..."); 364 | sendProgress.setIndeterminate(true); 365 | new Thread(this).start(); 366 | } else { 367 | sendProgressLabel.setText("Could not connect to MeBoy on the selected device."); 368 | readyToSend = true; 369 | sendButton.setEnabled(readyToSend && deviceList.getSelectedValue() != null); 370 | } 371 | } 372 | 373 | public void servicesDiscovered(int transID, ServiceRecord[] servRecord) { 374 | for (int i = 0; i < servRecord.length; i++) { 375 | DataElement serviceElement = servRecord[i].getAttributeValue(0x0001); 376 | Enumeration serviceIDs = (Enumeration) (serviceElement == null ? null : serviceElement 377 | .getValue()); 378 | if (serviceIDs == null) { 379 | continue; 380 | } 381 | 382 | // Check if one of the service's class IDs matches the application's UUID 383 | while (serviceIDs.hasMoreElements()) { 384 | UUID serviceID = (UUID) (((DataElement) serviceIDs.nextElement()).getValue()); 385 | if (!serviceID.equals(MeBoyUUID)) 386 | continue; 387 | 388 | // get URL the "canonical" way 389 | connectionURL = servRecord[i].getConnectionURL( 390 | ServiceRecord.NOAUTHENTICATE_NOENCRYPT, false); 391 | if (connectionURL != null && connectionURL.length() > 0) 392 | return; 393 | 394 | // get URL the "messy but compatible" way, since getConnectionURL can fail 395 | connectionURL = null; 396 | long channel = getChannel(servRecord[i].getAttributeValue(0x0004)); 397 | if (channel <= 0) 398 | break; 399 | 400 | connectionURL = "btspp://" + servRecord[i].getHostDevice().getBluetoothAddress() 401 | + ":" + channel + ";authenticate=false;encrypt=false"; 402 | return; 403 | } 404 | } 405 | } 406 | 407 | // code based on an example snippet by "traud" 408 | // http://developer.sonyericsson.com/message/73104#73104 409 | static long getChannel(DataElement list) { 410 | try { 411 | Enumeration e = (Enumeration) list.getValue(); // DATSEQ | DATALT 412 | while (e.hasMoreElements()) { 413 | DataElement d = (DataElement) e.nextElement(); 414 | if (DataElement.DATSEQ == d.getDataType() || DataElement.DATALT == d.getDataType()) { 415 | long r = getChannel(d); 416 | if (r > 0) { 417 | return r; 418 | } 419 | } else if (DataElement.UUID == d.getDataType()) { 420 | if (d.getValue().equals(new UUID(0x0003))) { 421 | DataElement channelRFCOMM = (DataElement) e.nextElement(); 422 | if (DataElement.U_INT_8 == channelRFCOMM.getDataType() 423 | || DataElement.U_INT_16 == channelRFCOMM.getDataType() 424 | || DataElement.INT_16 == channelRFCOMM.getDataType()) { 425 | byte[] bytes = (byte[]) channelRFCOMM.getValue(); 426 | return bytes[0]; // Range (decimal): 1 to 30 427 | } else { 428 | return channelRFCOMM.getLong(); // U_INT_1 429 | } 430 | } 431 | } 432 | } 433 | } catch (Exception ex) { 434 | } 435 | 436 | return -1; 437 | } 438 | 439 | public void windowClosing(WindowEvent e) { 440 | if (discoveryAgentIsScanning) 441 | discoveryAgent.cancelServiceSearch(discoveryAgentId); 442 | else if (discoveryAgentIsInquiring) 443 | discoveryAgent.cancelInquiry(this); 444 | receiverThread.interrupt(); 445 | frame.setVisible(false); 446 | MeBoyBuilder.bluetoothInstance = null; 447 | } 448 | 449 | public void actionPerformed(ActionEvent e) { 450 | int returnVal = chooser.showOpenDialog(frame); 451 | if (returnVal != JFileChooser.APPROVE_OPTION) 452 | return; 453 | 454 | readyToSend = false; 455 | sendButton.setEnabled(false); 456 | 457 | try { 458 | File f = chooser.getSelectedFile(); 459 | InputStream is = new FileInputStream(f); 460 | 461 | sendFilename = f.getName(); 462 | int bufIndex = 0; 463 | sendBuffer = new byte[(int) f.length()]; 464 | int r; 465 | do { 466 | r = is.read(sendBuffer, bufIndex, sendBuffer.length - bufIndex); 467 | if (r > 0) 468 | bufIndex += r; 469 | } while (bufIndex < sendBuffer.length && r >= 0); 470 | 471 | if (r < 0) { 472 | throw new IOException("Unexpected end of file: " + bufIndex + "/" 473 | + sendBuffer.length); 474 | } 475 | 476 | is.close(); 477 | } catch (Exception ex) { 478 | MeBoyBuilder.nonFatalException(ex, "MeBoyBuilder failed to open the selected file."); 479 | readyToSend = true; 480 | sendButton.setEnabled(deviceList.getSelectedValue() != null); 481 | sendProgressLabel.setText("Failed to open file."); 482 | return; 483 | } 484 | 485 | int ix = deviceList.getSelectedIndex(); 486 | RemoteDevice device = remoteDevices.get(ix); 487 | sendProgress.setIndeterminate(true); 488 | sendProgressLabel.setText("Trying to connect to MeBoy on the selected device."); 489 | 490 | try { 491 | discoveryAgentIsScanning = true; 492 | discoveryAgentId = discoveryAgent.searchServices(attrSet, uuidSet, device, this); 493 | } catch (Exception ex) { 494 | MeBoyBuilder.nonFatalException(ex, "MeBoyBuilder failed to connect to MeBoy on the selected device."); 495 | readyToSend = true; 496 | sendButton.setEnabled(deviceList.getSelectedValue() != null); 497 | sendProgressLabel.setText("Failed to connect."); 498 | return; 499 | } 500 | } 501 | 502 | public void valueChanged(ListSelectionEvent e) { 503 | sendButton.setEnabled(readyToSend && deviceList.getSelectedValue() != null); 504 | } 505 | 506 | public void windowActivated(WindowEvent e) {} 507 | 508 | public void windowClosed(WindowEvent e) {} 509 | 510 | public void windowDeactivated(WindowEvent e) {} 511 | 512 | public void windowDeiconified(WindowEvent e) {} 513 | 514 | public void windowIconified(WindowEvent e) {} 515 | 516 | public void windowOpened(WindowEvent e) {} 517 | } 518 | -------------------------------------------------------------------------------- /Bluetooth.java: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | MeBoy 4 | 5 | Copyright 2005-2009 Bjorn Carlin 6 | http://www.arktos.se/ 7 | 8 | Based on JavaBoy, COPYRIGHT (C) 2001 Neil Millstone and The Victoria 9 | University of Manchester. Bluetooth support based on code contributed by 10 | Martin Neumann. 11 | 12 | This program is free software; you can redistribute it and/or modify it 13 | under the terms of the GNU General Public License as published by the Free 14 | Software Foundation; either version 2 of the License, or (at your option) 15 | any later version. 16 | 17 | This program is distributed in the hope that it will be useful, but WITHOUT 18 | ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 19 | FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for 20 | more details. 21 | 22 | 23 | You should have received a copy of the GNU General Public License along with 24 | this program; if not, write to the Free Software Foundation, Inc., 59 Temple 25 | Place - Suite 330, Boston, MA 02111-1307, USA. 26 | 27 | */ 28 | 29 | import java.io.*; 30 | import java.util.*; 31 | import javax.bluetooth.*; 32 | import javax.microedition.io.*; 33 | import javax.microedition.lcdui.*; 34 | import javax.microedition.rms.*; 35 | 36 | 37 | public class Bluetooth implements DiscoveryListener, CommandListener, Runnable { 38 | private static final UUID MeBoyUUID = new UUID("ceb15f4a19eb4db28d1c40eb0f091607", false); 39 | private MeBoy parent; 40 | private String connectionURL; 41 | private String savegameDisplayName; 42 | private String savegameFilename; 43 | private String savegameCartID; 44 | private String sendgameMode = "s"; 45 | private byte[] savegameData; 46 | 47 | private Vector remoteDevices = new Vector(); 48 | private DiscoveryAgent discoveryAgent; 49 | private boolean discoveryAgentIsInquiring; 50 | private boolean discoveryAgentIsScanning; 51 | private int discoveryAgentId; 52 | // 0x1101 is the UUID for the Serial Port Profile 53 | private UUID[] uuidSet = {new UUID(0x1101)}; // 0x0100 is the attribute for the service name element 54 | // in the service record 55 | private int[] attrSet = {0x0100}; 56 | 57 | // UI 58 | private Form waitForm; 59 | private List mainList; 60 | private Form receiveForm; 61 | private List savegameList; 62 | private List suspendgameList; 63 | private List deviceList; 64 | private Gauge gauge; 65 | 66 | private Vector savegameCartIDs; 67 | private Vector savegameFilenames; 68 | 69 | // transmit stuff 70 | private boolean iAmSender; 71 | private StreamConnectionNotifier notifier; 72 | private StreamConnection connection; 73 | private DataInputStream is; 74 | private DataOutputStream os; 75 | 76 | private boolean poisoned; 77 | 78 | 79 | public Bluetooth(MeBoy parent) { 80 | this.parent = parent; 81 | showMain(); 82 | } 83 | 84 | public void deviceDiscovered(RemoteDevice remoteDevice, DeviceClass cod) { 85 | remoteDevices.addElement(remoteDevice); 86 | } 87 | 88 | public void inquiryCompleted(int discType) { 89 | if (poisoned) return; 90 | 91 | discoveryAgentIsInquiring = false; 92 | 93 | if (discType != INQUIRY_COMPLETED) { 94 | // probably canceled 95 | MeBoy.log("disctype = " + discType); 96 | return; 97 | } 98 | if (remoteDevices.size() == 0) { 99 | // the discovery process failed, so inform the user 100 | if (MeBoy.debug) 101 | parent.cancelBluetooth(MeBoy.literal[60] + " " + discType + " " + remoteDevices.size()); 102 | else 103 | parent.cancelBluetooth(MeBoy.literal[60]); 104 | return; 105 | } 106 | 107 | showDevice(); 108 | } 109 | 110 | public void servicesDiscovered(int transID, ServiceRecord[] servRecord) { 111 | if (poisoned) return; 112 | 113 | for (int i = 0; i < servRecord.length; i++) { 114 | DataElement serviceElement = servRecord[i].getAttributeValue(0x0001); 115 | Enumeration serviceIDs = (Enumeration) (serviceElement == null ? null : serviceElement.getValue()); 116 | if (serviceIDs == null) { 117 | // MeBoy.log("serviceIDs=null"); 118 | continue; 119 | } 120 | 121 | // check, if one of the service's class IDs matches the application's UUID 122 | while (serviceIDs.hasMoreElements()) { 123 | UUID serviceID = (UUID) (((DataElement) serviceIDs.nextElement()).getValue()); 124 | if (!serviceID.equals(MeBoyUUID)) 125 | continue; 126 | 127 | // MeBoy.log("Bluetooth: service found: " + servRecord[i].toString()); 128 | 129 | // get URL the "canonical" way 130 | connectionURL = servRecord[i].getConnectionURL(ServiceRecord.NOAUTHENTICATE_NOENCRYPT, false); 131 | if (connectionURL != null && connectionURL.length() > 0) 132 | return; 133 | 134 | // get URL the "messy but compatible" way, since getConnectionURL can fail 135 | long channel = getChannel(servRecord[i].getAttributeValue(0x0004)); 136 | if (channel <= 0) 137 | break; 138 | 139 | connectionURL = "btspp://" + servRecord[i].getHostDevice().getBluetoothAddress() + ":" + 140 | channel + ";authenticate=false;encrypt=false"; 141 | return; 142 | } 143 | } 144 | } 145 | 146 | public void serviceSearchCompleted(int transID, int respCode) { 147 | if (poisoned) return; 148 | 149 | discoveryAgentIsScanning = false; 150 | 151 | if (connectionURL != null && respCode == DiscoveryListener.SERVICE_SEARCH_COMPLETED) { 152 | iAmSender = true; 153 | new Thread(this).start(); 154 | showWait(MeBoy.literal[61], true); 155 | } else { 156 | parent.cancelBluetooth(MeBoy.literal[62]); 157 | } 158 | } 159 | 160 | public void showWait(String message) { 161 | showWait(message, false); 162 | } 163 | 164 | public void showWait(String message, boolean progressBar) { 165 | waitForm = new Form(MeBoy.literal[4]); 166 | waitForm.append(message); 167 | if (progressBar) { 168 | gauge = new Gauge("", false, 100, 5); 169 | waitForm.append(gauge); 170 | } 171 | waitForm.setCommandListener(this); 172 | waitForm.addCommand(new Command(MeBoy.literal[57], Command.CANCEL, 0)); 173 | parent.display.setCurrent(waitForm); 174 | } 175 | 176 | private void waitCommand() { 177 | parent.cancelBluetooth(); 178 | } 179 | 180 | private void showMain() { 181 | mainList = new List(MeBoy.literal[4], List.IMPLICIT); 182 | mainList.append(MeBoy.literal[52], null); 183 | if (parent.suspendName20.length > 0) 184 | mainList.append(MeBoy.literal[54], null); 185 | mainList.append(MeBoy.literal[56], null); 186 | mainList.addCommand(new Command(MeBoy.literal[10], Command.BACK, 1)); 187 | mainList.setCommandListener(this); 188 | parent.display.setCurrent(mainList); 189 | } 190 | 191 | private void mainCommand(Command com) { 192 | if (com.getCommandType() == Command.BACK) { 193 | parent.cancelBluetooth(); 194 | } else { 195 | String item = mainList.getString(mainList.getSelectedIndex()); 196 | if (item == MeBoy.literal[52]) { 197 | showSavegameSelect(); 198 | } else if (item == MeBoy.literal[54]) { 199 | showSuspendGameSelect(); 200 | } else if (item.equals(MeBoy.literal[56])) { 201 | showReceive(false); 202 | } 203 | } 204 | mainList = null; 205 | } 206 | 207 | private void showSavegameSelect() { 208 | savegameList = new List(MeBoy.literal[53], List.IMPLICIT); 209 | savegameCartIDs = new Vector(); 210 | savegameFilenames = new Vector(); 211 | parent.addSavegamesToList(savegameList, savegameCartIDs, savegameFilenames); 212 | 213 | if (savegameList.size() == 0) { 214 | parent.cancelBluetooth(MeBoy.literal[65]); 215 | return; 216 | } 217 | 218 | savegameList.addCommand(new Command(MeBoy.literal[10], Command.BACK, 1)); 219 | savegameList.setCommandListener(this); 220 | parent.display.setCurrent(savegameList); 221 | } 222 | 223 | private void startInquiry() throws BluetoothStateException, InterruptedException { 224 | LocalDevice localDevice = LocalDevice.getLocalDevice(); 225 | discoveryAgent = localDevice.getDiscoveryAgent(); 226 | for (int i = 0; i < 10; i++) { 227 | // For when there's an old inquiry still running, this will 228 | // retry 10 times to start an inquiry and ignoring the 229 | // state exception: 230 | try { 231 | discoveryAgent.startInquiry(DiscoveryAgent.GIAC, this); 232 | discoveryAgentIsInquiring = true; 233 | break; 234 | } catch (BluetoothStateException bse) { 235 | showWait(MeBoy.literal[66]); 236 | Thread.sleep(500); 237 | } 238 | } 239 | if (!discoveryAgentIsInquiring) { 240 | discoveryAgent.startInquiry(DiscoveryAgent.GIAC, this); 241 | discoveryAgentIsInquiring = true; 242 | } 243 | 244 | showWait(MeBoy.literal[66]); 245 | } 246 | 247 | private void savegameSelectCommand(Command com) { 248 | if (com.getCommandType() == Command.BACK) { 249 | showMain(); 250 | savegameList = null; 251 | return; 252 | } 253 | 254 | savegameDisplayName = savegameList.getString(savegameList.getSelectedIndex()); 255 | savegameCartID = (String) savegameCartIDs.elementAt(savegameList.getSelectedIndex()); 256 | savegameFilename = (String) savegameFilenames.elementAt(savegameList.getSelectedIndex()); 257 | sendgameMode = "r"; 258 | savegameList = null; 259 | 260 | try { 261 | RecordStore rs = RecordStore.openRecordStore("20R_" + savegameCartID, false); 262 | savegameData = rs.getRecord(1); 263 | rs.closeRecordStore(); 264 | 265 | startInquiry(); 266 | } catch (Exception e) { 267 | parent.cancelBluetooth(e); 268 | return; 269 | } 270 | } 271 | 272 | private void showSuspendGameSelect() { 273 | suspendgameList = new List(MeBoy.literal[55], List.IMPLICIT); 274 | 275 | for (int i = 0; i < parent.suspendName20.length; i++) { 276 | suspendgameList.append(parent.suspendName20[i], null); 277 | } 278 | 279 | suspendgameList.addCommand(new Command(MeBoy.literal[10], Command.BACK, 1)); 280 | suspendgameList.setCommandListener(this); 281 | parent.display.setCurrent(suspendgameList); 282 | } 283 | 284 | private void suspendGameSelectCommand(Command com) { 285 | if (com.getCommandType() == Command.BACK) { 286 | showMain(); 287 | suspendgameList = null; 288 | return; 289 | } 290 | 291 | savegameDisplayName = suspendgameList.getString(suspendgameList.getSelectedIndex()); 292 | savegameFilename = ""; 293 | sendgameMode = "s"; 294 | suspendgameList = null; 295 | 296 | try { 297 | RecordStore rs = RecordStore.openRecordStore("20S_" + savegameDisplayName, false); 298 | savegameCartID = new String(rs.getRecord(1)); 299 | savegameData = rs.getRecord(2); 300 | rs.closeRecordStore(); 301 | 302 | int ix = savegameDisplayName.indexOf(':'); 303 | if (ix > 0) 304 | savegameDisplayName = savegameDisplayName.substring(ix + 2); // : and space 305 | 306 | startInquiry(); 307 | } catch (Exception e) { 308 | parent.cancelBluetooth(e); 309 | return; 310 | } 311 | } 312 | 313 | private void showDevice() { 314 | try { 315 | // the discovery process was a success 316 | // so let's out them in a List and display it to the user 317 | deviceList = new List(MeBoy.literal[70], List.IMPLICIT); 318 | for (int i = 0; i < remoteDevices.size(); i++) { 319 | String device; 320 | try { 321 | device = ((RemoteDevice) remoteDevices.elementAt(i)).getFriendlyName(true); 322 | } catch (Exception ex) { 323 | device = ((RemoteDevice) remoteDevices.elementAt(i)).getBluetoothAddress(); 324 | } 325 | deviceList.append(device, null); 326 | } 327 | 328 | deviceList.addCommand(new Command(MeBoy.literal[57], Command.CANCEL, 1)); 329 | deviceList.setCommandListener(this); 330 | parent.display.setCurrent(deviceList); 331 | } catch (Exception e) { 332 | parent.cancelBluetooth(e); 333 | } 334 | } 335 | 336 | private void deviceCommand(Command com) { 337 | if (com.getCommandType() == Command.CANCEL) { 338 | parent.cancelBluetooth(); 339 | return; 340 | } 341 | 342 | int selectedDevice = deviceList.getSelectedIndex(); 343 | 344 | try { 345 | RemoteDevice remoteDevice = (RemoteDevice) remoteDevices.elementAt(selectedDevice); 346 | discoveryAgentId = discoveryAgent.searchServices(attrSet, uuidSet, remoteDevice, this); 347 | discoveryAgentIsScanning = true; 348 | } catch (Exception e) { 349 | parent.cancelBluetooth(e); 350 | return; 351 | } 352 | 353 | showWait(MeBoy.literal[67]); 354 | } 355 | 356 | private void showReceive(boolean gotConnection) { 357 | receiveForm = new Form(MeBoy.literal[56]); 358 | receiveForm.addCommand(new Command(MeBoy.literal[57], Command.CANCEL, 1)); 359 | receiveForm.setCommandListener(this); 360 | if (gotConnection) { 361 | receiveForm.append(MeBoy.literal[71]); 362 | gauge = new Gauge("", false, 100, 0); 363 | receiveForm.append(gauge); 364 | } else { 365 | receiveForm.append(MeBoy.literal[68]); 366 | iAmSender = false; 367 | new Thread(this).start(); 368 | } 369 | parent.display.setCurrent(receiveForm); 370 | } 371 | 372 | private void receiveCommand(Command com) { 373 | if (com.getCommandType() == Command.CANCEL) { 374 | parent.cancelBluetooth(); 375 | } 376 | } 377 | 378 | public void tearDown() { 379 | if (poisoned) return; 380 | 381 | poisoned = true; 382 | 383 | if (discoveryAgent != null) { 384 | if (discoveryAgentIsScanning) 385 | discoveryAgent.cancelServiceSearch(discoveryAgentId); 386 | else if (discoveryAgentIsInquiring) 387 | discoveryAgent.cancelInquiry(this); 388 | discoveryAgent = null; 389 | } 390 | 391 | if (is != null) { 392 | try { 393 | is.close(); 394 | } catch (IOException ex) { 395 | } //ignore 396 | is = null; 397 | } 398 | if (os != null) { 399 | try { 400 | os.close(); 401 | } catch (IOException ex) { 402 | } //ignore 403 | os = null; 404 | } 405 | if (connection != null) { 406 | try { 407 | connection.close(); 408 | } catch (IOException ex) { 409 | } //ignore 410 | connection = null; 411 | } 412 | if (notifier != null) { 413 | try { 414 | notifier.close(); 415 | } catch (IOException ex) { 416 | } //ignore 417 | notifier = null; 418 | } 419 | mainList = null; 420 | receiveForm = null; 421 | savegameList = null; 422 | deviceList = null; 423 | System.gc(); 424 | } 425 | 426 | public void commandAction(Command com, Displayable s) { 427 | if (s == waitForm) 428 | waitCommand(); 429 | else if (s == mainList) 430 | mainCommand(com); 431 | else if (s == savegameList) 432 | savegameSelectCommand(com); 433 | else if (s == suspendgameList) 434 | suspendGameSelectCommand(com); 435 | else if (s == deviceList) 436 | deviceCommand(com); 437 | else if (s == receiveForm) 438 | receiveCommand(com); 439 | } 440 | 441 | private void send() throws Exception { 442 | connection = (StreamConnection) Connector.open(connectionURL); 443 | is = connection.openDataInputStream(); 444 | os = connection.openDataOutputStream(); 445 | os.writeUTF("MeBoy20"); 446 | os.flush(); 447 | if (MeBoy.debug) showWait("sent protocol"); 448 | String ack = is.readUTF(); 449 | if (MeBoy.debug) showWait("read " + ack); 450 | if (ack.equals("version")) { 451 | throw new IOException(MeBoy.literal[69]); 452 | } else if (!ack.equals("go")) { 453 | // weird answer 454 | throw new IOException("read " + ack); 455 | } 456 | os.writeUTF(sendgameMode); 457 | os.writeUTF(savegameDisplayName); 458 | os.writeUTF(savegameFilename); 459 | os.writeUTF(savegameCartID); 460 | os.writeInt(savegameData.length); 461 | if (MeBoy.debug) showWait("sent header", true); 462 | int lastPercent = 0; 463 | // Send small chunks, since SE K810i crashes hard if I send everything at once. 464 | for (int i = 0; i < savegameData.length; i += 512) { 465 | int length = Math.min(savegameData.length - i, 512); 466 | os.write(savegameData, i, length); 467 | os.flush(); 468 | if (MeBoy.debug) showWait("sent " + i, true); 469 | int percent = 100 * i / savegameData.length; 470 | if (percent > lastPercent + 10) { 471 | gauge.setValue(percent); 472 | Thread.sleep(50); 473 | lastPercent = percent; 474 | } 475 | } 476 | gauge.setValue(100); 477 | if (MeBoy.debug) showWait("sent all"); 478 | ack = is.readUTF(); 479 | if (ack.equals("ok")) { 480 | // everything fine 481 | parent.finishBluetooth(); 482 | } else { 483 | // something went wrong :( 484 | throw new IOException("read " + ack); 485 | } 486 | } 487 | 488 | private void receive() throws Exception { 489 | String serviceURL = "btspp://localhost:" + MeBoyUUID + 490 | ";authenticate=false;encrypt=false;name=MeBoy"; 491 | 492 | // create a server connection 493 | notifier = (StreamConnectionNotifier) Connector.open(serviceURL); 494 | 495 | if (MeBoy.debug) showWait("entering blocking"); 496 | // accept client connections 497 | connection = notifier.acceptAndOpen(); // blocking 498 | showReceive(true); 499 | 500 | if (MeBoy.debug) showWait("got connection"); 501 | // we have a client when notifier.acceptAndOpen(); returns 502 | is = connection.openDataInputStream(); 503 | os = connection.openDataOutputStream(); 504 | if (MeBoy.debug) showWait("got streams"); 505 | String protocol = is.readUTF(); 506 | if (!protocol.equals("MeBoy20")) { 507 | os.writeUTF("version"); 508 | os.flush(); 509 | throw new IOException("read " + protocol); 510 | } 511 | if (MeBoy.debug) showWait("read protocol"); 512 | os.writeUTF("go"); 513 | os.flush(); 514 | if (MeBoy.debug) showWait("sent go"); 515 | String mode = is.readUTF(); 516 | if (!mode.equals("r") && !mode.equals("s")) { 517 | throw new IOException("invalid mode: " + mode); 518 | } 519 | 520 | String gameDisplayName = is.readUTF(); 521 | String gameFileName = is.readUTF(); 522 | String gameCartID = is.readUTF(); 523 | if (MeBoy.debug) showWait("read name"); 524 | int length = is.readInt(); 525 | byte[] savegame = new byte[length]; 526 | gauge.setMaxValue(100); 527 | gauge.setValue(0); 528 | 529 | int a; 530 | int offset = 0; 531 | int lastPercent = 0; 532 | while (offset < length && 533 | (a = is.read(savegame, offset, length - offset)) != -1) { 534 | offset += a; 535 | if (MeBoy.debug) showWait("read total " + offset + "/" + length); 536 | int percent = 100 * offset / length; 537 | if (percent > lastPercent + 10) { 538 | gauge.setValue(percent); 539 | Thread.sleep(50); 540 | lastPercent = percent; 541 | } 542 | } 543 | 544 | if (offset == length) { 545 | os.writeUTF("ok"); 546 | os.flush(); 547 | parent.finishBluetoothReceive(mode, gameDisplayName, gameFileName, gameCartID, savegame); 548 | } else { 549 | throw new IOException("read " + offset + "/" + length); 550 | } 551 | } 552 | 553 | public void run() { 554 | try { 555 | if (iAmSender) { 556 | send(); 557 | } else { 558 | receive(); 559 | } 560 | } catch (InterruptedIOException e) { 561 | parent.cancelBluetooth(); 562 | } catch (Exception e) { 563 | parent.cancelBluetooth(e); 564 | } 565 | } 566 | 567 | // shamelessly stolen from "traud", with minor changes 568 | // http://developer.sonyericsson.com/message/73104#73104 569 | static long getChannel(DataElement list) { 570 | try { 571 | Enumeration e = (Enumeration) list.getValue(); // DATSEQ | DATALT 572 | while (e.hasMoreElements()) { 573 | DataElement d = (DataElement) e.nextElement(); 574 | if (DataElement.DATSEQ == d.getDataType() || DataElement.DATALT == d.getDataType()) { 575 | long r = getChannel(d); 576 | if (r > 0) { 577 | return r; 578 | } 579 | } else if (DataElement.UUID == d.getDataType()) { 580 | if (d.getValue().equals(new UUID(0x0003))) { 581 | DataElement channelRFCOMM = (DataElement) e.nextElement(); 582 | if (DataElement.U_INT_8 == channelRFCOMM.getDataType() || 583 | DataElement.U_INT_16 == channelRFCOMM.getDataType() || 584 | DataElement.INT_16 == channelRFCOMM.getDataType()) { 585 | byte[] bytes = (byte[]) channelRFCOMM.getValue(); 586 | return bytes[0]; // Range (decimal): 1 to 30 587 | } else { 588 | return channelRFCOMM.getLong(); // U_INT_1 589 | } 590 | } 591 | } 592 | } 593 | } catch (Exception ex) {} 594 | 595 | return -1; 596 | } 597 | 598 | // Will never return false, but invocation will throw an NoClassDefFoundError if not available. 599 | static boolean available() { 600 | return true; 601 | } 602 | } 603 | -------------------------------------------------------------------------------- /se/arktos/meboy/MeBoyBuilder.java: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | MeBoy 4 | 5 | Copyright 2005-2008 Bjorn Carlin 6 | http://www.arktos.se/ 7 | 8 | Based on JavaBoy, COPYRIGHT (C) 2001 Neil Millstone and The Victoria 9 | University of Manchester. Bluetooth support based on code contributed by 10 | Martin Neumann. 11 | 12 | This program is free software; you can redistribute it and/or modify it 13 | under the terms of the GNU General Public License as published by the Free 14 | Software Foundation; either version 2 of the License, or (at your option) 15 | any later version. 16 | 17 | This program is distributed in the hope that it will be useful, but WITHOUT 18 | ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 19 | FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for 20 | more details. 21 | 22 | 23 | You should have received a copy of the GNU General Public License along with 24 | this program; if not, write to the Free Software Foundation, Inc., 59 Temple 25 | Place - Suite 330, Boston, MA 02111-1307, USA. 26 | 27 | */ 28 | 29 | package se.arktos.meboy; 30 | 31 | import java.awt.*; 32 | import java.awt.event.*; 33 | import java.io.*; 34 | import java.lang.reflect.*; 35 | import java.net.*; 36 | import java.util.*; 37 | import java.util.prefs.*; 38 | import java.util.zip.*; 39 | 40 | import javax.bluetooth.*; 41 | import javax.swing.*; 42 | import javax.swing.border.*; 43 | import javax.swing.event.*; 44 | import javax.swing.filechooser.FileFilter; 45 | 46 | /** 47 | * A class for building the MeBoy.jar and .jad files. 48 | * It will copy the relevant contents of its own jar 49 | * and insert a new manifest file. 50 | * 51 | * It will also add roms, and enumerate them in the 52 | * carts file. 53 | */ 54 | public class MeBoyBuilder implements ActionListener, ListSelectionListener, WindowListener { 55 | public static void main(String[] args) { 56 | checkForUpdates(); 57 | new MeBoyBuilder(); 58 | } 59 | 60 | static Bluetooth bluetoothInstance; 61 | static ImageIcon optionIcon; 62 | 63 | JFrame frame = new JFrame("MeBoyBuilder 2.1"); 64 | JButton addGame = new JButton("Add game"); 65 | JButton removeGame = new JButton("Remove game"); 66 | JButton renameGame = new JButton("Rename game"); 67 | JButton createJar = new JButton("Create MeBoy.jar"); 68 | JButton bluetooth = new JButton("Bluetooth"); 69 | 70 | byte[][] iconData = new byte[4][]; 71 | ImageIcon[] icon = new ImageIcon[4]; 72 | JRadioButton[] iconButton = {new JRadioButton("small"), new JRadioButton("medium"), new JRadioButton("large"), new JRadioButton("other...")}; 73 | JLabel iconLabel = new JLabel(); 74 | int selectedIcon = 2; // large 75 | 76 | ArrayList games = new ArrayList(); 77 | 78 | JFileChooser chooser = new JFileChooser(); 79 | JFileChooser iconChooser = new JFileChooser(); 80 | 81 | JPanel content = new JPanel(new BorderLayout(4, 4)); 82 | JList list = new JList(); 83 | JScrollPane jsp = new JScrollPane(list, JScrollPane.VERTICAL_SCROLLBAR_ALWAYS, 84 | JScrollPane.HORIZONTAL_SCROLLBAR_NEVER); 85 | 86 | boolean dirty; 87 | 88 | private MeBoyBuilder() { 89 | Dimension screen = Toolkit.getDefaultToolkit().getScreenSize(); 90 | frame.setBounds(screen.width / 2 - 200, screen.height / 2 - 200, 400, 400); 91 | 92 | jsp.setPreferredSize(new Dimension(200, 200)); 93 | content.add(jsp, BorderLayout.CENTER); 94 | 95 | JPanel eastPanel = new JPanel(new BorderLayout(4, 4)); 96 | JPanel buttonPanel = new JPanel(new GridLayout(0, 1, 4, 4)); 97 | JPanel belowButtonPanel = new JPanel(new BorderLayout(4, 4)); 98 | 99 | // buttons 100 | addGame.addActionListener(this); 101 | removeGame.addActionListener(this); 102 | renameGame.addActionListener(this); 103 | createJar.addActionListener(this); 104 | bluetooth.addActionListener(this); 105 | 106 | try { 107 | // Test if there's a Bluetooth stack we can use: 108 | LocalDevice.getLocalDevice(); 109 | } catch (Throwable t) { 110 | bluetooth.setEnabled(false); 111 | } 112 | 113 | buttonPanel.add(addGame); 114 | buttonPanel.add(removeGame); 115 | buttonPanel.add(renameGame); 116 | buttonPanel.add(createJar); 117 | eastPanel.add(buttonPanel, BorderLayout.NORTH); 118 | eastPanel.add(belowButtonPanel, BorderLayout.CENTER); 119 | belowButtonPanel.add(bluetooth, BorderLayout.SOUTH); 120 | 121 | // icons 122 | iconChooser.setFileFilter(new ImageFilter()); 123 | JPanel iconPanel = new JPanel(new BorderLayout(4, 4)); 124 | iconPanel.setBorder(new TitledBorder("Icon")); 125 | JPanel radioButtonPanel = new JPanel(new GridLayout(0, 1, 4, 4)); 126 | 127 | try { 128 | for (int i = 0; i < 3; i++) { 129 | iconData[i] = readAll(ClassLoader.getSystemClassLoader().getResourceAsStream("meboy" + i + ".png")); 130 | icon[i] = new ImageIcon(iconData[i]); 131 | } 132 | } catch (Exception e) { 133 | e.printStackTrace(); 134 | } 135 | optionIcon = icon[2]; 136 | ButtonGroup g = new ButtonGroup(); 137 | for (JRadioButton b : iconButton) { 138 | g.add(b); 139 | radioButtonPanel.add(b); 140 | b.addActionListener(this); 141 | } 142 | iconButton[selectedIcon].setSelected(true); 143 | iconLabel.setIcon(icon[selectedIcon]); 144 | iconPanel.add(iconLabel); 145 | iconPanel.add(radioButtonPanel, BorderLayout.WEST); 146 | belowButtonPanel.add(iconPanel, BorderLayout.NORTH); 147 | 148 | content.setBorder(new EmptyBorder(4, 4, 4, 4)); 149 | content.add(eastPanel, BorderLayout.EAST); 150 | 151 | JScrollPane jsp2 = new JScrollPane(content, JScrollPane.VERTICAL_SCROLLBAR_ALWAYS, 152 | JScrollPane.HORIZONTAL_SCROLLBAR_ALWAYS); 153 | frame.getContentPane().add(jsp2); 154 | frame.setVisible(true); 155 | frame.setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE); 156 | frame.addWindowListener(this); 157 | 158 | chooser.setDialogTitle("Select ROM file"); 159 | iconChooser.setDialogTitle("Select icon"); 160 | 161 | updateList(); 162 | } 163 | 164 | private void updateList() { 165 | String[] names = new String[games.size()]; 166 | 167 | int i = 0; 168 | for (Game g : games) { 169 | names[i++] = g.label(); 170 | } 171 | 172 | list = new JList(names); 173 | list.addListSelectionListener(this); 174 | jsp.setViewportView(list); 175 | content.revalidate(); 176 | valueChanged(null); 177 | } 178 | 179 | public void valueChanged(ListSelectionEvent e) { 180 | removeGame.setEnabled(list.getSelectedValue() != null); 181 | renameGame.setEnabled(list.getSelectedValue() != null); 182 | createJar.setEnabled(list.getModel().getSize() > 0); 183 | } 184 | 185 | public void windowClosing(WindowEvent e) { 186 | if (!dirty) 187 | System.exit(0); 188 | 189 | String q = "Do you want to discard changes and quit? A MeBoy.jar file " + 190 | "has not yet been created with the games you added."; 191 | 192 | int response = JOptionPane.showOptionDialog(frame, string2JTA(q), "MeBoyBuilder 2.1", 0, 193 | JOptionPane.PLAIN_MESSAGE, icon[2], new Object[] { "Quit", "Cancel"}, "Quit"); 194 | 195 | if (response == 0) 196 | System.exit(0); 197 | } 198 | 199 | public void actionPerformed(ActionEvent e) { 200 | if (e.getSource() == addGame) { 201 | try { 202 | addGame(); 203 | updateList(); 204 | } catch (Exception ex) { 205 | fatalException(ex); 206 | } 207 | } else if (e.getSource() == removeGame) { 208 | int i = list.getSelectedIndex(); 209 | if (i < 0) 210 | return; 211 | games.remove(i); 212 | dirty = true; 213 | updateList(); 214 | } else if (e.getSource() == renameGame) { 215 | int i = list.getSelectedIndex(); 216 | if (i < 0) 217 | return; 218 | 219 | Game g = games.get(i); 220 | 221 | String s = (String) JOptionPane.showInputDialog(frame, 222 | "Please enter a new name (max 24 characters):", "Enter name", JOptionPane.QUESTION_MESSAGE, icon[2], null, g.displayName); 223 | 224 | if (s == null || s.trim().length() < 1) 225 | return; 226 | s = cleanName(s); 227 | if (g.displayName.equals(s)) 228 | return; 229 | 230 | if (findByDisplayName(s) != null) { 231 | JOptionPane.showMessageDialog(frame, 232 | string2JTA("Please select a unique name."), "MeBoyBuilder 2.1", 233 | JOptionPane.PLAIN_MESSAGE, icon[2]); 234 | return; 235 | } 236 | g.displayName = s; 237 | Collections.sort(games); 238 | updateList(); 239 | } else if (e.getSource() == createJar) { 240 | try { 241 | createJar(); 242 | } catch (Exception ex) { 243 | fatalException(ex); 244 | } 245 | } else if (e.getSource() == bluetooth) { 246 | if (bluetoothInstance == null) 247 | bluetoothInstance = new Bluetooth(); 248 | else { 249 | bluetoothInstance.frame.requestFocus(); 250 | } 251 | } 252 | for (int i = 0; i < icon.length - 1; i++) { 253 | if (e.getSource() == iconButton[i]) { 254 | selectedIcon = i; 255 | iconLabel.setIcon(icon[i]); 256 | } 257 | } 258 | if (e.getSource() == iconButton[icon.length - 1]) { 259 | int returnVal = iconChooser.showOpenDialog(frame); 260 | if (returnVal != JFileChooser.APPROVE_OPTION) { 261 | iconButton[selectedIcon].setSelected(true); 262 | return; 263 | } 264 | 265 | try { 266 | File f = iconChooser.getSelectedFile(); 267 | System.out.println(f); 268 | selectedIcon = icon.length - 1; 269 | ImageIcon i = new ImageIcon(f.getCanonicalPath()); 270 | if (i.getIconHeight() > 80 || i.getIconHeight() < 16 || i.getIconWidth() > 80 || i.getIconWidth() < 16) 271 | throw new RuntimeException(i.getIconHeight() + " " + i.getIconWidth()); 272 | 273 | iconData[icon.length - 1] = readAll(new FileInputStream(f)); 274 | icon[icon.length - 1] = i; 275 | iconLabel.setIcon(icon[icon.length - 1]); 276 | } catch (Exception ex) { 277 | iconButton[1].setSelected(true); 278 | iconLabel.setIcon(icon[1]); 279 | ex.printStackTrace(); 280 | JOptionPane.showMessageDialog(frame, "Please select an image file with a size between 16x16 and 80x80 pixels.", 281 | "MeBoy Error", JOptionPane.ERROR_MESSAGE, icon[2]); 282 | } 283 | } 284 | } 285 | 286 | private String cleanName(String name) { 287 | name = name.trim(); 288 | if (name.length() > 24) 289 | name = name.substring(0, 24); 290 | 291 | // replace all control characters with underscore 292 | StringBuilder sb = new StringBuilder(name); 293 | for (int i = 0; i < sb.length(); i++) 294 | if (sb.charAt(i) < 31 || sb.charAt(i) == '/') 295 | sb.setCharAt(i, '_'); 296 | name = sb.toString(); 297 | return name; 298 | } 299 | 300 | private Game findByDisplayName(String displayName) { 301 | for (Game g : games) 302 | if (g.displayName.equals(displayName)) 303 | return g; 304 | return null; 305 | } 306 | 307 | private Game findByCartID(String cartID) { 308 | for (Game g : games) 309 | if (g.cartID.equals(cartID)) 310 | return g; 311 | return null; 312 | } 313 | 314 | private byte[] readAll(InputStream is) throws IOException { 315 | int bufIndex = 0; 316 | byte[] buffer = new byte[4 << 20]; 317 | int r; 318 | do { 319 | r = is.read(buffer, bufIndex, buffer.length - bufIndex); 320 | if (r > 0) 321 | bufIndex += r; 322 | } while (bufIndex < buffer.length && r >= 0); 323 | is.close(); 324 | byte[] result = new byte[bufIndex]; 325 | System.arraycopy(buffer, 0, result, 0, bufIndex); 326 | return result; 327 | } 328 | 329 | private void addGame() throws Exception { 330 | int returnVal = chooser.showOpenDialog(frame); 331 | if (returnVal != JFileChooser.APPROVE_OPTION) 332 | return; 333 | 334 | File f = chooser.getSelectedFile(); 335 | InputStream fi; 336 | 337 | String fileAndDisplayName = f.getName(); 338 | 339 | if (fileAndDisplayName.endsWith(".zip")) { 340 | ZipInputStream zin = new ZipInputStream(new FileInputStream(f)); 341 | fi = zin; 342 | // replace the name with the name of the first zipped file 343 | fileAndDisplayName = zin.getNextEntry().getName(); 344 | } else { 345 | // normal, non-zipped file 346 | fi = new FileInputStream(f); 347 | } 348 | 349 | String uncleanName = fileAndDisplayName; 350 | fileAndDisplayName = cleanName(fileAndDisplayName); 351 | 352 | // make sure name is unique 353 | if (findByDisplayName(fileAndDisplayName) != null) { 354 | int ix = 2; 355 | while (findByDisplayName(fileAndDisplayName + "-" + ix) != null) 356 | ix++; 357 | fileAndDisplayName = fileAndDisplayName + "-" + ix; 358 | } 359 | 360 | String error = null; 361 | 362 | // read to buffer 363 | byte[] buffer = readAll(fi); 364 | 365 | // do sanity check 366 | boolean sane = true; 367 | 368 | if (buffer.length < 0x200 || buffer.length < f.length()) { 369 | error = uncleanName + " does not seem to be a valid ROM file, and it will not added."; 370 | System.out.println("length: " + buffer.length); 371 | sane = false; 372 | } 373 | 374 | // sanity check the cartridge file 375 | int cartSize = sane ? lookUpCartSize(buffer[0x0148] & 0xff) : -1; 376 | if (sane && (cartSize == -1 || (buffer[0x0104] != (byte) 0xCE) 377 | || (buffer[0x010d] != (byte) 0x73) || (buffer[0x0118] != (byte) 0x88))) { 378 | error = uncleanName + " does not seem to be a valid ROM file, and it will not added."; 379 | System.out.println("cartSize " + cartSize); 380 | System.out.println("104 " + buffer[0x0104]); 381 | System.out.println("10d " + buffer[0x010d]); 382 | System.out.println("118 " + buffer[0x0118]); 383 | System.out.println("148 " + buffer[0x0148]); 384 | sane = false; 385 | 386 | if (uncleanName.endsWith(".gba")) 387 | error += " Please note that MeBoy does not emulate the Gameboy Advance."; 388 | if (uncleanName.endsWith(".nds")) 389 | error += " Please note that MeBoy does not emulate the Nintendo DS."; 390 | } 391 | 392 | String identifier = ""; 393 | if (sane) { 394 | for (int i = 0x134; i < 0x143; i++) 395 | if (buffer[i] >= 31 && buffer[i] < 128) 396 | identifier = identifier + (char) buffer[i]; 397 | if (findByCartID(identifier) != null) { 398 | sane = false; 399 | error = "This game has already been added (" + findByCartID(identifier).displayName + ")."; 400 | } 401 | } 402 | 403 | if (sane) { 404 | Game g = new Game(); 405 | g.data = buffer; 406 | g.displayName = fileAndDisplayName; 407 | g.fileName = fileAndDisplayName; 408 | g.cartID = identifier; 409 | games.add(g); 410 | Collections.sort(games); 411 | dirty = true; 412 | } else { 413 | JOptionPane.showMessageDialog(null, string2JTA(error), "MeBoyBuilder warning", 414 | JOptionPane.WARNING_MESSAGE, icon[2]); 415 | } 416 | } 417 | 418 | private void createJar() throws IOException { 419 | JFileChooser saveChooser = new JFileChooser(); 420 | saveChooser.setSelectedFile(new File("MeBoy.jar")); 421 | int returnVal = saveChooser.showSaveDialog(frame); 422 | if (returnVal != JFileChooser.APPROVE_OPTION) 423 | return; 424 | 425 | File f = saveChooser.getSelectedFile(); 426 | 427 | String midletName = f.getName(); 428 | if (midletName.length() > 4 && midletName.toLowerCase().endsWith(".jar")) 429 | midletName = midletName.substring(0, midletName.length() - 4); 430 | 431 | if (!midletName.equals("MeBoy")) { 432 | String q = "You have selected a different name than MeBoy. Mobile phones " 433 | + "use the name to identify applications, and settings and saved games " 434 | + "can not be shared between applications with different names. Do " 435 | + "you want to use the name \"" + midletName + "\"?"; 436 | 437 | String a1 = "Use MeBoy"; 438 | String a2 = "Use " + midletName; 439 | 440 | int response = JOptionPane.showOptionDialog(frame, string2JTA(q), "MeBoyBuilder 2.1", 0, 441 | JOptionPane.PLAIN_MESSAGE, icon[2], new Object[] { a1, a2}, a1); 442 | 443 | if (response == -1) 444 | return; 445 | 446 | if (response == 0) 447 | midletName = "MeBoy"; 448 | } 449 | 450 | f = new File(f.getParent(), midletName + ".jar"); 451 | 452 | ZipOutputStream zo = new ZipOutputStream(new FileOutputStream(f)); 453 | 454 | ArrayList cartsTxt = new ArrayList(); 455 | 456 | for (Game g : games) { 457 | cartsTxt.add(g.displayName); 458 | cartsTxt.add(g.fileName); 459 | cartsTxt.add(g.cartID); 460 | 461 | int maxFileSize = 1 << 17; // for each split file 462 | 463 | for (int j = 0; j < Math.max(1, g.data.length / maxFileSize); j++) { 464 | zo.putNextEntry(new ZipEntry(g.cartID + j)); 465 | zo.write(g.data, j * maxFileSize, Math.min(maxFileSize, g.data.length - j * maxFileSize)); 466 | } 467 | } 468 | 469 | // copy MeBoy files 470 | String[] files = new String[] { 471 | "AdvancedGraphicsChip.class", 472 | "Bluetooth.class", 473 | "Dmgcpu.class", 474 | "GBCanvas.class", 475 | "GraphicsChip.class", 476 | "MeBoy.class", 477 | "SimpleGraphicsChip.class", 478 | "lang/index.txt", "lang/0.txt"}; 479 | for (String copyName : files) { 480 | InputStream tis = ClassLoader.getSystemClassLoader().getResourceAsStream(copyName); 481 | 482 | if (tis == null) { 483 | fatalException(new IOException("The file " + copyName + " could not be copied to " 484 | + "the output file, and MeBoy.jar will not function correctly.")); 485 | } 486 | 487 | zo.putNextEntry(new ZipEntry(copyName)); 488 | byte[] buf = new byte[100000]; 489 | int r = tis.read(buf, 0, 100000); 490 | while (r >= 0) { 491 | if (r > 0) 492 | zo.write(buf, 0, r); 493 | r = tis.read(buf, 0, 100000); 494 | } 495 | tis.close(); 496 | } 497 | // optional files (lang): 498 | for (int i = 1; i < 25; i++) { 499 | String copyName = "lang/" + i + ".txt"; 500 | InputStream tis = ClassLoader.getSystemClassLoader().getResourceAsStream(copyName); 501 | 502 | if (tis == null) { 503 | continue; 504 | } 505 | 506 | zo.putNextEntry(new ZipEntry(copyName)); 507 | byte[] buf = new byte[100000]; 508 | int r = tis.read(buf, 0, 100000); 509 | while (r >= 0) { 510 | if (r > 0) 511 | zo.write(buf, 0, r); 512 | r = tis.read(buf, 0, 100000); 513 | } 514 | tis.close(); 515 | } 516 | 517 | // add icon 518 | zo.putNextEntry(new ZipEntry("meboy.png")); 519 | zo.write(iconData[selectedIcon], 0, iconData[selectedIcon].length); 520 | 521 | // add carts 522 | zo.putNextEntry(new ZipEntry("carts")); 523 | 524 | DataOutputStream dos = new DataOutputStream(zo); 525 | dos.writeInt(cartsTxt.size() / 3); 526 | for (String s : cartsTxt) 527 | dos.writeUTF(s); 528 | dos.flush(); 529 | 530 | // add manifest 531 | zo.putNextEntry(new ZipEntry("META-INF/MANIFEST.MF")); 532 | PrintWriter pw = new PrintWriter(zo); 533 | pw.write("MIDlet-1: " + midletName + ", meboy.png, MeBoy\n"); 534 | pw.write("MIDlet-Name: " + midletName + "\n"); 535 | pw.write("MIDlet-Vendor: Bjorn Carlin, www.arktos.se\n"); 536 | pw.write("MIDlet-Version: 2.1.0\n"); 537 | pw.write("MIDlet-Description: Gameboy emulator for J2ME\n"); 538 | pw.write("MicroEdition-Configuration: CLDC-1.1\n"); 539 | pw.write("MicroEdition-Profile: MIDP-2.0\n"); 540 | pw.flush(); 541 | zo.close(); 542 | 543 | // create .jad 544 | pw = new PrintWriter(new FileWriter(new File(f.getParent(), midletName + ".jad"))); 545 | pw.write("MIDlet-1: " + midletName + ", meboy.png, MeBoy\n"); 546 | pw.write("MIDlet-Name: " + midletName + "\n"); 547 | pw.write("MIDlet-Vendor: Bjorn Carlin, www.arktos.se\n"); 548 | pw.write("MIDlet-Version: 2.1.0\n"); 549 | pw.write("MIDlet-Description: Gameboy emulator for J2ME\n"); 550 | pw.write("MicroEdition-Configuration: CLDC-1.1\n"); 551 | pw.write("MicroEdition-Profile: MIDP-2.0\n"); 552 | pw.write("MIDlet-Jar-URL: " + midletName + ".jar\n"); 553 | pw.write("MIDlet-Jar-Size: " + f.length() + '\n'); 554 | pw.close(); 555 | 556 | dirty = false; 557 | 558 | JOptionPane.showMessageDialog(frame, string2JTA("The files " + midletName + ".jar and " 559 | + midletName + ".jad have been created.\n" 560 | + "Most phones only require a \".jar\" file, so try to transfer " + midletName 561 | + ".jar to your phone.\n" + "- If your phone accepted " + midletName 562 | + ".jar, you do not need " + midletName + ".jad and can safely throw it away.\n" 563 | + "- If your phone requires a \".jad\" file, transfer " + midletName 564 | + ".jad instead."), "MeBoyBuilder Finished", JOptionPane.INFORMATION_MESSAGE, icon[2]); 565 | } 566 | 567 | private static void fatalException(Exception ex) { 568 | ex.printStackTrace(); 569 | String msg = "Unfortunately, an error has ocurred that prevents MeBoyBuilder" + 570 | " from continuing.\n"; 571 | int choice = JOptionPane.showOptionDialog(null, msg, "MeBoy Error", JOptionPane.YES_NO_OPTION, 572 | JOptionPane.ERROR_MESSAGE, optionIcon, new String[] {"Quit", "More Info"}, "Quit"); 573 | if (choice == 1) { 574 | StringWriter sw = new StringWriter(); 575 | ex.printStackTrace(new PrintWriter(sw)); 576 | JScrollPane p = new JScrollPane(string2JTA(msg + sw.toString())); 577 | p.setPreferredSize(new Dimension(800, 600)); 578 | JOptionPane.showMessageDialog(null, p); 579 | } 580 | System.exit(0); 581 | } 582 | 583 | static void nonFatalException(Exception ex, String message) { 584 | ex.printStackTrace(); 585 | int choice = JOptionPane.showOptionDialog(null, message, "MeBoy Error", JOptionPane.YES_NO_OPTION, 586 | JOptionPane.ERROR_MESSAGE, optionIcon, new String[] {"Close", "More Info"}, "Close"); 587 | if (choice == 1) { 588 | StringWriter sw = new StringWriter(); 589 | ex.printStackTrace(new PrintWriter(sw)); 590 | JScrollPane p = new JScrollPane(string2JTA(message + sw.toString())); 591 | p.setPreferredSize(new Dimension(800, 600)); 592 | JOptionPane.showMessageDialog(null, p); 593 | } 594 | } 595 | 596 | private static void checkForUpdates() { 597 | Preferences prefs = Preferences.userNodeForPackage(MeBoyBuilder.class).node("meboybuilder"); 598 | String checkPrefs = prefs.get("CheckUpdates", null); 599 | 600 | boolean firstLaunch = checkPrefs == null; 601 | 602 | if (firstLaunch) { 603 | int response = JOptionPane 604 | .showOptionDialog( 605 | null, 606 | string2JTA("Do you want MeBoyBuilder to automatically check for program updates when launched?"), 607 | "MeBoyBuilder 2.1", 0, JOptionPane.PLAIN_MESSAGE, optionIcon, new Object[] { 608 | "Don't check", "Check"}, "Check"); 609 | 610 | if (response == 1) { 611 | prefs.put("CheckUpdates", "true"); 612 | checkPrefs = "true"; 613 | } else if (response == 0) { 614 | prefs.put("CheckUpdates", "false"); 615 | } 616 | } 617 | 618 | if ("true".equals(checkPrefs)) { 619 | long now = System.currentTimeMillis(); 620 | long lastTime = prefs.getLong("LastTime", now - 1000 * 60 * 60 * 24); 621 | if (lastTime + 1000 * 60 * 60 * 24 > now) { 622 | return; // less than a day since last check 623 | } 624 | prefs.putLong("LastTime", now); 625 | 626 | try { 627 | // read info from home page 628 | URL inputURL = new URL("http://arktos.se/meboy/update/210.txt"); 629 | URLConnection urlConnection = inputURL.openConnection(); 630 | urlConnection.connect(); 631 | 632 | BufferedInputStream in = new BufferedInputStream(urlConnection.getInputStream()); 633 | byte[] buf = new byte[65536]; 634 | int done = 0; 635 | 636 | int r = in.read(buf, 0, 65536); 637 | while (r >= 0) { 638 | done += r; 639 | r = in.read(buf, done, 65536 - done); 640 | } 641 | 642 | if (done > 1) { 643 | String message = new String(buf, 0, done); 644 | 645 | int response = JOptionPane.showOptionDialog(null, 646 | string2JTA(message.toString()), "MeBoyBuilder 2.1", 0, 647 | JOptionPane.PLAIN_MESSAGE, optionIcon, new Object[] { "Cancel", 648 | "Visit homepage"}, "Visit homepage"); 649 | 650 | if (response == 1) { 651 | openURL("http://arktos.se/meboy/"); 652 | } 653 | } 654 | } catch (Exception e) { 655 | // not really important, just can't check for updates... 656 | e.printStackTrace(); 657 | } 658 | } 659 | } 660 | 661 | private static JTextArea string2JTA(String message) { 662 | JTextArea jta = new JTextArea(message, 1 + message.length() / 40, 40); 663 | 664 | jta.setLineWrap(true); 665 | jta.setWrapStyleWord(true); 666 | jta.setEditable(false); 667 | jta.setBackground(new java.awt.Color(0, true)); 668 | return jta; 669 | } 670 | 671 | private static int lookUpCartSize(int sizeByte) { 672 | /** Translation between ROM size byte contained in the ROM header, and the number 673 | * of 16Kb ROM banks the cartridge will contain 674 | */ 675 | if (sizeByte < 8) 676 | return 2 << sizeByte; 677 | else if (sizeByte == 0x52) 678 | return 72; 679 | else if (sizeByte == 0x53) 680 | return 80; 681 | else if (sizeByte == 0x54) 682 | return 96; 683 | return -1; 684 | } 685 | 686 | 687 | ///////////////////////////////////////////////////////// 688 | // Bare Bones Browser Launch // 689 | // Version 1.5 // 690 | // December 10, 2005 // 691 | // Supports: Mac OS X, GNU/Linux, Unix, Windows XP // 692 | // Example Usage: // 693 | // String url = "http://www.centerkey.com/"; // 694 | // BareBonesBrowserLaunch.openURL(url); // 695 | // Public Domain Software -- Free to Use as You Like // 696 | ///////////////////////////////////////////////////////// 697 | private static void openURL(String url) throws Exception { 698 | String osName = System.getProperty("os.name"); 699 | if (osName.startsWith("Mac OS")) { 700 | Class fileMgr = Class.forName("com.apple.eio.FileManager"); 701 | Method openURL = fileMgr.getDeclaredMethod("openURL", new Class[] { String.class}); 702 | openURL.invoke(null, new Object[] { url}); 703 | } else if (osName.startsWith("Windows")) 704 | Runtime.getRuntime().exec("rundll32 url.dll,FileProtocolHandler " + url); 705 | else { //assume Unix or Linux 706 | String[] browsers = { "firefox", "opera", "konqueror", "epiphany", "mozilla", 707 | "netscape"}; 708 | String browser = null; 709 | for (int count = 0; count < browsers.length && browser == null; count++) 710 | if (Runtime.getRuntime().exec(new String[] { "which", browsers[count]}).waitFor() == 0) 711 | browser = browsers[count]; 712 | if (browser == null) 713 | throw new Exception("Could not find web browser"); 714 | else 715 | Runtime.getRuntime().exec(new String[] { browser, url}); 716 | } 717 | } 718 | 719 | public void windowActivated(WindowEvent e) {} 720 | 721 | public void windowClosed(WindowEvent e) {} 722 | 723 | public void windowDeactivated(WindowEvent e) {} 724 | 725 | public void windowDeiconified(WindowEvent e) {} 726 | 727 | public void windowIconified(WindowEvent e) {} 728 | 729 | public void windowOpened(WindowEvent e) {} 730 | } 731 | 732 | class Game implements Comparable { 733 | String displayName; 734 | String cartID; 735 | String fileName; 736 | byte[] data; 737 | String label() { 738 | return displayName + ", " + data.length / 1024 + " kB"; 739 | } 740 | 741 | public int compareTo(Object o) { 742 | return displayName.toLowerCase().compareTo(((Game) o).displayName.toLowerCase()); 743 | } 744 | } 745 | 746 | class ImageFilter extends FileFilter { 747 | public boolean accept(File f) { 748 | return f.isDirectory() || f.getName().toLowerCase().endsWith(".png") || f.getName().toLowerCase().endsWith(".gif") || 749 | f.getName().toLowerCase().endsWith(".jpg") || f.getName().toLowerCase().endsWith(".jpeg"); 750 | } 751 | 752 | public String getDescription() { 753 | return "PNG/GIF/JPG Files"; 754 | } 755 | } 756 | -------------------------------------------------------------------------------- /MeBoy.java: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | MeBoy 4 | 5 | Copyright 2005-2009 Bjorn Carlin 6 | http://www.arktos.se/ 7 | 8 | Based on JavaBoy, COPYRIGHT (C) 2001 Neil Millstone and The Victoria 9 | University of Manchester. Bluetooth support based on code contributed by 10 | Martin Neumann. 11 | 12 | This program is free software; you can redistribute it and/or modify it 13 | under the terms of the GNU General Public License as published by the Free 14 | Software Foundation; either version 2 of the License, or (at your option) 15 | any later version. 16 | 17 | This program is distributed in the hope that it will be useful, but WITHOUT 18 | ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 19 | FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for 20 | more details. 21 | 22 | 23 | You should have received a copy of the GNU General Public License along with 24 | this program; if not, write to the Free Software Foundation, Inc., 59 Temple 25 | Place - Suite 330, Boston, MA 02111-1307, USA. 26 | 27 | */ 28 | 29 | import java.io.*; 30 | import java.util.*; 31 | import javax.microedition.lcdui.*; 32 | import javax.microedition.midlet.*; 33 | import javax.microedition.rms.*; 34 | 35 | /** 36 | * The main class offers a list of games, read from the file "carts.txt". It 37 | * also handles logging. 38 | */ 39 | public class MeBoy extends MIDlet implements CommandListener { 40 | // Settings, etc. 41 | public static final boolean debug = false; 42 | public static int rotations = 0; 43 | public static int maxFrameSkip = 3; 44 | public static boolean enableScaling = true; 45 | public static int scalingMode = 0; 46 | public static boolean keepProportions = true; 47 | public static boolean fullScreen = false; 48 | public static boolean disableColor = false; 49 | public static boolean enableSound = false; 50 | public static boolean advancedSound = false; 51 | public static boolean advancedGraphics = false; 52 | public static boolean showFps = false; 53 | public static boolean showLogItem = false; 54 | public static int lazyLoadingThreshold = 64; // number of banks, each 0x4000 bytes = 16kB 55 | public static int language; 56 | private static boolean bluetoothAvailable = false; 57 | 58 | public static int suspendCounter = 1; // next index for saved games 59 | public static String[] suspendName10 = new String[0]; // v1-style suspended games 60 | public static String[] suspendName20 = new String[0]; // v2-style suspended games 61 | public static String[] literal = new String[80]; 62 | private static int languageCount = 1; 63 | private static String[] languages = new String[] {"English"}; // if index.txt fails 64 | private static int[] languageLookup = new int[] {0}; 65 | private String[] cartDisplayName = null; 66 | private String[] cartFileName = null; 67 | private String[] cartID = null; 68 | private int numCarts; 69 | private boolean fatalError; 70 | 71 | public static String logString = ""; 72 | private static MeBoy instance; 73 | 74 | // UI components 75 | public static Display display; 76 | private List mainMenu; 77 | private Form messageForm; 78 | private GBCanvas gbCanvas; 79 | private List cartList; 80 | private List suspendList; 81 | 82 | // Settings 83 | private Form settingsForm; 84 | private TextField frameSkipField; 85 | private TextField rotationField; 86 | private TextField loadThresholdField; 87 | private TextField scalingModeField; 88 | private ChoiceGroup graphicsGroup; 89 | private ChoiceGroup miscSettingsGroup; 90 | private ChoiceGroup soundGroup; 91 | private ChoiceGroup languageGroup; 92 | 93 | private Bluetooth bluetooth; 94 | 95 | 96 | public void startApp() { 97 | if (instance == this) { 98 | return; 99 | } 100 | 101 | try { 102 | bluetoothAvailable = Bluetooth.available(); 103 | } catch (NoClassDefFoundError t) { 104 | } 105 | 106 | instance = this; 107 | display = Display.getDisplay(this); 108 | 109 | GBCanvas.readSettings(); 110 | 111 | readLangIndexFile(); 112 | 113 | if (!readLiteralsFile()) 114 | return; 115 | if (!readCartNames()) 116 | return; 117 | if (!upgradeSavegames()) 118 | return; 119 | 120 | showMainMenu(); 121 | } 122 | 123 | private boolean readLiteralsFile() { 124 | try { 125 | InputStreamReader isr = new InputStreamReader(getClass().getResourceAsStream("/lang/" + language + ".txt"), "UTF-8"); 126 | int counter = 0; 127 | String s; 128 | while ((s = next(isr)) != null) 129 | literal[counter++] = s; 130 | isr.close(); 131 | while (counter < literal.length) 132 | literal[counter++] = "?"; 133 | } catch (Exception e) { 134 | e.printStackTrace(); 135 | if (language > 0) { 136 | language = 0; 137 | return readLiteralsFile(); 138 | } 139 | 140 | showError("Failed to read the language file.", null, e); 141 | fatalError = true; 142 | return false; 143 | } 144 | return true; 145 | } 146 | 147 | private void readLangIndexFile() { 148 | try { 149 | InputStreamReader isr = new InputStreamReader(getClass().getResourceAsStream("/lang/index.txt"), "UTF-8"); 150 | 151 | String s; 152 | languageLookup = new int[20]; 153 | Vector langVector = new Vector(); 154 | languageCount = 0; 155 | while ((s = next(isr)) != null) { 156 | languageLookup[languageCount++] = s.charAt(0) - 'a'; 157 | langVector.addElement(next(isr)); 158 | } 159 | languages = new String[languageCount]; 160 | langVector.copyInto(languages); 161 | 162 | isr.close(); 163 | } catch (Exception e) { 164 | languages = new String[] {"English"}; 165 | languageLookup = new int[] {0}; 166 | languageCount = 1; 167 | 168 | if (debug) { 169 | e.printStackTrace(); 170 | } 171 | } 172 | } 173 | 174 | private String next(InputStreamReader isr) throws IOException { 175 | StringBuffer sb = new StringBuffer(); 176 | int c; 177 | while ((c = isr.read()) != -1) { 178 | if (c >= 32) { 179 | sb.append((char) c); 180 | } else if (sb.length() > 0) { 181 | return sb.toString(); 182 | } 183 | } 184 | if (sb.length() > 0) { 185 | return sb.toString(); 186 | } 187 | return null; 188 | } 189 | 190 | private boolean readCartNames() { 191 | try { 192 | InputStream is = getClass().getResourceAsStream("/carts"); 193 | DataInputStream dis = new DataInputStream(is); 194 | 195 | numCarts = dis.readInt(); 196 | 197 | cartDisplayName = new String[numCarts]; 198 | cartFileName = new String[numCarts]; 199 | cartID = new String[numCarts]; 200 | 201 | for (int i = 0; i < numCarts; i++) { 202 | cartDisplayName[i] = dis.readUTF(); 203 | cartFileName[i] = dis.readUTF(); 204 | cartID[i] = dis.readUTF(); 205 | } 206 | is.close(); 207 | } catch (Exception e) { 208 | fatalError = true; 209 | showError(literal[51], null, e); 210 | return false; 211 | } 212 | return true; 213 | } 214 | 215 | public boolean upgradeSavegames() { 216 | // upgrade cart ram: 217 | for (int i = 0; i < numCarts; i++) { 218 | String oldSaveName = cartFileName[i]; 219 | String newSaveName = cartID[i]; 220 | 221 | try { 222 | RecordStore rs = RecordStore.openRecordStore(oldSaveName, false); 223 | if (rs.getNumRecords() > 0) { 224 | byte[] b = rs.getRecord(1); 225 | 226 | RecordStore rs2 = RecordStore.openRecordStore("20R_" + newSaveName, true); 227 | 228 | if (rs2.getNumRecords() == 0) { 229 | rs2.addRecord(b, 0, b.length); 230 | } else { 231 | // Don't overwrite the new-style saveram 232 | } 233 | 234 | rs2.closeRecordStore(); 235 | rs.deleteRecord(1); // in case of exception, prevent overwriting new 236 | } 237 | rs.closeRecordStore(); 238 | } catch (Exception e) { 239 | } 240 | } 241 | 242 | try { 243 | // upgrade suspended games: 244 | for (int i = 0; i < suspendName10.length; ) { 245 | if (upgradeSuspendedGame(i)) { 246 | // don't update i, since the old entry has been removed 247 | } else { 248 | log("Could not upgrade " + suspendName10[i]); 249 | i++; 250 | } 251 | } 252 | } catch (Exception e) { 253 | fatalError = true; 254 | showError(literal[50], null, e); 255 | return false; 256 | } 257 | return true; 258 | } 259 | 260 | private boolean upgradeSuspendedGame(int index) throws RecordStoreException { 261 | boolean suspendName10Shrunk = false; 262 | String suspendName = suspendName10[index]; 263 | RecordStore rs = RecordStore.openRecordStore("s" + suspendName, false); 264 | byte[] oldStyle = rs.getRecord(1); 265 | 266 | StringBuffer sb = new StringBuffer(); 267 | int j = 0; 268 | while (oldStyle[j] != 0) { 269 | sb.append((char) oldStyle[j]); 270 | j++; 271 | } 272 | 273 | String cartFileNameTemp = sb.toString(); 274 | 275 | // see if we can find the corresponding cart filename 276 | for (int k = 0; k < numCarts; k++) { 277 | if (cartFileNameTemp.equals(cartFileName[k])) { 278 | // remove suspended game from 1.0-style index: 279 | String[] oldNameIndex = suspendName10; 280 | suspendName10 = new String[oldNameIndex.length - 1]; 281 | System.arraycopy(oldNameIndex, 0, suspendName10, 0, index); 282 | System.arraycopy(oldNameIndex, index + 1, suspendName10, index, 283 | suspendName10.length - index); 284 | suspendName10Shrunk = true; 285 | GBCanvas.writeSettings(); 286 | 287 | // delete the old suspended game 288 | rs.deleteRecord(1); 289 | rs.closeRecordStore(); 290 | rs = null; 291 | 292 | // write the new suspended game 293 | RecordStore rs2 = RecordStore.openRecordStore("20S_" + suspendName, true); 294 | 295 | boolean addToIndex = false; 296 | if (rs2.getNumRecords() == 0) { 297 | // copy oldstyle to newstyle savegame buffers 298 | byte[] cartIDBuffer = cartID[k].getBytes(); 299 | 300 | boolean gbcFeatures; 301 | int gbSizeModBank = cartFileNameTemp.length() + 629; 302 | if ((oldStyle.length - gbSizeModBank) % 0x2000 == 132) { 303 | gbcFeatures = true; 304 | } else if ((oldStyle.length - gbSizeModBank) % 0x2000 == 0) { 305 | gbcFeatures = false; 306 | } else { 307 | rs2.closeRecordStore(); 308 | throw new RuntimeException("1 " + oldStyle.length + " " + gbSizeModBank); 309 | } 310 | 311 | int sizeDiff = 107 + cartFileNameTemp.length() + (gbcFeatures ? 1 : 0); 312 | byte[] newStyle = new byte[oldStyle.length - sizeDiff]; 313 | 314 | int newIndex = 0; 315 | int oldIndex = cartFileNameTemp.length() + 1; 316 | newStyle[newIndex++] = 1; // version 317 | newStyle[newIndex++] = (byte) (gbcFeatures ? 1 : 0); 318 | 319 | // registers + 3 interrupt times 320 | System.arraycopy(oldStyle, oldIndex, newStyle, newIndex, 24); 321 | newIndex += 24; 322 | oldIndex += 24; 323 | 324 | // nextTimaOverflow, nextInterruptEnabled 325 | int nextTimaOverflow = GBCanvas.getInt(oldStyle, oldIndex-4); 326 | int nextInterruptEnable = GBCanvas.getInt(oldStyle, oldIndex); 327 | oldIndex += 4; 328 | 329 | // nextTimedInterrupt, version 330 | System.arraycopy(oldStyle, oldIndex, newStyle, newIndex, 4); 331 | newIndex += 4; 332 | oldIndex += 5; 333 | 334 | newStyle[newIndex++] = (byte) ((nextTimaOverflow == 0x7fffffff) ? 1 : 0); 335 | newStyle[newIndex++] = 0; // graphicsChipMode 336 | newStyle[newIndex++] = oldStyle[oldIndex++]; // interruptsEnabled 337 | newStyle[newIndex++] = oldStyle[oldIndex++]; // interruptsArmed 338 | newStyle[newIndex++] = (byte) ((nextInterruptEnable == 0x7fffffff) ? 1 : 0); 339 | 340 | // mainRam + oam (0x100 bytes -> 0xa0) 341 | int mainRamLength = gbcFeatures ? 0x8000 : 0x2000; 342 | System.arraycopy(oldStyle, oldIndex, newStyle, newIndex, mainRamLength + 0xa0); 343 | newIndex += mainRamLength + 0xa0; 344 | oldIndex += mainRamLength + 0x100; 345 | 346 | // registers, divReset, instrsPerTima, (cartType) 347 | System.arraycopy(oldStyle, oldIndex, newStyle, newIndex, 0x108); 348 | newIndex += 0x108; 349 | oldIndex += 0x108 + 4; 350 | 351 | if (gbcFeatures) { 352 | int untilNextSkip = newStyle.length - newIndex - 131; 353 | System.arraycopy(oldStyle, oldIndex, newStyle, newIndex, untilNextSkip); 354 | newIndex += untilNextSkip; 355 | oldIndex += untilNextSkip + 7; 356 | 357 | System.arraycopy(oldStyle, oldIndex, newStyle, newIndex, 131); 358 | newIndex += 131; 359 | oldIndex += 131; 360 | } else { 361 | int untilNextSkip = newStyle.length - newIndex; 362 | System.arraycopy(oldStyle, oldIndex, newStyle, newIndex, untilNextSkip); 363 | newIndex += untilNextSkip; 364 | oldIndex += untilNextSkip + 6; 365 | } 366 | 367 | if (oldIndex != oldStyle.length) { 368 | rs2.closeRecordStore(); 369 | throw new RuntimeException(suspendName + " " + oldIndex + "/" + oldStyle.length); 370 | } 371 | 372 | rs2.addRecord(cartIDBuffer, 0, cartIDBuffer.length); 373 | rs2.addRecord(newStyle, 0, newStyle.length); 374 | addToIndex = true; 375 | } else { 376 | // Don't overwrite new-style suspended game, and don't add to index 377 | } 378 | rs2.closeRecordStore(); 379 | 380 | if (addToIndex) { 381 | // update 2.0-style index: 382 | addSuspendedGame(suspendName); 383 | } 384 | break; // don't keep looking for filename match 385 | } 386 | } 387 | if (rs != null) 388 | rs.closeRecordStore(); 389 | return suspendName10Shrunk; 390 | } 391 | 392 | public void pauseApp() { 393 | if (gbCanvas != null) { 394 | gbCanvas.pause(); 395 | } 396 | } 397 | 398 | public void destroyApp(boolean unconditional) { 399 | } 400 | 401 | public void unloadCart() { 402 | showMainMenu(); 403 | gbCanvas.releaseReferences(); 404 | gbCanvas = null; 405 | } 406 | 407 | private static String formatDetails(String details, Throwable exception) { 408 | if (debug && exception != null) { 409 | exception.printStackTrace(); 410 | } 411 | 412 | if (exception != null) { 413 | if (details == null || details.length() == 0) 414 | details = exception.toString(); 415 | else 416 | details += ", " + exception; 417 | } 418 | if (details != null && details.length() > 0) 419 | return " (" + details + ")"; 420 | return ""; 421 | } 422 | 423 | public static void showError(String message, String details, Throwable exception) { 424 | if (message == null) 425 | message = literal[47]; 426 | instance.showMessage(literal[46], message + formatDetails(details, exception)); 427 | if (instance.gbCanvas != null) 428 | instance.gbCanvas.releaseReferences(); 429 | instance.gbCanvas = null; 430 | } 431 | 432 | public static void showLog() { 433 | instance.showMessage(literal[28], logString); 434 | } 435 | 436 | public void showMessage(String title, String message) { 437 | messageForm = new Form(title); 438 | messageForm.append(message); 439 | messageForm.setCommandListener(this); 440 | messageForm.addCommand(new Command(literal[10], Command.BACK, 0)); 441 | display.setCurrent(messageForm); 442 | } 443 | 444 | private void messageCommand() { 445 | if (fatalError) { 446 | destroyApp(true); 447 | notifyDestroyed(); 448 | } else { 449 | messageForm = null; 450 | showMainMenu(); 451 | } 452 | } 453 | 454 | private void showMainMenu() { 455 | mainMenu = new List("MeBoy 2.2", List.IMPLICIT); 456 | mainMenu.append(literal[0], null); 457 | if (suspendName20.length > 0) { 458 | mainMenu.append(literal[1], null); 459 | } 460 | mainMenu.append(literal[2], null); 461 | if (bluetoothAvailable) { 462 | mainMenu.append(literal[4], null); 463 | } 464 | mainMenu.append(literal[5], null); 465 | if (showLogItem) { 466 | mainMenu.append(literal[3], null); 467 | } 468 | mainMenu.append(literal[6], null); 469 | mainMenu.setCommandListener(this); 470 | display.setCurrent(mainMenu); 471 | } 472 | 473 | private void mainMenuCommand(Command com) { 474 | String item = mainMenu.getString(mainMenu.getSelectedIndex()); 475 | if (item == literal[0]) { 476 | showCartList(); 477 | } else if (item == literal[1]) { 478 | showResumeGame(); 479 | } else if (item == literal[2]) { 480 | showSettings(); 481 | } else if (item == literal[4]) { 482 | bluetooth = new Bluetooth(this); 483 | } else if (item == literal[5]) { 484 | showMessage(literal[5], "MeBoy 2.2 © Björn Carlin, 2005-2009.\nhttp://arktos.se/meboy/"); 485 | } else if (item == literal[3]) { 486 | log(literal[29] + " " + Runtime.getRuntime().freeMemory() + "/" + Runtime.getRuntime().totalMemory()); 487 | showLog(); 488 | } else if (item == literal[6]) { 489 | destroyApp(true); 490 | notifyDestroyed(); 491 | } else { 492 | showError(null, "Unknown command: " + com.getLabel(), null); 493 | } 494 | } 495 | 496 | private void showCartList() { 497 | cartList = new List(literal[7], List.IMPLICIT); 498 | 499 | for (int i = 0; i < numCarts; i++) 500 | cartList.append(cartDisplayName[i], null); 501 | 502 | cartList.addCommand(new Command(literal[10], Command.BACK, 1)); 503 | cartList.setCommandListener(this); 504 | display.setCurrent(cartList); 505 | } 506 | 507 | private void cartListCommand(Command com) { 508 | if (com.getCommandType() == Command.BACK) { 509 | cartList = null; 510 | showMainMenu(); 511 | return; 512 | } 513 | 514 | int ix = cartList.getSelectedIndex(); 515 | String selectedCartID = cartID[ix]; 516 | String selectedCartDisplayName = cartDisplayName[ix]; 517 | try { 518 | gbCanvas = new GBCanvas(selectedCartID, this, selectedCartDisplayName); 519 | cartList = null; 520 | display.setCurrent(gbCanvas); 521 | } catch (Exception e) { 522 | showError(null, "error#1", e); 523 | } 524 | } 525 | 526 | private void showResumeGame() { 527 | if (suspendName20.length == 0) { 528 | showMainMenu(); 529 | return; 530 | } 531 | suspendList = new List(literal[7], List.IMPLICIT); 532 | 533 | for (int i = 0; i < suspendName20.length; i++) { 534 | suspendList.append(suspendName20[i], null); 535 | } 536 | suspendList.addCommand(new Command(literal[8], Command.SCREEN, 2)); 537 | suspendList.addCommand(new Command(literal[9], Command.SCREEN, 2)); 538 | suspendList.addCommand(new Command(literal[10], Command.BACK, 1)); 539 | suspendList.setCommandListener(this); 540 | display.setCurrent(suspendList); 541 | } 542 | 543 | private void resumeGameCommand(Command com) { 544 | if (com.getCommandType() == Command.BACK) { 545 | suspendList = null; 546 | showMainMenu(); 547 | return; 548 | } 549 | 550 | String label = com.getLabel(); 551 | int index = suspendList.getSelectedIndex(); 552 | String selectedName = suspendName20[index]; 553 | 554 | if (label == literal[8]) { 555 | try { 556 | // update index: 557 | String[] oldIndex = suspendName20; 558 | suspendName20 = new String[oldIndex.length - 1]; 559 | System.arraycopy(oldIndex, 0, suspendName20, 0, index); 560 | System.arraycopy(oldIndex, index + 1, suspendName20, index, 561 | suspendName20.length - index); 562 | GBCanvas.writeSettings(); 563 | 564 | // delete the state itself 565 | RecordStore.deleteRecordStore("20S_" + selectedName); 566 | } catch (Exception e) { 567 | showError(null, "error#2", e); 568 | } 569 | showResumeGame(); 570 | } else if (label == literal[9]) { 571 | try { 572 | String oldName = selectedName; 573 | String newName = suspendCounter++ + oldName.substring(oldName.indexOf(':')); 574 | 575 | RecordStore rs = RecordStore.openRecordStore("20S_" + oldName, 576 | true); 577 | byte[] b1 = rs.getRecord(1); // cartid 578 | byte[] b2 = rs.getRecord(2); // data 579 | rs.closeRecordStore(); 580 | 581 | rs = RecordStore.openRecordStore("20S_" + newName, true); 582 | rs.addRecord(b1, 0, b1.length); 583 | rs.addRecord(b2, 0, b2.length); 584 | rs.closeRecordStore(); 585 | 586 | addSuspendedGame(newName); 587 | showResumeGame(); 588 | } catch (Exception e) { 589 | showError(null, "error#3", e); 590 | } 591 | } else { 592 | try { 593 | String suspendName = selectedName; 594 | RecordStore rs = RecordStore.openRecordStore("20S_" + suspendName, false); 595 | String suspendCartID = new String(rs.getRecord(1)); 596 | byte[] suspendState = rs.getRecord(2); 597 | rs.closeRecordStore(); 598 | String suspendCartDisplayName = null; 599 | for (int i = 0; i < numCarts; i++) 600 | if (suspendCartID.equals(cartID[i])) 601 | suspendCartDisplayName = cartDisplayName[i]; 602 | if (suspendCartDisplayName == null) { 603 | showError(literal[11], suspendCartID, null); 604 | } else { 605 | gbCanvas = new GBCanvas(suspendCartID, this, suspendCartDisplayName, 606 | suspendName, suspendState); 607 | display.setCurrent(gbCanvas); 608 | } 609 | suspendList = null; 610 | } catch (Exception e) { 611 | showError(null, "error#4", e); 612 | } 613 | } 614 | } 615 | 616 | private void showSettings() { 617 | settingsForm = new Form(literal[2]); 618 | 619 | frameSkipField = new TextField(literal[12], "" + maxFrameSkip, 3, TextField.NUMERIC); 620 | settingsForm.append(frameSkipField); 621 | rotationField = new TextField(literal[13], "" + rotations, 2, TextField.NUMERIC); 622 | settingsForm.append(rotationField); 623 | 624 | graphicsGroup = new ChoiceGroup(literal[14], ChoiceGroup.MULTIPLE, 625 | new String[]{literal[15], literal[16], literal[17]}, null); 626 | graphicsGroup.setSelectedIndex(0, enableScaling); 627 | graphicsGroup.setSelectedIndex(1, keepProportions); 628 | graphicsGroup.setSelectedIndex(2, advancedGraphics); 629 | settingsForm.append(graphicsGroup); 630 | 631 | scalingModeField = new TextField(literal[18], Integer.toString(scalingMode), 2, TextField.NUMERIC); 632 | settingsForm.append(scalingModeField); 633 | 634 | soundGroup = new ChoiceGroup(literal[19], 635 | ChoiceGroup.MULTIPLE, new String[]{literal[20], literal[21]}, 636 | null); 637 | soundGroup.setSelectedIndex(0, enableSound); 638 | soundGroup.setSelectedIndex(1, advancedSound); 639 | settingsForm.append(soundGroup); 640 | 641 | languageGroup = new ChoiceGroup(literal[22], ChoiceGroup.EXCLUSIVE, 642 | languages, null); 643 | for (int i = 0; i < languages.length; i++) 644 | languageGroup.setSelectedIndex(i, language == languageLookup[i]); 645 | settingsForm.append(languageGroup); 646 | 647 | miscSettingsGroup = new ChoiceGroup(literal[23], 648 | ChoiceGroup.MULTIPLE, new String[]{literal[24], literal[25]}, 649 | null); 650 | miscSettingsGroup.setSelectedIndex(0, disableColor); 651 | miscSettingsGroup.setSelectedIndex(1, showLogItem); 652 | settingsForm.append(miscSettingsGroup); 653 | 654 | loadThresholdField = new TextField(literal[26], "" + lazyLoadingThreshold * 16, 5, TextField.NUMERIC); 655 | settingsForm.append(loadThresholdField); 656 | 657 | settingsForm.addCommand(new Command(literal[10], Command.BACK, 0)); 658 | settingsForm.addCommand(new Command(literal[27], Command.OK, 1)); 659 | settingsForm.setCommandListener(this); 660 | display.setCurrent(settingsForm); 661 | } 662 | 663 | private void settingsCommand(Command com) { 664 | if (com.getCommandType() == Command.BACK) { 665 | settingsForm = null; 666 | showMainMenu(); 667 | return; 668 | } 669 | 670 | int f = Integer.parseInt(frameSkipField.getString()); 671 | maxFrameSkip = Math.max(Math.min(f, 59), 0); 672 | rotations = Integer.parseInt(rotationField.getString()) & 3; 673 | lazyLoadingThreshold = Math.max(Integer.parseInt(loadThresholdField.getString()) / 16, 20); 674 | enableScaling = graphicsGroup.isSelected(0); 675 | keepProportions = graphicsGroup.isSelected(1); 676 | advancedGraphics = graphicsGroup.isSelected(2); 677 | f = Integer.parseInt(scalingModeField.getString()); 678 | scalingMode = Math.max(Math.min(f, 3), 0); 679 | disableColor = miscSettingsGroup.isSelected(0); 680 | showLogItem = miscSettingsGroup.isSelected(1); 681 | enableSound = soundGroup.isSelected(0); 682 | advancedSound = soundGroup.isSelected(1); 683 | 684 | int oldLanguage = language; 685 | language = languageLookup[languageGroup.getSelectedIndex()]; 686 | 687 | GBCanvas.writeSettings(); 688 | if (oldLanguage != language) { 689 | readLiteralsFile(); 690 | cartList = null; 691 | } 692 | settingsForm = null; 693 | showMainMenu(); 694 | } 695 | 696 | public void addSavegamesToList(List list, Vector cartIDs, Vector filenames) { 697 | for (int i = 0; i < numCarts; i++) { 698 | try { 699 | RecordStore rs = RecordStore.openRecordStore("20R_" + cartID[i], true); 700 | 701 | if (rs.getNumRecords() > 0) { 702 | list.append(cartDisplayName[i], null); 703 | cartIDs.addElement(cartID[i]); 704 | filenames.addElement(cartFileName[i]); 705 | } 706 | rs.closeRecordStore(); 707 | } catch (Exception e) { 708 | if (MeBoy.debug) { 709 | e.printStackTrace(); 710 | } 711 | MeBoy.log(e.toString()); 712 | } 713 | } 714 | } 715 | 716 | private boolean tearDownBluetooth() { 717 | if (bluetooth != null) { 718 | bluetooth.tearDown(); 719 | bluetooth = null; 720 | return true; 721 | } 722 | return false; 723 | } 724 | 725 | // Cancel without message 726 | public synchronized void cancelBluetooth() { 727 | if (tearDownBluetooth()) 728 | showMainMenu(); 729 | } 730 | 731 | // Cancel with custom message 732 | public synchronized void cancelBluetooth(String message) { 733 | if (tearDownBluetooth()) 734 | showMessage(literal[4], message); 735 | } 736 | 737 | // Cancel with generic message, log exception 738 | public synchronized void cancelBluetooth(Exception e) { 739 | if (!tearDownBluetooth()) 740 | return; 741 | 742 | log("BT: " + e); 743 | if (debug) { 744 | e.printStackTrace(); 745 | showLog(); 746 | } else { 747 | showMessage(literal[4], literal[59]); 748 | } 749 | } 750 | 751 | // Successfully finished bluetooth operation 752 | public synchronized void finishBluetooth() { 753 | if (tearDownBluetooth()) 754 | showMessage(literal[4], literal[58]); 755 | } 756 | 757 | public synchronized void finishBluetoothReceive(String mode, String gameDisplayName, 758 | String gameFileName, String gameCartID, byte[] savegame) { 759 | tearDownBluetooth(); 760 | if (mode.equals("r")) { 761 | // cart ram 762 | 763 | if (gameCartID.length() == 0) { 764 | int ix = -1; 765 | if (ix == -1 && gameFileName.length() > 0) 766 | ix = findMatch(gameFileName, cartFileName, true); 767 | if (ix == -1 && gameDisplayName.length() > 0) 768 | ix = findMatch(gameDisplayName, cartDisplayName, true); 769 | if (ix > -1) 770 | gameCartID = cartID[ix]; 771 | } 772 | 773 | if (gameCartID.length() > 0 && findMatch(gameCartID, cartID, false) > -1) { 774 | addGameRAM(gameCartID, savegame); 775 | showMessage(literal[4], literal[58]); 776 | } else { 777 | String cart = ""; 778 | if (gameDisplayName.length() > 0) 779 | cart = gameDisplayName; 780 | else if (gameFileName.length() > 0) 781 | cart = gameFileName; 782 | else if (gameCartID.length() > 0) 783 | cart = gameCartID; 784 | showMessage(literal[4], literal[63] + formatDetails(cart, null)); 785 | } 786 | } else { 787 | // suspended game 788 | 789 | if (gameCartID.length() > 0 && findMatch(gameCartID, cartID, false) > -1) { 790 | try { 791 | String suspendName = (MeBoy.suspendCounter++) + ": " + gameDisplayName; 792 | 793 | RecordStore rs = RecordStore.openRecordStore("20S_" + suspendName, true); 794 | 795 | if (rs.getNumRecords() == 0) { 796 | rs.addRecord(gameCartID.getBytes(), 0, gameCartID.length()); 797 | rs.addRecord(savegame, 0, savegame.length); 798 | } else { 799 | rs.setRecord(1, gameCartID.getBytes(), 0, gameCartID.length()); 800 | rs.setRecord(2, savegame, 0, savegame.length); 801 | } 802 | 803 | rs.closeRecordStore(); 804 | addSuspendedGame(suspendName); 805 | showMessage(literal[4], literal[58]); 806 | } catch (Exception e) { 807 | if (debug) 808 | e.printStackTrace(); 809 | showMessage(literal[4], literal[64] + formatDetails(gameCartID, e)); 810 | } 811 | } else { 812 | showMessage(literal[4], literal[63] + formatDetails(gameCartID, null)); 813 | } 814 | } 815 | } 816 | 817 | public void commandAction(Command com, Displayable s) { 818 | if (s == messageForm) 819 | messageCommand(); 820 | else if (s == mainMenu) 821 | mainMenuCommand(com); 822 | else if (s == cartList) 823 | cartListCommand(com); 824 | else if (s == suspendList) 825 | resumeGameCommand(com); 826 | else if (s == settingsForm) 827 | settingsCommand(com); 828 | } 829 | 830 | public static void log(String s) { 831 | if (s == null) { 832 | return; 833 | } 834 | logString = logString + s + '\n'; 835 | if (debug) { 836 | System.out.println(s); 837 | } 838 | } 839 | 840 | public static void addGameRAM(String name, byte[] data) { 841 | try { 842 | RecordStore rs = RecordStore.openRecordStore("20R_" + name, true); 843 | 844 | if (rs.getNumRecords() == 0) { 845 | rs.addRecord(data, 0, data.length); 846 | } else { 847 | rs.setRecord(1, data, 0, data.length); 848 | } 849 | 850 | rs.closeRecordStore(); 851 | } catch (Exception e) { 852 | if (MeBoy.debug) 853 | e.printStackTrace(); 854 | } 855 | } 856 | 857 | // name should include number prefix 858 | public static void addSuspendedGame(String name) { 859 | String[] oldIndex = suspendName20; 860 | suspendName20 = new String[oldIndex.length + 1]; 861 | System.arraycopy(oldIndex, 0, suspendName20, 0, oldIndex.length); 862 | suspendName20[oldIndex.length] = name; 863 | GBCanvas.writeSettings(); 864 | } 865 | 866 | private int findMatch(String match, String[] list, boolean fuzzy) { 867 | // exact match 868 | for (int i = 0; i < list.length; i++) 869 | if (list[i].equals(match)) 870 | return i; 871 | if (!fuzzy) 872 | return -1; 873 | // prefix match, sans extension 874 | int lastdot = match.lastIndexOf('.'); 875 | if (lastdot != -1 && lastdot > match.length() - 5) 876 | match = match.substring(0, lastdot); 877 | for (int i = 0; i < list.length; i++) 878 | if (list[i].startsWith(match)) 879 | return i; 880 | return -1; 881 | } 882 | } 883 | --------------------------------------------------------------------------------