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, создателей флеш-картриджей за возможность разработки под любимое железо, а также мою жену за помощь в съемках видео и редактировании этой статьи.

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

Комментарии (6)


  1. Ded_Banzai
    27.09.2019 06:27
    +1

    Жаль, кармы для плюса не хватает. Спасибо за пост, интересно.


    1. izemskov Автор
      27.09.2019 15:18

      Вам спасибо, рад что интересно


  1. EXL
    27.09.2019 12:58

    Скажите пожалуйста, а исходный код KVM от Sun Microsystems открыт? Хотелось бы посмотреть реализацию этой JVM. Буду рад ссылке.

    Большое спасибо за статью. Не ожидал, что вы начнёте свои эксперименты с написания GUI-тулкита.


    1. izemskov Автор
      27.09.2019 14:17

      Насколько я понимаю он был открыт, но найденная мной ссылка, по которой должен быть доступен исходный код (http://java.sun.com/j2me/), сейчас ведет на сайт Oracle и там исходники KVM найти не удалось (возможно плохо искал). Я хочу связаться с одним из авторов порта KVM для Nintendo DS (благо он отметился в Readme.txt) и узнать по поводу исходников, поскольку очень хочется прикрутить работу с сетью к Java машине. Это бы существенно расширило возможности приложений для этой JVM. Если что то прояснится отпишусь здесь.

      Рад, что статья понравилась :) Я начинал эксперименты с более скромных приложений, но решил их здесь не описывать. Вряд ли очередной hello world будет интересен сообществу.


      1. EXL
        28.09.2019 14:46

        Когда-то давно, может быть лет 10 назад, у меня был довольно интересный девайс с Linux'ом и J2ME на борту, про который я написал обзор для хабра: Полный обзор возможностей Motorola ZINE ZN5. Этот девайс был, как сейчас бы сказали «гиковским», после взлома прошивки появлялась возможность установки туда различного нативного софта и он фактически усилием энтузиастов превращался в смартфон.

        Но что самое интересное, в качестве виртуальной машины для J2ME там тоже использовался SUN'овский KVM, видимо достаточно сильно приправленный Motorola, которая прикрутила к нему возможность работы с 3D и прокинула привязки GUI-интерфейса до общесистемного GUI-тулкита в виде Qt 2.3.8. К моему сожалению, в официальный репозиторий Motorola ZN5 они не выложили исходники своих наработок над KVM, видимо им это позволяла лицензия или исходный код KVM был вообще закрыт и лицензирован исключительно для Motorola.

        Мне в то время очень хотелось открутить от KVM интересную библиотеку libezx3dgraphics.so.1.0.0, которая как раз и представляла реализацию 3D.



        Ну а исходники KVM мне помогли бы разобраться как использовать эту библиотеку, чтобы написать свою Homebrew-демку в 3D на её основе. Увы, подобному не суждено было сбыться и моим камнем преткновения стало именно то, что я не смог найти в своё время исходники KVM, а ковыряться с декомпилятором мне тогда было лень, да и подобного опыта не хватало.

        Я хочу связаться с одним из авторов порта KVM для Nintendo DS (благо он отметился в Readme.txt) и узнать по поводу исходников, поскольку очень хочется прикрутить работу с сетью к Java машине. Это бы существенно расширило возможности приложений для этой JVM. Если что то прояснится отпишусь здесь.

        Я буду очень благодарен, если вы не забудете и отпишетесь, когда вам ответит этот человек. Это как минимум закроет некоторые вопросы, на которые я в своё время не нашёл ответа:

        1. Были ли исходники KVM открыты вообще?
        2. Если исходники KVM были открыты, то под какой лицензией?
        3. Если исходники KVM были открыты, не нарушила ли Motorola их лицензию не опубликовав свои изменения?
        4. В этом KVM изначально имеется интерфейс для различных 3D-классов или же это привнесение Motorola?

        Иногда думаю достать это устройство и добить Homebrew на основе системной 3D библиотеки, так что буду рад любой информации. Ещё раз спасибо за статью!


        1. izemskov Автор
          28.09.2019 23:58
          +1

          Прочитал Вашу статью, очень интересно, спасибо! Не слышал раньше про этот телефон, но видимо классная штука была. Если получится написать 3D демку для java с удовольствием прочитаю про это.

          Автору kvm порта для Nintendo DS написал, но пока не получил от него никакого ответа. Однако на просторах интернета набрёл на интересный проект: sourceforge.net/projects/pspkvm При беглом осмотре это очень похоже на реализацию kvm для Sony PSP, более глубоко пока времени не было посмотреть. Возможно Вам это поможет, я тоже как будет время детально поизучаю этот проект.