Пишем на Java для Nintendo DS

в 11:35, , рубрики: java, nintendo ds, гик-порно, мобильная разработка, Разработка под Java ME, старое железо

image

Введение

Все началось с того, что я случайно нашел список homebrew программ (программы, разработанные усилиями пользователей для устройств, не предназначенных для запуска пользовательского ПО) для Nintendo DS и в нем увидел одну очень интересную строчку, а именно: «Pstros NDS — MIDP implementation run on the CLDC java machine compiled for NDS».

Будучи большим поклонником Java и Nintendo DS я решил разобраться, что это за зверь такой, и по возможности попробовать написать под эту JVM свое приложение. Тех, кому это интересно прошу под кат.

Pstros NDS

Скачав заветный архив с сайта, я начал его исследование. Почитав readme оказалось, что Pstros NDS представляет собой порт KVM (виртуальная машина Java, разработанная фирмой Sun Microsystems) для Nintendo DS. Как следует из статей в интернете (статья 1, статья 2) эта Java-машина предназначена для небольших устройств, имеющих ограниченный объем оперативной памяти, и относится к подмножеству платформ Java — J2ME.

Архитектура J2ME

J2ME имеет модульную архитектуру, в которой основными элементами являются конфигурации и профили.

Конфигурация – это спецификация, которая описывает доступные средства, для разработки под конкретное семейство мобильных устройств (набор функций языка Java, характеристики и возможности виртуальной машины, минимальные поддерживаемые библиотеки).

Профиль расширяет и дополняет функции конфигурации под конкретное мобильное устройство. Он дает возможность работать с графическим дисплеем, коммуникационными функциями, специфичной периферией (в нашем случае это тачскрин).

Схема архитектуры J2ME представлена ниже:

image

Запуск homebrew

Просто так запустить несертифицированное Nintendo программное обеспечение на приставке нельзя. Для решения этой проблемы были созданы специальные флеш-картриджи (на самом деле скорее всего они были созданы для запуска пиратских копий игр, а homebrew был побочным продуктом, но история не про это).

На AliExpress есть множество вариантов флеш-картриджей по доступным ценам. Для себя я заказал R4i Dual Core. Пользоваться им предельно просто: достаточно распаковать специальный архив в корень microSD, закачать туда файл c Java-машиной в формате jvm, вставить флеш-картридж в приставку и можно пользоваться.

image

Первый запуск KVM

В архиве Pstros NDS есть несколько примеров java-программ. Для запуска этих программ надо положить их в любое место на microSD флеш-картриджа. После запуска kvm.nds появится возможность выбора файлов на файловой системе:

image

Выбрав файл Hello.class увидим следующее:
image

Еще несколько примеров приложений:

image

image

Эмулятор

Подключить Nintendo DS напрямую к компьютеру, насколько я знаю, невозможно. Постоянно перетыкать microSD из компьютера в устройство и обратно, чтобы посмотреть результат, слишком долго. Поэтому для тестовых запусков приложения будем использовать популярный эмулятор DeSmuME.

В запуске homebrew приложений для DeSmuME есть некоторые особенности. Насколько я разобрался в вопросе, это проистекает из проблемы работы с файловой системой на флеш-картридже.

Для взаимодействия homebrew приложений с файловой системой была написана библиотека libfat. Однако для Nintendo DS существует огромное количество флеш-картриджей, и все они используют специфичные команды чтения/записи файлов. Библиотека libfat не может распознать эти команды и отправляет homebrew-приложению сообщение об ошибке. Чтобы этого избежать была придумана технология, которая называется DLDI (Dynamically-Linked Device Interface). Она выступает прослойкой между homebrew приложением и флеш-картриджем, преобразуя специфичные команды чтения/записи в команды понятные для libfat библиотеки.

При попытке запуска DeSmuME kvm.nds получаем следующую ошибку:

image

В FAQ к эмулятору указано, что DLDI-patch будет применен автоматически, но для корректного доступа к файлам необходимо в GBA-слоте указать MPCF Flash Card Device, как показано на рисунке ниже:

image

После этого JVM запускается нормально.

Настройка окружения для разработки

Изучая архив с jvm, я обратил внимание на файл _rebuild.bat. Его содержимое представлено ниже:

Содержимое _rebuild.bat

