Skip to content

Instantly share code, notes, and snippets.

@grishka
Created January 26, 2022 16:02
Show Gist options
  • Star 6 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save grishka/e0bc94d85d03a55a8f4151e181584c14 to your computer and use it in GitHub Desktop.
Save grishka/e0bc94d85d03a55a8f4151e181584c14 to your computer and use it in GitHub Desktop.
Siemens SGold Bad Apple

Как собрать

  1. Установите кросс-компилятор для ARM (arm-none-eabi-gcc)
  2. Склонируйте https://github.com/Alexious-sh/sie-dev в соседнюю папку
  3. Запустите make

Для создания .rlv файла разберите видео Bad Apple (или другое!) на кадры (frames/%04d.png) с помощью ffpmeg, с частотой 15 кадров в секунду. Поверните и отмасштабируйте кадры, чтобы у них было разрешение 132*176. Запустите java FrameConverter.java.

Чтобы запустить на телефоне, создайте папку BadApple в корне карты памяти и положите в неё сам эльф, видеофайл BadApple.rlv и звуковую дорожку BadApple.mp3.

#include <swilib.h>
// Глобальные переменные и структуры для создания окна и графического контекста
#define GUI_STATE_CLOSED 0
#define GUI_STATE_BACKGROUND 1
#define GUI_STATE_ACTIVE 2
wchar_t maincsm_name_body[140];
typedef struct{
CSM_RAM csm;
int guiID;
}MAIN_CSM;
typedef struct{
CSM_DESC maincsm;
WSHDR name;
}MAIN_CSM_DESC;
typedef struct{
GUI gui;
WSHDR *ws;
}MAIN_GUI;
int minus11=-11;
MAIN_CSM_DESC mainCsmDesc={
{
0, // onmessage
0, // oncreate
#ifdef NEWSGOLD
0, 0, 0, 0,
#endif
0, // onclose
sizeof(MAIN_CSM),
1,
&minus11 // wtf?
},
{
maincsm_name_body,
NAMECSM_MAGIC1,
NAMECSM_MAGIC2,
0, 139, 0
}
};
void *guiFunctions[11];
RECT guiCanvasSize={0};
MAIN_CSM mainCSM;
/////
IMGHDR imgHdr;
RECT imgRect={0};
DRWOBJ drwobj;
int fileHandle;
int fileErr;
unsigned int *fileData;
unsigned int *nextFileData;
unsigned int offsetIntoFileData=0;
unsigned int fileDataLength=0;
unsigned int frameLength;
unsigned int offsetIntoFrame;
unsigned int offsetIntoBitmap;
int playing=0;
MUTEX fileMutex;
int frameCount=0;
int mainCsmID;
int audioPlayerID;
int avOffset=0;
int logFile;
GBSTMR redrawTimer;
#define RLV_SKIP 0
#define RLV_COPY 0x01000000
#define RLV_MEMSET 0x02000000
#define RLV_ZEROS 0x03000000
#define RLV_PADDING 0x04000000
#define RLV_END_OF_FILE 0x05000000
#define FILE_PAGE_SIZE 40960
void RedrawTimerProc(){
int timerValue;
// Подстраиваем синхронизацию звука и картинки.
// Таймер принимает интервал в каких-то максимально всратых единицах, и вообще не особенно точен.
if(avOffset<-67){ // Картинка запаздывает
timerValue=10;
}else if(avOffset>67){ // Картинка рисуется слишком быстро
timerValue=15;
}else{
timerValue=13; // Примерно 67 мс
}
GBS_StartTimerProc(&redrawTimer, timerValue, RedrawTimerProc);
REDRAW();
// Я хз как нормально отключить таймаут подсветки, поэтому просто каждый кадр ставлю максимальную яркость
// ¯\_(ツ)_/¯
// Один вызов для экрана, другой для клавиатуры, хз что есть что
SetIllumination(0, 1, 100, 0);
SetIllumination(1, 1, 100, 0);
frameCount++;
}
void DoReadMoreFileData(){
MutexLock(&fileMutex);
_read(fileHandle, nextFileData, fileDataLength<<2, &fileErr);
MutexUnlock(&fileMutex);
}
void ReadMoreFileData(){
MutexLock(&fileMutex);
unsigned int* tmp=nextFileData;
nextFileData=fileData;
fileData=tmp;
offsetIntoFileData=0;
SUBPROC((void*)DoReadMoreFileData);
MutexUnlock(&fileMutex);
}
static inline void ApplyNextBitmapOperation(){
unsigned int command=fileData[offsetIntoFileData++];
offsetIntoFrame++;
unsigned int cmdArg=command & 0x00ffffff;
switch(command & 0xff000000){
case RLV_SKIP:
offsetIntoBitmap+=cmdArg;
break;
case RLV_COPY:
memcpy(imgHdr.bitmap+(offsetIntoBitmap<<2), fileData+offsetIntoFileData, cmdArg<<2);
offsetIntoFileData+=cmdArg;
offsetIntoFrame+=cmdArg;
offsetIntoBitmap+=cmdArg;
break;
case RLV_ZEROS:
zeromem(imgHdr.bitmap+(offsetIntoBitmap<<2), cmdArg<<2);
offsetIntoBitmap+=cmdArg;
break;
case RLV_MEMSET:
memset(imgHdr.bitmap+(offsetIntoBitmap<<2), fileData[offsetIntoFileData++], cmdArg<<2);
offsetIntoBitmap+=cmdArg;
offsetIntoFrame++;
break;
}
}
void RenderNextFrameIntoBitmap(){
int frameLength=fileData[offsetIntoFileData++];
int lengthCmd=frameLength & 0xff000000;
if(lengthCmd==RLV_PADDING){
ReadMoreFileData();
frameLength=fileData[offsetIntoFileData++];
}else if(lengthCmd==RLV_END_OF_FILE){
playing=0;
/* CloseCSM(mainCsmID); */
return;
}
frameLength>>=2;
offsetIntoFrame=0;
offsetIntoBitmap=0;
if(offsetIntoFileData+frameLength<fileDataLength){
while(offsetIntoFrame<frameLength){
ApplyNextBitmapOperation();
}
}
}
// Колбэки для окна/"GUI"
void GUI_OnRedraw(MAIN_GUI *gui){
if(!playing)
return;
RenderNextFrameIntoBitmap();
DrawObject(&drwobj);
/* wsprintf(gui->ws, "%d ms", avOffset); */
/* DrawString(gui->ws, 0, 0, ScreenW(), 30, FONT_SMALL, 0, GetPaletteAdrByColorIndex(1), GetPaletteAdrByColorIndex(0)); */
}
void GUI_OnCreate(MAIN_GUI *gui, void *(*malloc_adr)(int)){
/* logFile=_open("4:\\BadApple\\log.txt", A_WriteOnly | A_BIN | A_Create, P_WRITE, &fileErr); */
gui->ws=AllocWS(128);
imgHdr.w=ScreenW();
imgHdr.h=ScreenH();
imgHdr.bpnum=5;
imgHdr.bitmap=(unsigned char*)malloc(132*176);
gui->gui.state=1;
zeromem(imgHdr.bitmap, 132*176);
imgRect.x2=imgHdr.w;
imgRect.y2=imgHdr.h;
SetPropTo_Obj5(&drwobj, &imgRect, 0, &imgHdr);
SetColor(&drwobj, 0, 0);
MutexCreate(&fileMutex);
fileHandle=_open("4:\\BadApple\\BadApple.rlv", A_ReadOnly | A_BIN, P_READ, &fileErr);
fileData=malloc(fileDataLength=FILE_PAGE_SIZE);
nextFileData=malloc(FILE_PAGE_SIZE);
fileDataLength>>=2;
DoReadMoreFileData();
ReadMoreFileData();
WSHDR* sndPath=AllocWS(256);
WSHDR* sndFName=AllocWS(256);
str_2ws(sndFName, "BadApple.mp3", 256);
str_2ws(sndPath, "4:\\BadApple", 256);
PLAYFILE_OPT opt;
zeromem(&opt, sizeof(PLAYFILE_OPT));
opt.repeat_num=1;
opt.time_between_play=0;
opt.play_first=0;
opt.volume=1;
opt.unk5=1;
opt.unk4=0x80000000;
audioPlayerID=PlayFile(0xC, sndPath, sndFName, 0, GBS_GetCurCepid(), MSG_PLAYFILE_REPORT, &opt);
FreeWS(sndPath);
FreeWS(sndFName);
}
void GUI_OnClose(MAIN_GUI *gui, void (*mfree_adr)(void *)){
playing=0;
GBS_DelTimer(&redrawTimer);
gui->gui.state=GUI_STATE_CLOSED;
FreeWS(gui->ws);
mfree(imgHdr.bitmap);
mfree(fileData);
mfree(nextFileData);
_close(fileHandle, &fileErr);
/* _close(logFile, &fileErr); */
MutexDestroy(&fileMutex);
}
void GUI_OnFocus(MAIN_GUI *gui, void *(*malloc_adr)(int), void (*mfree_adr)(void *)){
gui->gui.state=GUI_STATE_ACTIVE;
DisableIDLETMR();
}
void GUI_OnUnfocus(MAIN_GUI *gui, void (*mfree_adr)(void *)){
if(gui->gui.state!=GUI_STATE_ACTIVE)
return;
gui->gui.state=GUI_STATE_BACKGROUND;
}
int GUI_OnKey(MAIN_GUI *gui, GUI_MSG *msg){
if(msg->gbsmsg->msg==KEY_DOWN && msg->gbsmsg->submess==RIGHT_SOFT){
return 1;
}
return 0;
}
void GUI_OnDealloc(MAIN_GUI *gui, void (*mfree_adr)(void *)){
// no-op
}
int GUI_UnknownFunc8(){
return 0;
}
int GUI_UnknownFunc9(){
return 0;
}
// Колбэки для "CSM" (я хз что такое CSM, если честно)
void MainCSM_OnClose(CSM_RAM *csm){
// Здесь надо бы выгружать эльф, но я не умею это делать
}
int MainCSM_OnMessage(CSM_RAM *data, GBS_MSG *msg){
MAIN_CSM *csm=(MAIN_CSM*)data;
if(msg->msg==MSG_GUI_DESTROYED && (int)msg->data0==csm->guiID){
csm->csm.state=-3;
}else if(msg->msg==MSG_PLAYFILE_REPORT){
/* char buf[200]; */
/* sprintf(buf, "playfile report: %08x %08x %08x\n", msg->submess, msg->data0, msg->data1); */
/* _write(logFile, buf, strlen(buf), &fileErr); */
int sub=msg->submess & 0x0000ffff;
if(sub==0x0004){ // playback started
playing=1;
GBS_StartTimerProc(&redrawTimer, 10, RedrawTimerProc);
}else if(sub==0x0020){ // position update
int currentTime=(int)msg->data0;
int targetTime=frameCount/15*1000+frameCount%15*67;
avOffset=targetTime-currentTime;
}
}
return 1;
}
void MainCSM_OnCreate(CSM_RAM *data){
guiFunctions[0]=(void*)GUI_OnRedraw;
guiFunctions[1]=(void*)GUI_OnCreate;
guiFunctions[2]=(void*)GUI_OnClose;
guiFunctions[3]=(void*)GUI_OnFocus;
guiFunctions[4]=(void*)GUI_OnUnfocus;
guiFunctions[5]=(void*)GUI_OnKey;
guiFunctions[6]=NULL;
guiFunctions[7]=(void*)GUI_OnDealloc;
guiFunctions[8]=(void*)GUI_UnknownFunc8;
guiFunctions[9]=(void*)GUI_UnknownFunc9;
guiFunctions[10]=NULL;
guiCanvasSize.x2=ScreenW()-1;
guiCanvasSize.y2=ScreenH()-1;
MAIN_CSM *csm=(MAIN_CSM*)data;
MAIN_GUI *gui=malloc(sizeof(MAIN_GUI));
zeromem(gui, sizeof(MAIN_GUI));
gui->gui.canvas=&guiCanvasSize;
gui->gui.methods=(void*)guiFunctions;
gui->gui.item_ll.data_mfree=(void (*)(void *))mfree_adr();
csm->csm.state=0;
csm->csm.unk1=0;
csm->guiID=CreateGUI(gui);
}
size_t wcslen(wchar_t* str){
size_t len=0;
while(str[len]){
len++;
}
return len;
}
void __init_switab(){
register int ret asm("r0")=0;
asm volatile("swi 0x80FF\n\t"
:"=r"(ret)
:
:"lr", "memory");
__sys_switab_addres=(int*)ret;
}
void _start(){
__init_switab();
mainCsmDesc.maincsm.onMessage=MainCSM_OnMessage;
mainCsmDesc.maincsm.onCreate=MainCSM_OnCreate;
mainCsmDesc.maincsm.onClose=MainCSM_OnClose;
mainCsmDesc.name.wsbody=maincsm_name_body;
wsprintf((WSHDR*)(&mainCsmDesc.name), "Bad Apple");
mainCsmID=CreateCSM(&mainCsmDesc.maincsm, &mainCSM, 0);
}
import javax.imageio.*;
import java.awt.image.*;
import java.io.*;
import java.util.*;
public class FrameConverter{
private static final int CMD_SKIP=0;
private static final int CMD_COPY=0x01000000;
private static final int CMD_MEMSET=0x02000000;
private static final int CMD_ZEROS=0x03000000;
private static final int CMD_PADDING=0x04000000;
private static final int CMD_END_OF_FILE=0x05000000;
private static final int PAGE_SIZE=40960;
private static final int SMALL_RUN_THRESHOLD=3;
public static void main(String[] args) throws Exception{
FileOutputStream out=new FileOutputStream("BadApple.rlv");
DataOutputStream fds=new DataOutputStream(out);
int[] pixels=new int[132*176];
int[] convertedFrame=new int[5808];
int[] prevConvertedFrame=new int[5808];
int totalFrames=3287;
int currentPageSize=0;
for(int i=0;i<totalFrames;i++){
File file=new File(String.format("frames/%04d.png", i+1));
BufferedImage img=ImageIO.read(file);
img.getRGB(0, 0, 132, 176, pixels, 0, 132);
ByteArrayOutputStream buf=new ByteArrayOutputStream();
DataOutputStream ds=new DataOutputStream(buf);
int pixelBuf=0;
int k=0;
for(int j=0;j<132*176;j++){
int pixel=pixels[j] & 0xFF;
int p;
if(pixel<50)
p=0;
else if(pixel<170)
p=0x92;
else
p=0xff;
// int p=((pixel >> 6) & 3) | ((pixel >> 11) & 0x1c) | ((pixel >> 16) & 0xe0);
pixelBuf<<=8;
pixelBuf|=p;
if(j%4==3){
convertedFrame[k]=pixelBuf;
k++;
}
}
ArrayList<Run> frameRuns=new ArrayList<>();
ArrayList<Integer> currentRun=new ArrayList<>();
ArrayList<Integer> currentUnchanged=new ArrayList<>();
for(int j=0;j<5808;j++){
if(convertedFrame[j]==prevConvertedFrame[j]){
if(currentRun.size()>0){
frameRuns.add(Run.withValues(currentRun));
currentRun.clear();
}
currentUnchanged.add(convertedFrame[j]);
}else{
if(currentUnchanged.size()>0){
frameRuns.add(Run.skip(currentUnchanged));
currentUnchanged.clear();
}
currentRun.add(convertedFrame[j]);
}
}
if(currentUnchanged.size()>0){
frameRuns.add(Run.skip(currentUnchanged));
}else if(currentRun.size()>0){
frameRuns.add(Run.withValues(currentRun));
}
int mergedCount=0;
ArrayList<Run> sequentialSmallRuns=new ArrayList<>();
for(Run run:frameRuns){
if(run.values.size()<SMALL_RUN_THRESHOLD){
sequentialSmallRuns.add(run);
}else if(sequentialSmallRuns.size()>0){
Run merged=new Run();
for(Run sr:sequentialSmallRuns){
merged.values.addAll(sr.values);
mergedCount++;
}
sequentialSmallRuns.clear();
merged.write(ds);
run.write(ds);
}else{
run.write(ds);
}
}
if(sequentialSmallRuns.size()>0){
Run merged=new Run();
for(Run sr:sequentialSmallRuns){
merged.values.addAll(sr.values);
}
sequentialSmallRuns.clear();
merged.write(ds);
}
byte[] serializedFrame=buf.toByteArray();
System.out.println("frame "+i+" size "+serializedFrame.length+", "+mergedCount+" runs merged");
if(currentPageSize+serializedFrame.length+4>=PAGE_SIZE){
int paddingBytes=PAGE_SIZE-currentPageSize-4;
fds.writeInt(Integer.reverseBytes(CMD_PADDING | paddingBytes));
for(int j=0;j<paddingBytes;j++){
fds.write(0xff);
}
currentPageSize=0;
}
fds.writeInt(Integer.reverseBytes(serializedFrame.length));
fds.write(serializedFrame);
currentPageSize+=serializedFrame.length+4;
if(currentPageSize>PAGE_SIZE){
throw new IllegalStateException("Single frame is larger than page size "+i);
}
int[] _tmp=convertedFrame;
convertedFrame=prevConvertedFrame;
prevConvertedFrame=_tmp;
}
fds.writeInt(Integer.reverseBytes(CMD_END_OF_FILE));
fds.close();
}
private static class Run{
public ArrayList<Integer> values=new ArrayList<>();
public boolean isSkip;
public static Run skip(List<Integer> values){
Run r=new Run();
r.isSkip=true;
r.values.addAll(values);
return r;
}
public static Run withValues(List<Integer> values){
Run r=new Run();
r.values.addAll(values);
return r;
}
public void write(DataOutputStream ds) throws Exception{
if(isSkip){
ds.writeInt(Integer.reverseBytes(CMD_SKIP | values.size()));
}else{
int pValue=values.get(0);
boolean allSame=true;
for(int px:values){
if(px!=pValue){
allSame=false;
}
}
if(!allSame || values.size()==1){
ds.writeInt(Integer.reverseBytes(CMD_COPY | values.size()));
for(int px:values){
ds.writeInt(px);
}
}else{
if(pValue==0){
ds.writeInt(Integer.reverseBytes(CMD_ZEROS | values.size()));
}else{
ds.writeInt(Integer.reverseBytes(CMD_MEMSET | values.size()));
ds.writeInt(pValue);
}
}
}
}
}
}
CC=arm-none-eabi-gcc
CFLAGS=-I../sie-dev/elfloader3/dev/include -DSGOLD -DX75 -D__NO_LIBC -O2 -std=gnu99 -Wl,-s -nostartfiles -nostdlib -fPIE -fshort-wchar
LIBS=-lgcc
DEPS=
%.o: %.c $(DEPS)
$(CC) -c -o $@ $< $(CFLAGS)
BadApple: BadApple.o
$(CC) -o BadApple.elf BadApple.o $(CFLAGS) $(LIBS)
.PHONY: clean
clean:
rm -f *.o *.elf
@Wiz-IO
Copy link

Wiz-IO commented Jan 27, 2022

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment