akmenu-next/arm9/source/cheatwnd.cpp
2024-10-04 22:35:39 -07:00

510 lines
13 KiB
C++

/*
cheatwnd.cpp
Portions copyright (C) 2008 Normmatt, www.normmatt.com, Smiths (www.emuholic.com)
Portions copyright (C) 2008 bliss (bliss@hanirc.org)
Copyright (C) 2009 yellow wood goblin
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "cheatwnd.h"
#include "uisettings.h"
#include "windowmanager.h"
#include "language.h"
#include "msgbox.h"
#include "../../share/gamecode.h"
#include <sys/stat.h>
#include <elm.h>
using namespace akui;
#define CRCPOLY 0xedb88320
static u32 crc32(const u8* p,size_t len)
{
u32 crc=-1;
while(len--)
{
crc^=*p++;
for(int ii=0;ii<8;++ii) crc=(crc>>1)^((crc&1)?CRCPOLY:0);
}
return crc;
}
cCheatWnd::cCheatWnd(s32 x,s32 y,u32 w,u32 h,cWindow* parent,const std::string& text):
cForm(x,y,w,h,parent,text),
_buttonDeselect(0,0,46,18,this,"\x05 Deselect"),
_buttonInfo(0,0,46,18,this,"\x04 Info"),
_buttonGenerate(0,0,46,18,this,"\x03 OK"),
_buttonCancel(0,0,46,18,this,"\x02 Cancel"),
_List(0,0,w-8,h-44,this,"cheat tree")
{
s16 buttonY=size().y-_buttonCancel.size().y-4;
_buttonCancel.setStyle(cButton::press);
_buttonCancel.setText("\x02 "+LANG("setting window","cancel"));
_buttonCancel.setTextColor(uis().buttonTextColor);
_buttonCancel.loadAppearance(SFN_BUTTON3);
_buttonCancel.clicked.connect(this,&cCheatWnd::onCancel);
addChildWindow(&_buttonCancel);
_buttonGenerate.setStyle(cButton::press);
_buttonGenerate.setText( "\x03 " + LANG( "setting window", "ok" ) );
_buttonGenerate.setTextColor(uis().buttonTextColor);
_buttonGenerate.loadAppearance(SFN_BUTTON2);
_buttonGenerate.clicked.connect(this,&cCheatWnd::onGenerate);
addChildWindow(&_buttonGenerate);
_buttonInfo.setStyle(cButton::press);
_buttonInfo.setText( "\x04 " + LANG( "cheats", "info" ) );
_buttonInfo.setTextColor(uis().buttonTextColor);
_buttonInfo.loadAppearance(SFN_BUTTON3);
_buttonInfo.clicked.connect(this,&cCheatWnd::onInfo);
addChildWindow(&_buttonInfo);
_buttonDeselect.setStyle(cButton::press);
_buttonDeselect.setText( "\x05 " + LANG( "cheats", "deselect" ) );
_buttonDeselect.setTextColor(uis().buttonTextColor);
_buttonDeselect.loadAppearance(SFN_BUTTON4);
_buttonDeselect.clicked.connect(this,&cCheatWnd::onDeselectAll);
addChildWindow(&_buttonDeselect);
s16 nextButtonX=size().x;
s16 buttonPitch=_buttonCancel.size().x+4;
nextButtonX-=buttonPitch;
_buttonCancel.setRelativePosition(cPoint(nextButtonX,buttonY));
buttonPitch=_buttonGenerate.size().x+4;
nextButtonX-=buttonPitch;
_buttonGenerate.setRelativePosition(cPoint(nextButtonX,buttonY));
buttonPitch=_buttonInfo.size().x+4;
nextButtonX-=buttonPitch;
_buttonInfo.setRelativePosition(cPoint(nextButtonX,buttonY));
buttonPitch=_buttonDeselect.size().x+4;
nextButtonX-=buttonPitch;
_buttonDeselect.setRelativePosition(cPoint(nextButtonX,buttonY));
_List.setRelativePosition(cPoint(4, 20));
//CIniFile ini(SFN_UI_SETTINGS);
//_List.setColors(ini.GetInt("main list","textColor",RGB15(7,7,7)),ini.GetInt("main list","textColorHilight",RGB15(31,0,31)),ini.GetInt("main list","selectionBarColor1",RGB15(16,20,24)),ini.GetInt("main list","selectionBarColor2",RGB15(20,25,0)));
_List.insertColumn(0,"icon",EIconWidth);
_List.insertColumn(1,"showName",_List.size().x+2-EIconWidth);
_List.arangeColumnsSize();
_List.setRowHeight(ERowHeight);
_List.selectedRowClicked.connect(this,&cCheatWnd::onItemClicked);
_List.ownerDraw.connect(this,&cCheatWnd::onDraw);
addChildWindow(&_List);
loadAppearance("");
arrangeChildren();
}
cCheatWnd::~cCheatWnd()
{}
void cCheatWnd::draw()
{
_renderDesc.draw(windowRectangle(),_engine);
cForm::draw();
}
bool cCheatWnd::process(const akui::cMessage& msg)
{
bool ret=false;
ret=cForm::process(msg);
if(!ret)
{
if(msg.id()>cMessage::keyMessageStart&&msg.id()<cMessage::keyMessageEnd)
{
ret=processKeyMessage((cKeyMessage&)msg);
}
}
return ret;
}
bool cCheatWnd::processKeyMessage(const cKeyMessage& msg)
{
bool ret=false;
if(msg.id()==cMessage::keyDown)
{
switch(msg.keyCode())
{
case cKeyMessage::UI_KEY_DOWN:
_List.selectNext();
ret=true;
break;
case cKeyMessage::UI_KEY_UP:
_List.selectPrev();
ret=true;
break;
case cKeyMessage::UI_KEY_LEFT:
{
size_t ii=_List.selectedRowId();
while(--ii>0)
{
if(_data[_indexes[ii]]._flags&cParsedItem::EFolder) break;
}
_List.selectRow(ii);
}
ret=true;
break;
case cKeyMessage::UI_KEY_RIGHT:
{
size_t ii=_List.selectedRowId(),top=_List.getRowCount();
while(++ii<top)
{
if(_data[_indexes[ii]]._flags&cParsedItem::EFolder) break;
}
_List.selectRow(ii);
}
ret=true;
break;
case cKeyMessage::UI_KEY_A:
onSelect();
ret=true;
break;
case cKeyMessage::UI_KEY_B:
onCancel();
ret=true;
break;
case cKeyMessage::UI_KEY_X:
onGenerate();
ret=true;
break;
case cKeyMessage::UI_KEY_Y:
onInfo();
ret=true;
break;
case cKeyMessage::UI_KEY_L:
onDeselectAll();
ret=true;
break;
default:
break;
}
}
return ret;
}
cWindow& cCheatWnd::loadAppearance(const std::string& aFileName )
{
_renderDesc.loadData(SFN_FORM_TITLE_L,SFN_FORM_TITLE_R,SFN_FORM_TITLE_M);
_renderDesc.setTitleText(_text);
return *this;
}
void cCheatWnd::onItemClicked(u32 index)
{
(void)index;
onSelect();
}
void cCheatWnd::onSelect(void)
{
size_t index=_indexes[_List.selectedRowId()];
if(_data[index]._flags&cParsedItem::EFolder)
{
_data[index]._flags^=cParsedItem::EOpen;
u32 first=_List.firstVisibleRowId(),row=_List.selectedRowId();
generateList();
_List.setFirstVisibleIdAndSelectRow(first,row);
}
else
{
bool deselect=(_data[index]._flags&(cParsedItem::EOne|cParsedItem::ESelected))==(cParsedItem::EOne|cParsedItem::ESelected);
if(_data[index]._flags&cParsedItem::EOne) deselectFolder(index);
if(!deselect) _data[index]._flags^=cParsedItem::ESelected;
}
}
void cCheatWnd::onDeselectAll(void)
{
std::vector<cParsedItem>::iterator itr=_data.begin();
while(itr!=_data.end())
{
(*itr)._flags&=~cParsedItem::ESelected;
++itr;
}
}
void cCheatWnd::onInfo(void)
{
size_t index=_indexes[_List.selectedRowId()];
std::string body(_data[index]._title);
body+="\n\n";
body+=_data[index]._comment;
messageBox(this,LANG("cheats","title"),body,MB_OK);
}
void cCheatWnd::onCancel(void)
{
cForm::onCancel();
}
static void updateDB(u8 value,u32 offset,FILE* db)
{
u8 oldvalue;
if(!db) return;
if(!offset) return;
if(fseek(db,offset,SEEK_SET)) return;
if(fread(&oldvalue,sizeof(oldvalue),1,db)!=1) return;
if(oldvalue!=value)
{
if(fseek(db,offset,SEEK_SET)) return;
fwrite(&value,sizeof(value),1,db);
}
}
void cCheatWnd::onGenerate(void)
{
NandFast();
FILE* db=fopen(SFN_CHEATS,"r+b");
if(db)
{
std::vector<cParsedItem>::iterator itr=_data.begin();
while(itr!=_data.end())
{
updateDB(((*itr)._flags&cParsedItem::ESelected)?1:0,(*itr)._offset,db);
++itr;
}
fclose(db);
}
NandFlush();
cForm::onOK();
}
void cCheatWnd::drawMark(const cListView::cOwnerDraw& od,u16 width)
{
u16 color=gdi().getPenColor(od._engine);
u16 size=od._size.y-ESelectTop*2;
gdi().fillRect(color,color,od._position.x+((width-size)>>1)-1,od._position.y+ESelectTop,size,size,od._engine);
}
void cCheatWnd::onDraw(const cListView::cOwnerDraw& od)
{
size_t index=_indexes[od._row];
u32 flags=_data[index]._flags;
if(od._col==EIconColumn)
{
if(flags&cParsedItem::EFolder)
{
u16 size=od._size.y-EFolderTop*2;
s16 x1=od._position.x+((od._size.x-size)>>1)-1,x2=x1+(size>>1),y1=od._position.y+EFolderTop,y2=y1+(size>>1);
gdi().frameRect(x1,y1,size,size,od._engine);
gdi().drawLine(x1,y2,x1+size,y2,od._engine);
if(!(flags&cParsedItem::EOpen)) gdi().drawLine(x2,y1,x2,y1+size,od._engine);
}
else if(!(flags&cParsedItem::EInFolder))
{
if(flags&cParsedItem::ESelected) drawMark(od,od._size.x);
}
}
else if(od._col==ETextColumn)
{
if(flags&cParsedItem::EInFolder)
{
if(flags&cParsedItem::ESelected) drawMark(od,EFolderWidth);
}
s16 x=od._position.x; u16 w=od._size.x;
if(flags&cParsedItem::EInFolder)
{
x+=EFolderWidth;
w-=EFolderWidth;
}
gdi().textOutRect(x,od._textY,w,od._textHeight,od._text,od._engine);
}
}
bool cCheatWnd::parse(const std::string& aFileName)
{
bool res=false;
_fileName=aFileName;
u32 romcrc32,gamecode;
if(romData(_fileName,gamecode,romcrc32))
{
FILE* dat=fopen(SFN_CHEATS,"rb");
if(dat)
{
res=parseInternal(dat,gamecode,romcrc32);
fclose(dat);
}
}
return res;
}
bool cCheatWnd::romData(const std::string& aFileName,u32& aGameCode,u32& aCrc32)
{
bool res=false;
FILE* rom=fopen(aFileName.c_str(),"rb");
if(rom)
{
u8 header[512];
if(1==fread(header,sizeof(header),1,rom))
{
aCrc32=crc32(header,sizeof(header));
aGameCode=gamecode((const char*)(header+12));
res=true;
}
fclose(rom);
}
return res;
}
bool cCheatWnd::searchCheatData(FILE* aDat,u32 gamecode,u32 crc32,long& aPos,size_t& aSize)
{
aPos=0;
aSize=0;
const char* KHeader="R4 CheatCode";
char header[12];
fread(header,12,1,aDat);
if(strncmp(KHeader,header,12)) return false;
sDatIndex idx,nidx;
fseek(aDat,0,SEEK_END);
long fileSize=ftell(aDat);
fseek(aDat,0x100,SEEK_SET);
fread(&nidx,sizeof(nidx),1,aDat);
bool done=false;
while(!done)
{
memcpy(&idx,&nidx,sizeof(idx));
fread(&nidx,sizeof(nidx),1,aDat);
if(gamecode==idx._gameCode&&crc32==idx._crc32)
{
aSize=((nidx._offset)?nidx._offset:fileSize)-idx._offset;
aPos=idx._offset;
done=true;
}
if(!nidx._offset) done=true;
}
return (aPos&&aSize);
}
bool cCheatWnd::parseInternal(FILE* aDat,u32 gamecode,u32 crc32)
{
dbg_printf("%x, %x\n",gamecode,crc32);
_data.clear();
long dataPos; size_t dataSize;
if(!searchCheatData(aDat,gamecode,crc32,dataPos,dataSize)) return false;
fseek(aDat,dataPos,SEEK_SET);
dbg_printf("record found: %d\n",dataSize);
char* buffer=(char*)malloc(dataSize);
if(!buffer) return false;
fread(buffer,dataSize,1,aDat);
char* gameTitle=buffer;
u32* ccode=(u32*)(((u32)gameTitle+strlen(gameTitle)+4)&~3);
u32 cheatCount=*ccode;
cheatCount&=0x0fffffff;
ccode+=9;
u32 cc=0;
while(cc<cheatCount)
{
u32 folderCount=1;
char* folderName=NULL;
char* folderNote=NULL;
u32 flagItem=0;
if((*ccode>>28)&1)
{
flagItem|=cParsedItem::EInFolder;
if((*ccode>>24)==0x11) flagItem|=cParsedItem::EOne;
folderCount=*ccode&0x00ffffff;
folderName=(char*)((u32)ccode+4);
folderNote=(char*)((u32)folderName+strlen(folderName)+1);
_data.push_back(cParsedItem(folderName,folderNote,cParsedItem::EFolder));
cc++;
ccode=(u32*)(((u32)folderName+strlen(folderName)+1+strlen(folderNote)+1+3)&~3);
}
u32 selectValue=cParsedItem::ESelected;
for(size_t ii=0;ii<folderCount;++ii)
{
char* cheatName=(char*)((u32)ccode+4);
char* cheatNote=(char*)((u32)cheatName+strlen(cheatName)+1);
u32* cheatData=(u32*)(((u32)cheatNote+strlen(cheatNote)+1+3)&~3);
u32 cheatDataLen=*cheatData++;
if(cheatDataLen)
{
_data.push_back(cParsedItem(cheatName,cheatNote,flagItem|((*ccode&0xff000000)?selectValue:0),dataPos+(((char*)ccode+3)-buffer)));
if((*ccode&0xff000000)&&(flagItem&cParsedItem::EOne)) selectValue=0;
for(size_t jj=0;jj<cheatDataLen;++jj)
{
_data.back()._cheat+=formatString("%08X",*(cheatData+jj));
_data.back()._cheat+=((jj+1)%2)?" ":"\n";
}
if(cheatDataLen%2) _data.back()._cheat+="\n";
}
cc++;
ccode=(u32*)((u32)ccode+(((*ccode&0x00ffffff)+1)*4));
}
}
free(buffer);
generateList();
return true;
}
void cCheatWnd::generateList(void)
{
_indexes.clear();
_List.removeAllRows();
std::vector<cParsedItem>::iterator itr=_data.begin();
while(itr!=_data.end())
{
std::vector<std::string> row;
row.push_back("");
row.push_back((*itr)._title);
_List.insertRow(_List.getRowCount(),row);
_indexes.push_back(itr-_data.begin());
u32 flags=(*itr)._flags;
++itr;
if((flags&cParsedItem::EFolder)&&(flags&cParsedItem::EOpen)==0)
{
while(((*itr)._flags&cParsedItem::EInFolder)&&itr!=_data.end()) ++itr;
}
}
}
void cCheatWnd::deselectFolder(size_t anIndex)
{
std::vector<cParsedItem>::iterator itr=_data.begin()+anIndex;
while(--itr>=_data.begin())
{
if((*itr)._flags&cParsedItem::EFolder)
{
++itr;
break;
}
}
while(((*itr)._flags&cParsedItem::EInFolder)&&itr!=_data.end())
{
(*itr)._flags&=~cParsedItem::ESelected;
++itr;
}
}