SET WTKDIR=c:/wtk22
SET CP=%WTKDIR%/lib/cldcapi10.jar;%WTKDIR%/lib/midpapi20.jar;./classes.zip 
del .output*.* /Q
"c:/program files/java/jdk1.5.0_06/bin/javac.exe" -source 1.3 -target 1.1 -bootclasspath %CP% ./src/*.java
%WTKDIR%/bin/preverify -classpath %CP% ./src
del .src*.class /Q
copy output*.* .

Из этого становится понятно, как собирать приложения для jvm и какое окружение нам необходимо. На сайте oracle в архиве есть необходимые нам версии wtk и jdk. Устанавливаем их по указанным в bat файле путям (или правим пути в bat файле) и можно приступать к разработке.

Разработка приложения

По роду своей деятельности я много работал с библиотекой для создания графического интерфейса Swing. Мне нравится эта библиотека, поэтому в качестве примера я решил написать собственную ее реализацию. Разрабатывать я решил по принципу «черного ящика», т.е. зная, как это должно выглядеть снаружи, не смотреть, как это устроено внутри. Во-первых, потому что задуманная мной библиотека будет иметь существенно меньший функционал (поскольку я разрабатываю ее в развлекательных целях) и, вероятно, многие решения, примененные в Swing, мне будут просто не нужны. Во-вторых, придумывать свою реализацию гораздо интереснее.

Как видно из скрипта сборки, разрабатывать придется под java 1.1.

Что касается требований, то в первую очередь хотелось для пользователя библиотеки поддержать типичный Swing-овый код вызова диалоговой формы:

MyDialogForm myDialogForm = new MyDialogForm(this, true); 

myDialogForm.setVisible(true); 

if (myDialogForm.getAnsewer()) { 
    // TODO - do something here 
} 

Т.е. в данном случае показывается диалоговая форма и пока она не будет закрыта, выполнение не пойдет дальше setVisible(true). При этом для других компонентов, которые находятся на вызванной форме, должна продолжаться обработка их пользовательских действий.

Первый вариант схемы работы библиотеки представлен на рисунке ниже:

image

Как видно из схемы каждое пользовательское действие выполняется в отдельном потоке. При такой реализации требование по работе с диалоговыми формами выполнено, однако существует проблема с тем, что пользователь в своих обработчиках может одновременно из двух потоков работать с одними и теми же данными (например, быстро нажав на две кнопки). Это порождает дополнительные неудобства связанные с необходимостью защиты доступа к такими данным для пользователя.

Сделаем допущение, что все формы диалоговые, т.е. при показе одной формы невозможно работать с другими формами. В данном случае это адекватное допущение, поскольку экраны Nintendo DS не большие, и отображать одновременно несколько форм попросту неудобно.

В таком случае схема работы библиотеки приобретает следующий вид:

image

Т.е. для каждой формы создается свой поток обработки действий пользователя приложения. Если в результате обработки действия вызывается новая форма, то поток, ее вызвавший, ждет закрытия этой формы, а в это время создается новый поток обработки пользовательских действий.

Пример кода работы с библиотекой представлен ниже:

Пример работы

JNDSComponentsForm jNDSComponentsForm = new JNDSComponentsForm(); 
jNDSComponentsForm.setTitle("Main form"); 
 
final JNDSLabel jndsLabel = new JNDSLabel("Simple label", 20, 30); 
jNDSComponentsForm.addComponent(jndsLabel); 

JNDSTextField jndsTextField = new JNDSTextField("Hello world", 20, 58, 150); 
jNDSComponentsForm.addComponent(jndsTextField); 

JNDSButton jndsButtonDialogForm = new JNDSButton("Show dialog form", 20, 90); 
JNDSAction jndsActionImplDialogForm = new JNDSAction() { 
     public void action() { 
         final JNDSDialogForm jndsDialogForm = new JNDSDialogForm(); 
         jndsDialogForm.setTitle("Dialog form"); 

         JNDSButton jndsButtonClose = new JNDSButton("Close", 10, 130); 
         jndsButtonClose.setClickAction(new JNDSAction() { 
             public void action() { 
                 jndsDialogForm.setVisible(false); 
             } 
         }); 
         jndsDialogForm.addComponent(jndsButtonClose); 

         JNDSLabel jndsLabelDialogForm = new JNDSLabel("This is Dialog Form!", 70, 80); 
         jndsDialogForm.addComponent(jndsLabelDialogForm); 

         jndsDialogForm.setVisible(true); 
    } 
}; 
jndsButtonDialogForm.setClickAction(jndsActionImplDialogForm); 

jNDSComponentsForm.addComponent(jndsButtonDialogForm); 

jNDSComponentsForm.setVisible(true); 

JNDSWindowsManager.instance().run(); 

Как и в Swing сначала описывается верстка форм и обработчики действий пользователя, а потом управление передается библиотеке, которая инициализирует контекст и занимается отрисовкой и обработкой нажатий на тачскрин.

Еще одна проблема, с которой я столкнулся, это клавиатура. К сожалению, способа вызвать готовую экранную клавиатуру операционной системы, я не нашел. Поэтому пришлось делать свою, сильно упрощенную ее реализацию. Результат ее работы представлен ниже:

image

В качестве компонентов на данный момент я реализовал только Label, TextField и Button. Для примера, думаю, достаточно; внешний вид формы, описанный выше, представлен на рисунке:

image

С полным текстом исходных кодов можно ознакомиться по ссылке.

Запуск на реальном устройстве

Копируем полученные в результате компиляции файлы *.class на флеш-картридж, запускаем kvm.nds и выбираем в нем ExampleJNDSWindowsManager.class.

Результат работы приложения представлен на следующем видео:

Заключение

В заключении хотелось бы поблагодарить создателей Nintendo DS, разработчиков порта JVM для Nintendo DS, создателей флеш-картриджей за возможность разработки под любимое железо, а также мою жену за помощь в съемках видео и редактировании этой статьи.

Всем спасибо за внимание!

Автор: Илья

Источник

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js