From 41de6da14ee18ce5401aacf6cbc9b95238117c6d Mon Sep 17 00:00:00 2001 From: freakdave Date: Sun, 22 Jul 2012 19:54:04 +0200 Subject: [PATCH] (Xbox 1) Added a simple GUI, right now it browses and lists all files contained in the "roms" folder. The selected rom (its filename) is saved into T:\\tmp.retro. Press start and core.xbe will be launched. If you want to apply changes to the launching code, take a look into MenuMain.cpp. (Xbox 1) Todo: Pass arguments (filename, settings, etc.. ?) into core.xbe or read tmp.retro and get the filename / settings from there --- xbox1/RetroLaunch/Debug.cpp | 83 + xbox1/RetroLaunch/Debug.h | 41 + xbox1/RetroLaunch/Font.cpp | 229 ++ xbox1/RetroLaunch/Font.h | 53 + xbox1/RetroLaunch/Global.h | 53 + xbox1/RetroLaunch/IniFile.cpp | 152 + xbox1/RetroLaunch/IniFile.h | 98 + xbox1/RetroLaunch/Input.cpp | 328 ++ xbox1/RetroLaunch/Input.h | 171 + xbox1/RetroLaunch/IoSupport.cpp | 261 ++ xbox1/RetroLaunch/IoSupport.h | 53 + xbox1/RetroLaunch/Launcher.cpp | 72 + xbox1/RetroLaunch/MenuMain.cpp | 241 ++ xbox1/RetroLaunch/MenuMain.h | 99 + xbox1/RetroLaunch/MenuManager.cpp | 98 + xbox1/RetroLaunch/MenuManager.h | 50 + xbox1/RetroLaunch/RetroLaunch.sln | 33 + xbox1/RetroLaunch/RetroLaunch.vcproj | 447 +++ xbox1/RetroLaunch/Rom.cpp | 76 + xbox1/RetroLaunch/Rom.h | 44 + xbox1/RetroLaunch/RomList.cpp | 309 ++ xbox1/RetroLaunch/RomList.h | 72 + xbox1/RetroLaunch/SimpleIni.h | 3262 +++++++++++++++++ xbox1/RetroLaunch/Surface.cpp | 255 ++ xbox1/RetroLaunch/Surface.h | 90 + xbox1/RetroLaunch/Undocumented.h | 1373 +++++++ xbox1/RetroLaunch/Video.cpp | 138 + xbox1/RetroLaunch/Video.h | 53 + xbox1/RetroLaunch/dist/Media/arial.ttf | Bin 0 -> 65692 bytes xbox1/RetroLaunch/dist/Media/menuMainBG.png | Bin 0 -> 6415 bytes .../dist/Media/menuMainRomSelectPanel.png | Bin 0 -> 3206 bytes xbox1/RetroLaunch/dist/Roms/Add roms here.txt | 0 xbox1/RetroLaunch/retroarch.h | 5 + xbox1/RetroLaunch/titleimage.jpg | Bin 0 -> 8865 bytes 34 files changed, 8239 insertions(+) create mode 100644 xbox1/RetroLaunch/Debug.cpp create mode 100644 xbox1/RetroLaunch/Debug.h create mode 100644 xbox1/RetroLaunch/Font.cpp create mode 100644 xbox1/RetroLaunch/Font.h create mode 100644 xbox1/RetroLaunch/Global.h create mode 100644 xbox1/RetroLaunch/IniFile.cpp create mode 100644 xbox1/RetroLaunch/IniFile.h create mode 100644 xbox1/RetroLaunch/Input.cpp create mode 100644 xbox1/RetroLaunch/Input.h create mode 100644 xbox1/RetroLaunch/IoSupport.cpp create mode 100644 xbox1/RetroLaunch/IoSupport.h create mode 100644 xbox1/RetroLaunch/Launcher.cpp create mode 100644 xbox1/RetroLaunch/MenuMain.cpp create mode 100644 xbox1/RetroLaunch/MenuMain.h create mode 100644 xbox1/RetroLaunch/MenuManager.cpp create mode 100644 xbox1/RetroLaunch/MenuManager.h create mode 100644 xbox1/RetroLaunch/RetroLaunch.sln create mode 100644 xbox1/RetroLaunch/RetroLaunch.vcproj create mode 100644 xbox1/RetroLaunch/Rom.cpp create mode 100644 xbox1/RetroLaunch/Rom.h create mode 100644 xbox1/RetroLaunch/RomList.cpp create mode 100644 xbox1/RetroLaunch/RomList.h create mode 100644 xbox1/RetroLaunch/SimpleIni.h create mode 100644 xbox1/RetroLaunch/Surface.cpp create mode 100644 xbox1/RetroLaunch/Surface.h create mode 100644 xbox1/RetroLaunch/Undocumented.h create mode 100644 xbox1/RetroLaunch/Video.cpp create mode 100644 xbox1/RetroLaunch/Video.h create mode 100644 xbox1/RetroLaunch/dist/Media/arial.ttf create mode 100644 xbox1/RetroLaunch/dist/Media/menuMainBG.png create mode 100644 xbox1/RetroLaunch/dist/Media/menuMainRomSelectPanel.png create mode 100644 xbox1/RetroLaunch/dist/Roms/Add roms here.txt create mode 100644 xbox1/RetroLaunch/retroarch.h create mode 100644 xbox1/RetroLaunch/titleimage.jpg diff --git a/xbox1/RetroLaunch/Debug.cpp b/xbox1/RetroLaunch/Debug.cpp new file mode 100644 index 0000000000..1f94d72466 --- /dev/null +++ b/xbox1/RetroLaunch/Debug.cpp @@ -0,0 +1,83 @@ +/** + * RetroLaunch 2012 + * + * 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 2 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, write to the Free Software Foundation, Inc., + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. To contact the + * authors: Surreal64 CE Team (http://www.emuxtras.net) + */ + +#include "Global.h" +#include "Debug.h" + +CDebug g_debug; + +CDebug::CDebug(void) +{ + iLine = 1; + m_bFileOpen = false; +} + +CDebug::~CDebug(void) +{ +} + +void CDebug::Print(char *szMessage, ...) +{ + char szMsg[512]; + va_list vaArgList; + string szDebugFile ("D:\\debug.log");//(FixPath(PathRoot() + "debug.log")); + + va_start(vaArgList, szMessage); + vsprintf(szMsg, szMessage, vaArgList); + va_end(vaArgList); + + OutputDebugStringA(IntToString(iLine).c_str()); + OutputDebugStringA(": "); + OutputDebugStringA(szMsg); + OutputDebugStringA("\n\n"); + +#ifdef WIN32 + cout << IntToString(iLine)<< ": " << szMsg << endl << endl; +#endif + +//#ifdef _LOG + //Open the log file, create one if it does not exist, append the data + if(!m_bFileOpen) + { + fp = fopen(szDebugFile.c_str(), "w+"); + m_bFileOpen = true; + } + else + { + fp = fopen(szDebugFile.c_str(), "a+"); + } + + //print the data to the log file + fprintf(fp, IntToString(iLine).c_str()); + fprintf(fp, ": "); + fprintf(fp, szMsg); + fprintf(fp, "\r\n\r\n"); + + //close the file + fclose(fp); +//#endif //_LOG + + iLine ++; +} + + +string CDebug::IntToString(int value) +{ + stringstream ss; + + ss << value; + + return ss.str(); +} diff --git a/xbox1/RetroLaunch/Debug.h b/xbox1/RetroLaunch/Debug.h new file mode 100644 index 0000000000..5506100f44 --- /dev/null +++ b/xbox1/RetroLaunch/Debug.h @@ -0,0 +1,41 @@ +/** + * RetroLaunch 2012 + * + * 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 2 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, write to the Free Software Foundation, Inc., + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. To contact the + * authors: Surreal64 CE Team (http://www.emuxtras.net) + */ + +#ifndef _DEBUG_H__DASH_ +#define _DEBUG_H__DASH_ + +#if _MSC_VER > 1000 +#pragma once +#endif //_MSC_VER > 1000 + +class CDebug +{ +public: + CDebug(); + ~CDebug(); + + void Print(char *szMessage, ...); + string IntToString(int value); + +private: + int iLine; + FILE *fp; + bool m_bFileOpen; + +}; + +extern CDebug g_debug; + +#endif //_DEBUG_H__DASH_ \ No newline at end of file diff --git a/xbox1/RetroLaunch/Font.cpp b/xbox1/RetroLaunch/Font.cpp new file mode 100644 index 0000000000..e217735a8f --- /dev/null +++ b/xbox1/RetroLaunch/Font.cpp @@ -0,0 +1,229 @@ +/** + * RetroLaunch 2012 + * + * 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 2 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, write to the Free Software Foundation, Inc., + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. To contact the + * authors: Surreal64 CE Team (http://www.emuxtras.net) + */ + + +#ifdef _XBOX +#include "Font.h" + +#include + +Font g_font; + +Font::Font(void) +{ + m_pFont = NULL; +} + +Font::~Font(void) +{ + if (m_pFont) + m_pFont->Release(); +} + +bool Font::Create() +{ //Hardcoded + return Create("D:\\Media\\arial.ttf"); +} + +bool Font::Create(const string &szTTFFilename) +{ + if (m_pFont) + m_pFont->Release(); + + word *wcPathBuf = StringToWChar(szTTFFilename); + g_hResult = XFONT_OpenTrueTypeFont(wcPathBuf, 256 * 1024, &m_pFont); + + delete [] wcPathBuf; + + if (FAILED(g_hResult)) + return false; + + return true; +} + +void Font::Render(const string &str, int x, int y, dword height, dword style, D3DXCOLOR color, int maxWidth, bool fade, Align alignment) +{ + CSurface texture; + RenderToTexture(texture, str, height, style, color, maxWidth, fade); + + if (alignment != Left) + { + word *wcBuf = StringToWChar(str); + dword dwRequiredWidth; + m_pFont->GetTextExtent(wcBuf, -1, &dwRequiredWidth); + delete [] wcBuf; + + if (alignment == Center) + { + x -= (dwRequiredWidth / 2); + } + else if (alignment == Right) + { + x -= dwRequiredWidth; + } + } + + texture.Render(x, y); +} + +void Font::RenderToTexture(CSurface &texture, const string &str, dword height, dword style, D3DXCOLOR color, int maxWidth, bool fade) +{ + if (m_pFont == NULL) + return; + + m_pFont->SetTextHeight(height); + m_pFont->SetTextStyle(style); + m_pFont->SetTextColor(color); + + dword dwMaxWidth = (maxWidth <= 0) ? 1000 : maxWidth; + + // get the exact width and height required to display the string + dword dwRequiredWidth = GetRequiredWidth(str, height, style); + dword dwRequiredHeight = GetRequiredHeight(str, height, style);; + + // calculate the texture width and height needed to display the font + dword dwTextureWidth = dwRequiredWidth * 2; + dword dwTextureHeight = dwRequiredHeight * 2; + { + // because the textures are swizzled we make sure + // the dimensions are a power of two + for(dword wmask = 1; dwTextureWidth &(dwTextureWidth - 1); wmask = (wmask << 1 ) + 1) + { + dwTextureWidth = (dwTextureWidth + wmask) & ~wmask; + } + for(dword hmask = 1; dwTextureHeight &(dwTextureHeight - 1); hmask = (hmask << 1) + 1) + { + dwTextureHeight = ( dwTextureHeight + hmask ) & ~hmask; + } + + // also enforce a minimum pitch of 64 bytes + dwTextureWidth = max(64 / XGBytesPerPixelFromFormat(D3DFMT_A8R8G8B8), dwTextureWidth); + } + + // create an temporary image surface to render to + D3DSurface *pTempSurface; + g_video.m_pD3DDevice->CreateImageSurface(dwTextureWidth, dwTextureHeight, D3DFMT_LIN_A8R8G8B8, &pTempSurface); + + // clear the temporary surface + { + D3DLOCKED_RECT tmpLr; + pTempSurface->LockRect(&tmpLr, NULL, 0); + memset(tmpLr.pBits, 0, dwTextureWidth * dwTextureHeight * XGBytesPerPixelFromFormat(D3DFMT_A8R8G8B8)); + pTempSurface->UnlockRect(); + } + + // render the text to the temporary surface + word *wcBuf = StringToWChar(str); + m_pFont->TextOut(pTempSurface, wcBuf, -1, 0, 0); + delete [] wcBuf; + + // create the texture that will be drawn to the screen + texture.Destroy(); + texture.Create(dwTextureWidth, dwTextureHeight); + + // copy from the temporary surface to the final texture + { + D3DLOCKED_RECT tmpLr; + D3DLOCKED_RECT txtLr; + + pTempSurface->LockRect(&tmpLr, NULL, 0); + texture.GetTexture()->LockRect(0, &txtLr, NULL, 0); + + if (fade) + { + // draw the last 35 pixels of the string fading out to max width or texture width + dword dwMinFadeDistance = min(static_cast(dwTextureWidth * 0.35), 35); + dword dwFadeStart = min(dwTextureWidth, dwMaxWidth - dwMinFadeDistance); + dword dwFadeEnd = min(dwTextureWidth, dwMaxWidth); + dword dwFadeDistance = dwFadeEnd - dwFadeStart; + + for (dword h = 0; h < dwTextureHeight; h++) + { + for (dword w = 0; w < dwFadeDistance; w++) + { + dword *pColor = reinterpret_cast(tmpLr.pBits); + dword offset = (h * dwTextureWidth) + (dwFadeStart + w); + + D3DXCOLOR color = D3DXCOLOR(pColor[offset]); + color.a = color.a * (1.0f - static_cast(w) / static_cast(dwFadeDistance)); + pColor[offset] = color; + } + } + } + + // dont draw anything > than max width + for (dword h = 0; h < dwTextureHeight; h++) + { + for (dword w = min(dwTextureWidth, dwMaxWidth); w < dwTextureWidth; w++) + { + dword *pColor = reinterpret_cast(tmpLr.pBits); + dword offset = (h * dwTextureWidth) + w; + + D3DXCOLOR color = D3DXCOLOR(pColor[offset]); + color.a = 0.0; + pColor[offset] = color; + } + } + + // copy and swizzle the linear surface to the swizzled texture + XGSwizzleRect(tmpLr.pBits, tmpLr.Pitch, NULL, txtLr.pBits, dwTextureWidth, dwTextureHeight, NULL, 4); + + texture.GetTexture()->UnlockRect(0); + pTempSurface->UnlockRect(); + } + + pTempSurface->Release(); +} + +int Font::GetRequiredWidth(const string &str, dword height, dword style) +{ + word *wcBuf = StringToWChar(str); + dword reqWidth; + + m_pFont->SetTextHeight(height); + m_pFont->SetTextStyle(style); + m_pFont->GetTextExtent(wcBuf, -1, &reqWidth); + + delete [] wcBuf; + + return reqWidth; +} + +int Font::GetRequiredHeight(const string &str, dword height, dword style) +{ + word *wcBuf = StringToWChar(str); + dword reqHeight; + + m_pFont->SetTextHeight(height); + m_pFont->SetTextStyle(style); + m_pFont->GetFontMetrics(&reqHeight, NULL); + + delete [] wcBuf; + + return reqHeight; +} + + +word *Font::StringToWChar(const string &str) +{ + word *retVal = new word[(str.length() + 1) * 2]; + memset(retVal, 0, (str.length() + 1) * 2 * sizeof(word)); + + if (str.length() > 0) + mbstowcs(retVal, str.c_str(), str.length()); + + return retVal; +} +#endif \ No newline at end of file diff --git a/xbox1/RetroLaunch/Font.h b/xbox1/RetroLaunch/Font.h new file mode 100644 index 0000000000..3126ee2c9d --- /dev/null +++ b/xbox1/RetroLaunch/Font.h @@ -0,0 +1,53 @@ +/** + * RetroLaunch 2012 + * + * 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 2 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, write to the Free Software Foundation, Inc., + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. To contact the + * authors: Surreal64 CE Team (http://www.emuxtras.net) + */ + +#pragma once +#ifdef _XBOX +#include "Global.h" +#include "Surface.h" + +#define XFONT_TRUETYPE // use true type fonts +#include + +enum Align +{ + Left, + Center, + Right +}; + +class Font +{ +public: + Font(void); + ~Font(void); + + bool Create(); + bool Create(const string &szTTFFilename); + + void Render(const string &str, int x, int y, dword height, dword style = XFONT_NORMAL, D3DXCOLOR color = D3DCOLOR_XRGB(0, 0, 0), int dwMaxWidth = -1, bool fade = false, Align alignment = Left); + void RenderToTexture(CSurface &texture, const string &str, dword height, dword style = XFONT_NORMAL, D3DXCOLOR color = D3DCOLOR_XRGB(0, 0, 0), int maxWidth = -1, bool fade = false); + + int GetRequiredWidth(const string &str, dword height, dword style); + int GetRequiredHeight(const string &str, dword height, dword style); + + word *StringToWChar(const string &str); + +private: + XFONT *m_pFont; +}; + +extern Font g_font; +#endif \ No newline at end of file diff --git a/xbox1/RetroLaunch/Global.h b/xbox1/RetroLaunch/Global.h new file mode 100644 index 0000000000..f84ba2529f --- /dev/null +++ b/xbox1/RetroLaunch/Global.h @@ -0,0 +1,53 @@ +/** + * RetroLaunch 2012 + * + * 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 2 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, write to the Free Software Foundation, Inc., + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. To contact the + * authors: Surreal64 CE Team (http://www.emuxtras.net) + */ + +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#ifdef _XBOX + #include + #include +#else + #pragma comment(lib,"d3d8.lib") + #pragma comment(lib,"d3dx8.lib") + #pragma comment(lib,"DxErr8.lib") + #define WIN32_LEAN_AND_MEAN + #include + #include + #include + #include +#endif + + +using namespace std; + +#define XBUILD "Launcher CE" + +typedef unsigned __int8 byte; +typedef unsigned __int16 word; +typedef unsigned __int32 dword; +typedef unsigned __int64 qword; + + diff --git a/xbox1/RetroLaunch/IniFile.cpp b/xbox1/RetroLaunch/IniFile.cpp new file mode 100644 index 0000000000..cfa7e04bbc --- /dev/null +++ b/xbox1/RetroLaunch/IniFile.cpp @@ -0,0 +1,152 @@ +/** + * RetroLaunch 2012 + * + * 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 2 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, write to the Free Software Foundation, Inc., + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. To contact the + * authors: Surreal64 CE Team (http://www.emuxtras.net) + */ + +#include "SimpleIni.h" +#include "IniFile.h" + + +IniFile g_iniFile; + +//FIXME: SCREEN XPOS, YPOS, XSCALE, YSCALE should be floats! +//FIXME: Path for WIN32 + +IniFile::IniFile(void) +{ +} + +IniFile::~IniFile(void) +{ +} + +bool IniFile::Save(const string &szIniFileName) +{ + CSimpleIniA ini; + SI_Error rc; + ini.SetUnicode(true); + ini.SetMultiKey(true); + ini.SetMultiLine(true); + + //GENERAL SETTINGS + ini.SetBoolValue("GENERAL SETTINGS", "SHOW DEBUG INFO", m_currentIniEntry.bShowDebugInfo); + + //VIDEO SETTINGS + ini.SetBoolValue("VIDEO SETTINGS", "AUTOMATIC FRAME SKIP", m_currentIniEntry.bAutomaticFrameSkip); + + rc = ini.SaveFile(szIniFileName.c_str()); + + OutputDebugStringA(szIniFileName.c_str()); + + if (rc < 0) + { + OutputDebugStringA(" failed to save!\n"); + return false; + } + + OutputDebugStringA(" saved successfully!\n"); + return true; +} + +bool IniFile::Load(const string &szIniFileName) +{ + CSimpleIniA ini; + SI_Error rc; + ini.SetUnicode(true); + ini.SetMultiKey(true); + ini.SetMultiLine(true); + + rc = ini.LoadFile(szIniFileName.c_str()); + + if (rc < 0) + { + OutputDebugString("Failed to load "); + OutputDebugString(szIniFileName.c_str()); + OutputDebugString("\n"); + return false; + } + + OutputDebugStringA("Successfully loaded "); + OutputDebugString(szIniFileName.c_str()); + OutputDebugString("\n"); + + //GENERAL SETTINGS + m_currentIniEntry.bShowDebugInfo = ini.GetBoolValue("GENERAL SETTINGS", "SHOW DEBUG INFO", NULL ); + + //VIDEO SETTINGS + m_currentIniEntry.bAutomaticFrameSkip = ini.GetBoolValue("VIDEO SETTINGS", "AUTOMATIC FRAME SKIP", NULL ); + + return true; +} + +bool IniFile::CreateAndSaveDefaultIniEntry() +{ + //GENERAL SETTINGS + m_defaultIniEntry.bShowDebugInfo = false; + + //VIDEO SETTINGS + m_defaultIniEntry.bAutomaticFrameSkip = true; + + // our current ini is now the default ini + m_currentIniEntry = m_defaultIniEntry; + + // save the default ini + // FIXME! -> CD/DVD -> utility drive X: + Save("D:\\retrolaunch.ini"); + + return true; +} + + +bool IniFile::CheckForIniEntry() +{ + // try to load our ini file + if(!Load("D:\\retrolaunch.ini")) + { + // create a new one, if it doesn't exist + CreateAndSaveDefaultIniEntry(); + } + + return true; +} + + +bool IniFile::SaveTempRomFileName(const string &szFileName) +{ + CSimpleIniA ini; + SI_Error rc; + ini.SetUnicode(true); + ini.SetMultiKey(true); + ini.SetMultiLine(true); + + ini.SetValue("LAUNCHER", "ROM", szFileName.c_str()); + + DeleteFile("T:\\tmp.retro"); + rc = ini.SaveFile("T:\\tmp.retro"); + + OutputDebugStringA("T:\\tmp.retro"); + + if (rc < 0) + { + OutputDebugStringA(" failed to save!\n"); + return false; + } + + OutputDebugStringA(" saved successfully!\n"); + return true; + +} + + + + diff --git a/xbox1/RetroLaunch/IniFile.h b/xbox1/RetroLaunch/IniFile.h new file mode 100644 index 0000000000..dcc28505e7 --- /dev/null +++ b/xbox1/RetroLaunch/IniFile.h @@ -0,0 +1,98 @@ +/** + * RetroLaunch 2012 + * + * 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 2 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, write to the Free Software Foundation, Inc., + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. To contact the + * authors: Surreal64 CE Team (http://www.emuxtras.net) + */ + +#pragma once +#include "Global.h" + +struct IniFileEntry +{ + //debug output + bool bShowDebugInfo; + + //automatic frame skip method + bool bAutomaticFrameSkip; + + //manual frame skip method (throttlespeed) + bool bManualFrameSkip; + + //number of frames to skip + dword dwNumFrameSkips; + + //sync audio to video + bool bSyncAudioToVideo; + + //vertical synchronization + bool bVSync; + + //flicker filter + dword dwFlickerFilter; + + //soft display filter + bool bSoftDisplayFilter; + + //texture filter + DWORD dwTextureFilter; + + //screen xpos + dword dwXPOS; + //screen ypos + dword dwYPOS; + //screen xscale + dword dwXWIDTH; + //screen yscale + dword dwYHEIGHT; + + //hide normal scroll 0 + bool bHideNBG0; + //hide normal scroll 1 + bool bHideNBG1; + //hide normal scroll 2 + bool bHideNBG2; + //hide normal scroll 3 + bool bHideNBG3; + //hide rotation scroll 0 + bool bHideRBG0; + //hide VDP1 + bool bHideVDP1; +}; + + +class IniFile +{ +public: + IniFile(); + ~IniFile(); + + bool Save(const string &szIniFileName); + bool SaveTempRomFileName(const string &szFileName); + bool Load(const string &szIniFileName); + bool CreateAndSaveDefaultIniEntry(); + bool CheckForIniEntry(); + + IniFileEntry m_currentIniEntry; + +private: + IniFileEntry m_defaultIniEntry; + + string szRomFileName; + +}; + +extern IniFile g_iniFile; + + + + + diff --git a/xbox1/RetroLaunch/Input.cpp b/xbox1/RetroLaunch/Input.cpp new file mode 100644 index 0000000000..fc79b5357c --- /dev/null +++ b/xbox1/RetroLaunch/Input.cpp @@ -0,0 +1,328 @@ +/** + * RetroLaunch 2012 + * + * 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 2 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, write to the Free Software Foundation, Inc., + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. To contact the + * authors: Surreal64 CE Team (http://www.emuxtras.net) + */ + +#ifdef _XBOX +#include "Input.h" + +Input g_input; + +Input::Input(void) +{ + m_pollingParameters.fAutoPoll = TRUE; + m_pollingParameters.fInterruptOut = TRUE; + m_pollingParameters.bInputInterval = 8; + m_pollingParameters.bOutputInterval = 8; + m_lastTick = GetTickCount(); + m_buttonDelay = XBINPUT_PRESS_BUTTON_DELAY; + m_triggerDelay = XBINPUT_PRESS_TRIGGER_DELAY; + m_buttonPressed = false; +} + +Input::~Input(void) +{ +} + +bool Input::Create() +{ + XInitDevices(0, NULL); + + // get a mask of all currently available devices + dword dwDeviceMask = XGetDevices(XDEVICE_TYPE_GAMEPAD); + + // open the devices + for(dword i = 0; i < XGetPortCount(); i++) + { + ZeroMemory(&m_inputStates[i], sizeof(XINPUT_STATE)); + ZeroMemory(&m_gamepads[i], sizeof(XBGAMEPAD)); + + if(dwDeviceMask & (1 << i)) + { + // get a handle to the device + m_gamepads[i].hDevice = XInputOpen(XDEVICE_TYPE_GAMEPAD, i, + XDEVICE_NO_SLOT, &m_pollingParameters); + + // store capabilities of the device + XInputGetCapabilities(m_gamepads[i].hDevice, &m_gamepads[i].caps); + + // initialize last pressed buttons + XInputGetState(m_gamepads[i].hDevice, &m_inputStates[i]); + + m_gamepads[i].wLastButtons = m_inputStates[i].Gamepad.wButtons; + + for(dword b = 0; b < 8; b++) + { + m_gamepads[i].bLastAnalogButtons[b] = + // Turn the 8-bit polled value into a boolean value + (m_inputStates[i].Gamepad.bAnalogButtons[b] > XINPUT_GAMEPAD_MAX_CROSSTALK); + } + } + } + + return true; +} + +void Input::RefreshDevices() +{ + dword dwInsertions, dwRemovals; + + XGetDeviceChanges(XDEVICE_TYPE_GAMEPAD, reinterpret_cast(&dwInsertions), reinterpret_cast(&dwRemovals)); + + // loop through all gamepads + for(dword i = 0; i < XGetPortCount(); i++) + { + // handle removed devices + m_gamepads[i].bRemoved = (dwRemovals & (1 << i)) ? true : false; + + if(m_gamepads[i].bRemoved) + { + // if the controller was removed after XGetDeviceChanges but before + // XInputOpen, the device handle will be NULL + if(m_gamepads[i].hDevice) + XInputClose(m_gamepads[i].hDevice); + + m_gamepads[i].hDevice = NULL; + m_gamepads[i].Feedback.Rumble.wLeftMotorSpeed = 0; + m_gamepads[i].Feedback.Rumble.wRightMotorSpeed = 0; + } + + // handle inserted devices + m_gamepads[i].bInserted = (dwInsertions & (1 << i)) ? true : false; + + if(m_gamepads[i].bInserted) + { + m_gamepads[i].hDevice = XInputOpen(XDEVICE_TYPE_GAMEPAD, i, + XDEVICE_NO_SLOT, &m_pollingParameters ); + + // if the controller is removed after XGetDeviceChanges but before + // XInputOpen, the device handle will be NULL + if(m_gamepads[i].hDevice) + { + XInputGetCapabilities(m_gamepads[i].hDevice, &m_gamepads[i].caps); + + // initialize last pressed buttons + XInputGetState(m_gamepads[i].hDevice, &m_inputStates[i]); + + m_gamepads[i].wLastButtons = m_inputStates[i].Gamepad.wButtons; + + for(dword b = 0; b < 8; b++) + { + m_gamepads[i].bLastAnalogButtons[b] = + // Turn the 8-bit polled value into a boolean value + (m_inputStates[i].Gamepad.bAnalogButtons[b] > XINPUT_GAMEPAD_MAX_CROSSTALK); + } + } + } + } +} + +void Input::GetInput() +{ + RefreshDevices(); + + if (m_buttonPressed) + { + m_lastTick = GetTickCount(); + m_buttonPressed = false; + } + + // loop through all gamepads + for(dword i = 0; i < XGetPortCount(); i++) + { + // if we have a valid device, poll it's state and track button changes + if(m_gamepads[i].hDevice) + { + // read the input state + XInputGetState(m_gamepads[i].hDevice, &m_inputStates[i]); + + // copy gamepad to local structure + memcpy(&m_gamepads[i], &m_inputStates[i].Gamepad, sizeof(XINPUT_GAMEPAD)); + + // put xbox device input for the gamepad into our custom format + float fX1 = (m_gamepads[i].sThumbLX + 0.5f) / 32767.5f; + m_gamepads[i].fX1 = (fX1 >= 0.0f ? 1.0f : -1.0f) * + max(0.0f, (fabsf(fX1) - XBINPUT_DEADZONE) / (1.0f - XBINPUT_DEADZONE)); + + float fY1 = (m_gamepads[i].sThumbLY + 0.5f) / 32767.5f; + m_gamepads[i].fY1 = (fY1 >= 0.0f ? 1.0f : -1.0f) * + max(0.0f, (fabsf(fY1) - XBINPUT_DEADZONE) / (1.0f - XBINPUT_DEADZONE)); + + float fX2 = (m_gamepads[i].sThumbRX + 0.5f) / 32767.5f; + m_gamepads[i].fX2 = (fX2 >= 0.0f ? 1.0f : -1.0f) * + max(0.0f, (fabsf(fX2) - XBINPUT_DEADZONE) / (1.0f - XBINPUT_DEADZONE)); + + float fY2 = (m_gamepads[i].sThumbRY + 0.5f) / 32767.5f; + m_gamepads[i].fY2 = (fY2 >= 0.0f ? 1.0f : -1.0f) * + max(0.0f, (fabsf(fY2) - XBINPUT_DEADZONE) / (1.0f - XBINPUT_DEADZONE)); + + // get the boolean buttons that have been pressed since the last + // call. each button is represented by one bit. + m_gamepads[i].wPressedButtons = (m_gamepads[i].wLastButtons ^ m_gamepads[i].wButtons) & m_gamepads[i].wButtons; + m_gamepads[i].wLastButtons = m_gamepads[i].wButtons; + + // get the analog buttons that have been pressed or released since + // the last call. + for(dword b = 0; b < 8; b++) + { + // turn the 8-bit polled value into a boolean value + bool bPressed = (m_gamepads[i].bAnalogButtons[b] > XINPUT_GAMEPAD_MAX_CROSSTALK); + + if(bPressed) + m_gamepads[i].bPressedAnalogButtons[b] = !m_gamepads[i].bLastAnalogButtons[b]; + else + m_gamepads[i].bPressedAnalogButtons[b] = false; + + // store the current state for the next time + m_gamepads[i].bLastAnalogButtons[b] = bPressed; + } + } + } +} + +bool Input::IsButtonPressed(XboxButton button) +{ + if (m_lastTick + m_buttonDelay > GetTickCount()) + return false; + + bool buttonDown = false; + + switch (button) + { + case XboxLeftThumbLeft: + buttonDown = (m_gamepads[0].fX1 < -0.5); + break; + case XboxLeftThumbRight: + buttonDown = (m_gamepads[0].fX1 > 0.5); + break; + case XboxLeftThumbUp: + buttonDown = (m_gamepads[0].fY1 > 0.5); + break; + case XboxLeftThumbDown: + buttonDown = (m_gamepads[0].fY1 < -0.5); + break; + case XboxRightThumbLeft: + buttonDown = (m_gamepads[0].fX2 < -0.5); + break; + case XboxRightThumbRight: + buttonDown = (m_gamepads[0].fX2 > 0.5); + break; + case XboxRightThumbUp: + buttonDown = (m_gamepads[0].fY2 > 0.5); + break; + case XboxRightThumbDown: + buttonDown = (m_gamepads[0].fY2 < -0.5); + break; + case XboxDPadLeft: + buttonDown = ((m_gamepads[0].wButtons & XINPUT_GAMEPAD_DPAD_LEFT) != 0); + break; + case XboxDPadRight: + buttonDown = ((m_gamepads[0].wButtons & XINPUT_GAMEPAD_DPAD_RIGHT) != 0); + break; + case XboxDPadUp: + buttonDown = ((m_gamepads[0].wButtons & XINPUT_GAMEPAD_DPAD_UP) != 0); + break; + case XboxDPadDown: + buttonDown = ((m_gamepads[0].wButtons & XINPUT_GAMEPAD_DPAD_DOWN) != 0); + break; + case XboxStart: + buttonDown = ((m_gamepads[0].wButtons & XINPUT_GAMEPAD_START) != 0); + break; + case XboxBack: + buttonDown = ((m_gamepads[0].wButtons & XINPUT_GAMEPAD_BACK) != 0); + break; + case XboxA: + buttonDown = (m_gamepads[0].bAnalogButtons[XINPUT_GAMEPAD_A] > 30); + break; + case XboxB: + buttonDown = (m_gamepads[0].bAnalogButtons[XINPUT_GAMEPAD_B] > 30); + break; + case XboxX: + buttonDown = (m_gamepads[0].bAnalogButtons[XINPUT_GAMEPAD_X] > 30); + break; + case XboxY: + buttonDown = (m_gamepads[0].bAnalogButtons[XINPUT_GAMEPAD_Y] > 30); + break; + case XboxWhite: + buttonDown = (m_gamepads[0].bAnalogButtons[XINPUT_GAMEPAD_WHITE] > 30); + break; + case XboxBlack: + buttonDown = (m_gamepads[0].bAnalogButtons[XINPUT_GAMEPAD_BLACK] > 30); + break; + case XboxLeftThumbButton: + buttonDown = ((m_gamepads[0].wButtons & XINPUT_GAMEPAD_LEFT_THUMB) != 0); + break; + case XboxRightThumbButton: + buttonDown = ((m_gamepads[0].wButtons & XINPUT_GAMEPAD_RIGHT_THUMB) != 0); + break; + default: + return false; + } + + if (buttonDown) + { + m_buttonPressed = true; + return true; + } + + return false; +} + +byte Input::IsLTriggerPressed() +{ + if (m_lastTick + m_triggerDelay > GetTickCount()) + return 0; + + if (m_gamepads[0].bAnalogButtons[XINPUT_GAMEPAD_LEFT_TRIGGER] < 30) + return 0; + else + { + m_buttonPressed = true; + return m_gamepads[0].bAnalogButtons[XINPUT_GAMEPAD_LEFT_TRIGGER]; + } +} + +byte Input::IsRTriggerPressed() +{ + if (m_lastTick + m_triggerDelay > GetTickCount()) + return 0; + + if (m_gamepads[0].bAnalogButtons[XINPUT_GAMEPAD_RIGHT_TRIGGER] < 30) + return 0; + else + { + m_buttonPressed = true; + return m_gamepads[0].bAnalogButtons[XINPUT_GAMEPAD_RIGHT_TRIGGER]; + } +} + +DWORD Input::GetButtonDelay() +{ + return m_buttonDelay; +} + +void Input::SetButtonDelay(DWORD milliseconds) +{ + m_buttonDelay = milliseconds; +} + +DWORD Input::GetTriggerDelay() +{ + return m_triggerDelay; +} + +void Input::SetTriggerDelay(DWORD milliseconds) +{ + m_triggerDelay = milliseconds; +} +#endif \ No newline at end of file diff --git a/xbox1/RetroLaunch/Input.h b/xbox1/RetroLaunch/Input.h new file mode 100644 index 0000000000..c20ae81868 --- /dev/null +++ b/xbox1/RetroLaunch/Input.h @@ -0,0 +1,171 @@ +/** + * RetroLaunch 2012 + * + * 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 2 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, write to the Free Software Foundation, Inc., + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. To contact the + * authors: Surreal64 CE Team (http://www.emuxtras.net) + */ + +#pragma once +#ifdef _XBOX +#include "Global.h" + +#define XBINPUT_DEADZONE 0.24f +#define XBINPUT_PRESS_BUTTON_DELAY 200 +#define XBINPUT_PRESS_TRIGGER_DELAY 100 + +struct XBGAMEPAD : public XINPUT_GAMEPAD +{ + // thumb stick values converted to range [-1,+1] + float fX1; + float fY1; + float fX2; + float fY2; + + // state of buttons tracked since last poll + word wLastButtons; + bool bLastAnalogButtons[8]; + word wPressedButtons; + bool bPressedAnalogButtons[8]; + + // rumble properties + XINPUT_RUMBLE Rumble; + XINPUT_FEEDBACK Feedback; + + // device properties + XINPUT_CAPABILITIES caps; + HANDLE hDevice; + + // flags for whether game pad was just inserted or removed + bool bInserted; + bool bRemoved; +}; + +#define XBOX_BUTTON_COUNT 23 + +enum XboxButton +{ + XboxLeftThumbLeft, + XboxLeftThumbRight, + XboxLeftThumbUp, + XboxLeftThumbDown, + + XboxRightThumbLeft, + XboxRightThumbRight, + XboxRightThumbUp, + XboxRightThumbDown, + + XboxDPadLeft, + XboxDPadRight, + XboxDPadUp, + XboxDPadDown, + + XboxStart, + XboxBack, + + XboxLeftThumbButton, + XboxRightThumbButton, + + XboxA, + XboxB, + XboxX, + XboxY, + + XboxBlack, + XboxWhite, + + XboxLeftTrigger, + XboxRightTrigger, +}; + +enum N64Button +{ + N64ThumbLeft, + N64ThumbRight, + N64ThumbUp, + N64ThumbDown, + + N64DPadLeft, + N64DPadRight, + N64DPadUp, + N64DPadDown, + + N64CButtonLeft, + N64CButtonRight, + N64CButtonUp, + N64CButtonDown, + + N64Start, + + N64A, + N64B, + + N64ZTrigger, + N64LeftTrigger, + N64RightTrigger +}; + + +enum SATURNButton +{ + SATURNDPadLeft, + SATURNDPadRight, + SATURNDPadUp, + SATURNDPadDown, + + SATURNA, + SATURNB, + SATURNX, + SATURNY, + SATURNC, + SATURNZ, + + SATURNStart, + + SATURNRightTrigger, + SATURNLeftTrigger +}; + +class Input +{ +public: + Input(void); + ~Input(void); + + bool Create(); + void GetInput(); + + bool IsButtonPressed(XboxButton button); + + byte IsLTriggerPressed(); + byte IsRTriggerPressed(); + + DWORD GetButtonDelay(); + void SetButtonDelay(DWORD milliseconds); + + DWORD GetTriggerDelay(); + void SetTriggerDelay(DWORD milliseconds); + +private: + void RefreshDevices(); + +private: + XINPUT_POLLING_PARAMETERS m_pollingParameters; + XINPUT_STATE m_inputStates[4]; + XBGAMEPAD m_gamepads[4]; + + bool m_buttonPressed; + DWORD m_buttonDelay; + DWORD m_triggerDelay; + DWORD m_lastTick; +}; + +extern Input g_input; +#endif \ No newline at end of file diff --git a/xbox1/RetroLaunch/IoSupport.cpp b/xbox1/RetroLaunch/IoSupport.cpp new file mode 100644 index 0000000000..0885ed76c2 --- /dev/null +++ b/xbox1/RetroLaunch/IoSupport.cpp @@ -0,0 +1,261 @@ +// IoSupport.cpp: implementation of the CIoSupport class. +// +////////////////////////////////////////////////////////////////////// +#ifdef _XBOX +#include "iosupport.h" +#include "undocumented.h" + +#include + +#define CTLCODE(DeviceType, Function, Method, Access) ( ((DeviceType) << 16) | ((Access) << 14) | ((Function) << 2) | (Method) ) +#define FSCTL_DISMOUNT_VOLUME CTLCODE( FILE_DEVICE_FILE_SYSTEM, 0x08, METHOD_BUFFERED, FILE_ANY_ACCESS ) + +////////////////////////////////////////////////////////////////////// +// Construction/Destruction +////////////////////////////////////////////////////////////////////// + +CIoSupport g_IOSupport; + +CIoSupport::CIoSupport() +{ + m_dwLastTrayState = 0; +} + +CIoSupport::~CIoSupport() +{ + +} + +// szDrive e.g. "D:" +// szDevice e.g. "Cdrom0" or "Harddisk0\Partition6" + +HRESULT CIoSupport::Mount(CHAR* szDrive, CHAR* szDevice) +{ + CHAR szSourceDevice[48]; + CHAR szDestinationDrive[16]; + + sprintf(szSourceDevice, "\\Device\\%s", szDevice); + sprintf(szDestinationDrive, "\\??\\%s", szDrive); + + STRING DeviceName = + { + strlen(szSourceDevice), + strlen(szSourceDevice) + 1, + szSourceDevice + }; + + STRING LinkName = + { + strlen(szDestinationDrive), + strlen(szDestinationDrive) + 1, + szDestinationDrive + }; + + IoCreateSymbolicLink(&LinkName, &DeviceName); + + return S_OK; +} + + + +// szDrive e.g. "D:" + +HRESULT CIoSupport::Unmount(CHAR* szDrive) +{ + char szDestinationDrive[16]; + sprintf(szDestinationDrive, "\\??\\%s", szDrive); + + STRING LinkName = + { + strlen(szDestinationDrive), + strlen(szDestinationDrive) + 1, + szDestinationDrive + }; + + IoDeleteSymbolicLink(&LinkName); + + return S_OK; +} + + + + + +HRESULT CIoSupport::Remount(CHAR* szDrive, CHAR* szDevice) +{ + CHAR szSourceDevice[48]; + sprintf(szSourceDevice, "\\Device\\%s", szDevice); + + Unmount(szDrive); + + ANSI_STRING filename; + OBJECT_ATTRIBUTES attributes; + IO_STATUS_BLOCK status; + HANDLE hDevice; + NTSTATUS error; + DWORD dummy; + + RtlInitAnsiString(&filename, szSourceDevice); + InitializeObjectAttributes(&attributes, &filename, OBJ_CASE_INSENSITIVE, NULL); + + if (!NT_SUCCESS(error = NtCreateFile(&hDevice, GENERIC_READ | + SYNCHRONIZE | FILE_READ_ATTRIBUTES, &attributes, &status, NULL, 0, + FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, FILE_OPEN, + FILE_NON_DIRECTORY_FILE | FILE_SYNCHRONOUS_IO_NONALERT))) + { + return E_FAIL; + } + + if (!DeviceIoControl(hDevice, FSCTL_DISMOUNT_VOLUME, NULL, 0, NULL, 0, &dummy, NULL)) + { + CloseHandle(hDevice); + return E_FAIL; + } + + CloseHandle(hDevice); + Mount(szDrive, szDevice); + + return S_OK; +} + +HRESULT CIoSupport::Remap(CHAR* szMapping) +{ + char szMap[32]; + strcpy(szMap, szMapping ); + + char* pComma = strstr(szMap, ","); + if (pComma) + { + *pComma = 0; + + // map device to drive letter + Unmount(szMap); + Mount(szMap, &pComma[1]); + return S_OK; + } + + return E_FAIL; +} + + +HRESULT CIoSupport::EjectTray() +{ + HalWriteSMBusValue(0x20, 0x0C, FALSE, 0); // eject tray + return S_OK; +} + +HRESULT CIoSupport::CloseTray() +{ + HalWriteSMBusValue(0x20, 0x0C, FALSE, 1); // close tray + return S_OK; +} + +DWORD CIoSupport::GetTrayState() +{ + HalReadSMCTrayState(&m_dwTrayState, &m_dwTrayCount); + + if(m_dwTrayState == TRAY_CLOSED_MEDIA_PRESENT) + { + if (m_dwLastTrayState != TRAY_CLOSED_MEDIA_PRESENT) + { + m_dwLastTrayState = m_dwTrayState; + return DRIVE_CLOSED_MEDIA_PRESENT; + } + else + { + return DRIVE_READY; + } + } + else if(m_dwTrayState == TRAY_CLOSED_NO_MEDIA) + { + m_dwLastTrayState = m_dwTrayState; + return DRIVE_CLOSED_NO_MEDIA; + } + else if(m_dwTrayState == TRAY_OPEN) + { + m_dwLastTrayState = m_dwTrayState; + return DRIVE_OPEN; + } + else + { + m_dwLastTrayState = m_dwTrayState; + } + + return DRIVE_NOT_READY; +} + +HRESULT CIoSupport::Shutdown() +{ + HalInitiateShutdown(); + return S_OK; +} + +HANDLE CIoSupport::CreateFile() +{ + ANSI_STRING filename; + OBJECT_ATTRIBUTES attributes; + IO_STATUS_BLOCK status; + HANDLE hDevice; + NTSTATUS error; + + RtlInitAnsiString(&filename, "\\Device\\Cdrom0"); + InitializeObjectAttributes(&attributes, &filename, OBJ_CASE_INSENSITIVE, NULL); + + if (!NT_SUCCESS(error = NtCreateFile(&hDevice, GENERIC_READ | + SYNCHRONIZE | FILE_READ_ATTRIBUTES, &attributes, &status, NULL, 0, + FILE_SHARE_READ, FILE_OPEN, FILE_NON_DIRECTORY_FILE | FILE_SYNCHRONOUS_IO_NONALERT))) + { + return NULL; + } + + return hDevice; +} + +BOOL CIoSupport::GetFirstFile(CHAR* szFilename) +{ + ANSI_STRING filename; + OBJECT_ATTRIBUTES attributes; + IO_STATUS_BLOCK status; + HANDLE hDevice; + NTSTATUS error; + + RtlInitAnsiString(&filename, "\\Device\\Cdrom0"); + InitializeObjectAttributes(&attributes, &filename, OBJ_CASE_INSENSITIVE, NULL); + + if (!NT_SUCCESS(error = NtCreateFile(&hDevice, GENERIC_READ | + SYNCHRONIZE | FILE_READ_ATTRIBUTES, &attributes, &status, NULL, 0, + FILE_SHARE_READ, FILE_OPEN, FILE_NON_DIRECTORY_FILE | FILE_SYNCHRONOUS_IO_NONALERT))) + { + OutputDebugString("Unable to open Cdrom0.\n"); + return FALSE; + } + + + CHAR* szBuffer = new CHAR[2048]; + DWORD dwRead = 0; + + SetFilePointer(hDevice, 19 * 2048, NULL, FILE_BEGIN); + if (!ReadFile(hDevice, szBuffer, 2048, &dwRead, NULL)) + { + OutputDebugString("Unable to read ISO9660 root directory.\n"); + CloseHandle(hDevice); + return FALSE; + } + + CloseHandle(hDevice); + szBuffer[2047] = 0; + + int offset = 0; + while (szBuffer[offset] == 0x22) offset += 0x22; + offset += 33; // jump to start of filename + + strcpy(szFilename, "#"); + strcat(szFilename, &szBuffer[offset]); + + if (szBuffer) + delete [] szBuffer; + + return TRUE; +} + +#endif \ No newline at end of file diff --git a/xbox1/RetroLaunch/IoSupport.h b/xbox1/RetroLaunch/IoSupport.h new file mode 100644 index 0000000000..2319c1b3ed --- /dev/null +++ b/xbox1/RetroLaunch/IoSupport.h @@ -0,0 +1,53 @@ +// IoSupport.h: interface for the CIoSupport class. +// +////////////////////////////////////////////////////////////////////// + +#if !defined(AFX_IOSUPPORT_H__F084A488_BD6E_49D5_8CD3_0BE62149DB40__INCLUDED_) +#define AFX_IOSUPPORT_H__F084A488_BD6E_49D5_8CD3_0BE62149DB40__INCLUDED_ + +#if _MSC_VER > 1000 +#pragma once +#endif // _MSC_VER > 1000 +#ifdef _XBOX +#include +#include "global.h" + +#define TRAY_OPEN 16 +#define TRAY_CLOSED_NO_MEDIA 64 +#define TRAY_CLOSED_MEDIA_PRESENT 96 + +#define DRIVE_OPEN 0 // Open... +#define DRIVE_NOT_READY 1 // Opening.. Closing... +#define DRIVE_READY 2 +#define DRIVE_CLOSED_NO_MEDIA 3 // CLOSED...but no media in drive +#define DRIVE_CLOSED_MEDIA_PRESENT 4 // Will be send once when the drive just have closed + +class CIoSupport +{ +public: + CIoSupport(); + virtual ~CIoSupport(); + + HRESULT Mount(CHAR* szDrive, CHAR* szDevice); + HRESULT Unmount(CHAR* szDrive); + + HRESULT Remount(CHAR* szDrive, CHAR* szDevice); + HRESULT Remap(CHAR* szMapping); + + DWORD GetTrayState(); + HRESULT EjectTray(); + HRESULT CloseTray(); + HRESULT Shutdown(); + + HANDLE CreateFile(); + BOOL GetFirstFile(CHAR* szFilename); + +private: + DWORD m_dwTrayState; + DWORD m_dwTrayCount; + DWORD m_dwLastTrayState; +}; + +extern CIoSupport g_IOSupport; +#endif +#endif // !defined(AFX_IOSUPPORT_H__F084A488_BD6E_49D5_8CD3_0BE62149DB40__INCLUDED_) diff --git a/xbox1/RetroLaunch/Launcher.cpp b/xbox1/RetroLaunch/Launcher.cpp new file mode 100644 index 0000000000..4b28736f7b --- /dev/null +++ b/xbox1/RetroLaunch/Launcher.cpp @@ -0,0 +1,72 @@ +/** + * RetroLaunch 2012 + * + * 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 2 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, write to the Free Software Foundation, Inc., + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. To contact the + * authors: Surreal64 CE Team (http://www.emuxtras.net) + */ + +#include "Global.h" +#include "Video.h" +#include "IniFile.h" +#include "IoSupport.h" +#include "Input.h" +#include "Debug.h" +#include "Font.h" +#include "MenuManager.h" +#include "RomList.h" + +bool g_bExit = false; + +void __cdecl main() +{ + g_debug.Print("Starting Launcher CE\n"); + + // Set file cache size + XSetFileCacheSize(8 * 1024 * 1024); + + // Mount drives + g_IOSupport.Mount("A:", "cdrom0"); + g_IOSupport.Mount("E:", "Harddisk0\\Partition1"); + g_IOSupport.Mount("Z:", "Harddisk0\\Partition2"); + g_IOSupport.Mount("F:", "Harddisk0\\Partition6"); + g_IOSupport.Mount("G:", "Harddisk0\\Partition7"); + + + // Initialize Direct3D + if (!g_video.Create(NULL, false)) + return; + + // Parse ini file for settings + g_iniFile.CheckForIniEntry(); + + // Load the rom list if it isn't already loaded + if (!g_romList.IsLoaded()) { + g_romList.Load(); + } + + // Init input here + g_input.Create(); + + // Load the font here + g_font.Create(); + + // Build menu here (Menu state -> Main Menu) + g_menuManager.Create(); + + // Loop the app + while (!g_bExit) + { + g_video.BeginRender(); + g_input.GetInput(); + g_menuManager.Update(); + g_video.EndRender(); + } +} diff --git a/xbox1/RetroLaunch/MenuMain.cpp b/xbox1/RetroLaunch/MenuMain.cpp new file mode 100644 index 0000000000..5ee4114d5e --- /dev/null +++ b/xbox1/RetroLaunch/MenuMain.cpp @@ -0,0 +1,241 @@ +/** + * RetroLaunch 2012 + * + * 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 2 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, write to the Free Software Foundation, Inc., + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. To contact the + * authors: Surreal64 CE Team (http://www.emuxtras.net) + */ + +#include "MenuMain.h" +#include "Debug.h" +#include "Font.h" +#include "RomList.h" +#include "Input.h" + + +CMenuMain g_menuMain; + +CMenuMain::CMenuMain() +{ + // we think that the rom list is unloaded until we know otherwise + m_bRomListLoadedState = false; + + ifstream stateFile; + stateFile.open("T:\\RomlistState.dat"); + + if (stateFile.is_open()) + { + int baseIndex; + int romListMode; + + stateFile >> baseIndex; + stateFile >> romListMode; + stateFile >> m_displayMode; + + g_romList.SetRomListMode(romListMode); + g_romList.m_iBaseIndex = baseIndex; + + stateFile.close(); + } + else + { + m_displayMode = List; + } +} + +CMenuMain::~CMenuMain() +{ + ofstream stateFile; + stateFile.open("T:\\RomlistState.dat"); + + stateFile << g_romList.GetBaseIndex() << endl; + stateFile << g_romList.GetRomListMode() << endl; + stateFile << m_displayMode << endl; + + stateFile.close(); +} + +bool CMenuMain::Create() +{ + g_debug.Print("CMenuMain::Create()"); + + // Title coords with color + m_menuMainTitle_x = 305; + m_menuMainTitle_y = 30; + m_menuMainTitle_c = 0xFFFFFFFF; + + // Load background image + m_menuMainBG.Create("Media\\menuMainBG.png"); + m_menuMainBG_x = 0; + m_menuMainBG_y = 0; + m_menuMainBG_w = 640; + m_menuMainBG_h = 480; + + // Init rom list coords + m_menuMainRomListPos_x = 100; + m_menuMainRomListPos_y = 100; + m_menuMainRomListSpacing = 20; + + // Load rom selector panel + m_menuMainRomSelectPanel.Create("Media\\menuMainRomSelectPanel.png"); + m_menuMainRomSelectPanel_x = m_menuMainRomListPos_x - 5; + m_menuMainRomSelectPanel_y = m_menuMainRomListPos_y - 2; + m_menuMainRomSelectPanel_w = 440; + m_menuMainRomSelectPanel_h = 20; + + m_romListSelectedRom = 0; + + //The first element in the romlist to render + m_romListBeginRender = 0; + + //The last element in the romlist to render + m_romListEndRender = 18; + + //The offset in the romlist + m_romListOffset = 0; + + if(m_romListEndRender > g_romList.GetRomListSize() - 1) + { + m_romListEndRender = g_romList.GetRomListSize() - 1; + } + + //Generate the rom list textures only once + vector::iterator i; + dword y = 0; + for (i = g_romList.m_romList.begin(); i != g_romList.m_romList.end(); i++) + { + Rom *rom = *i; + g_font.RenderToTexture(rom->GetTexture(), rom->GetFileName(), 18, XFONT_BOLD, 0xff808080, -1, false); + } + + return true; +} + + +void CMenuMain::Render() +{ + //CheckRomListState(); + + //Render background image + m_menuMainBG.Render(m_menuMainBG_x, m_menuMainBG_y); + + //Display some text + //g_font.Render("Retro Arch", m_menuMainTitle_x, m_menuMainTitle_y, 20, XFONT_NORMAL, m_menuMainTitle_c); + g_font.Render("Press RIGHT ANALOG STICK to exit. Press START to launch a rom.", 65, 450, 16, XFONT_NORMAL, m_menuMainTitle_c); + + //Begin with the rom selector panel + //FIXME: Width/Height needs to be current Rom texture width/height (or should we just leave it at a fixed size?) + m_menuMainRomSelectPanel.Render(m_menuMainRomSelectPanel_x, m_menuMainRomSelectPanel_y, m_menuMainRomSelectPanel_w, m_menuMainRomSelectPanel_h); + + dword dwSpacing = 0; + + for (int i = m_romListBeginRender; i <= m_romListEndRender; i++) + { + g_romList.GetRomAt(i + m_romListOffset)->GetTexture().Render(m_menuMainRomListPos_x, m_menuMainRomListPos_y + dwSpacing); + dwSpacing += m_menuMainRomListSpacing; + } +} + + +void CMenuMain::ProcessInput() +{ + //FIXME: The calculations might be wrong :-/ + if(g_input.IsButtonPressed(XboxDPadDown) || g_input.IsButtonPressed(XboxLeftThumbDown) || g_input.IsRTriggerPressed()) + { + + if(m_romListSelectedRom < g_romList.GetRomListSize()) + { + + if(m_menuMainRomSelectPanel_y < (m_menuMainRomListPos_y + (m_menuMainRomListSpacing * m_romListEndRender))) + { + m_menuMainRomSelectPanel_y += m_menuMainRomListSpacing; + m_romListSelectedRom++; + g_debug.Print("SELECTED ROM: "); g_debug.Print("%d", m_romListSelectedRom); + } + + if(m_menuMainRomSelectPanel_y > (m_menuMainRomListPos_y + (m_menuMainRomListSpacing * (m_romListEndRender)))) + { + m_menuMainRomSelectPanel_y -= m_menuMainRomListSpacing; + m_romListSelectedRom++; + if(m_romListSelectedRom > g_romList.GetRomListSize() - 1) + { + m_romListSelectedRom = g_romList.GetRomListSize() - 1; + } + + + g_debug.Print("SELECTED ROM AFTER CORRECTION: "); g_debug.Print("%d", m_romListSelectedRom); + + if(m_romListSelectedRom < g_romList.GetRomListSize() - 1 && m_romListOffset < g_romList.GetRomListSize() - 1 - m_romListEndRender - 1) { + m_romListOffset++; + g_debug.Print("OFFSET: "); g_debug.Print("%d", m_romListOffset); + } + } + +///////////////////////////////////////////// + + } + } + + // Go up and stop if less than 0 (item 0) + if(g_input.IsButtonPressed(XboxDPadUp) || g_input.IsButtonPressed(XboxLeftThumbUp) || g_input.IsLTriggerPressed()) + { + if(m_romListSelectedRom > -1) + { + if(m_menuMainRomSelectPanel_y > (m_menuMainRomListPos_y - m_menuMainRomListSpacing)) + { + m_menuMainRomSelectPanel_y -= m_menuMainRomListSpacing; + m_romListSelectedRom--; + g_debug.Print("SELECTED ROM: "); g_debug.Print("%d", m_romListSelectedRom); + } + + if(m_menuMainRomSelectPanel_y < (m_menuMainRomListPos_y - m_menuMainRomListSpacing)) + { + m_menuMainRomSelectPanel_y += m_menuMainRomListSpacing; + m_romListSelectedRom--; + if(m_romListSelectedRom < 0) + { + m_romListSelectedRom = 0; + } + + + g_debug.Print("SELECTED ROM AFTER CORRECTION: "); g_debug.Print("%d", m_romListSelectedRom); + + if(m_romListSelectedRom > 0 && m_romListOffset > 0) { + m_romListOffset--; + g_debug.Print("OFFSET: "); g_debug.Print("%d", m_romListOffset); + } + } + } + + + + } + + // Press A to launch, selected rom filename is saved into T:\\tmp.retro + if(g_input.IsButtonPressed(XboxA)) + { + //OutputDebugString(g_romList.GetRomAt(m_romListSelectedRom)->GetFileName().c_str()); + //OutputDebugString("\n"); + g_iniFile.SaveTempRomFileName(g_romList.GetRomAt(m_romListSelectedRom)->GetFileName()); + XLaunchNewImage("D:\\core.xbe", NULL); + } + + if (g_input.IsButtonPressed(XboxStart)) + { + XLaunchNewImage("D:\\core.xbe", NULL); + } + + if (g_input.IsButtonPressed(XboxRightThumbButton)) + { + LD_LAUNCH_DASHBOARD LaunchData = { XLD_LAUNCH_DASHBOARD_MAIN_MENU }; + XLaunchNewImage( NULL, (LAUNCH_DATA*)&LaunchData ); + } +} + diff --git a/xbox1/RetroLaunch/MenuMain.h b/xbox1/RetroLaunch/MenuMain.h new file mode 100644 index 0000000000..c8d9135fbd --- /dev/null +++ b/xbox1/RetroLaunch/MenuMain.h @@ -0,0 +1,99 @@ +/** + * RetroLaunch 2012 + * + * 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 2 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, write to the Free Software Foundation, Inc., + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. To contact the + * authors: Surreal64 CE Team (http://www.emuxtras.net) + */ + +#pragma once +#include "Global.h" +#include "Surface.h" + +enum DisplayMode +{ + Box, + List +}; + +class CMenuMain +{ +public: +CMenuMain(); +~CMenuMain(); + +bool Create(); + +void Render(); + +void ProcessInput(); + +private: +/* +Texture, +_x = xpos, +_y = ypos, +_w = width, +_h = height, +_c = color, +*/ + +// Background image with coords +CSurface m_menuMainBG; +int m_menuMainBG_x; +int m_menuMainBG_y; +dword m_menuMainBG_w; +dword m_menuMainBG_h; + +// Rom selector panel with coords +CSurface m_menuMainRomSelectPanel; +int m_menuMainRomSelectPanel_x; +int m_menuMainRomSelectPanel_y; +dword m_menuMainRomSelectPanel_w; +dword m_menuMainRomSelectPanel_h; + +// Title coords with color +int m_menuMainTitle_x; +int m_menuMainTitle_y; +dword m_menuMainTitle_c; + +// Rom list coords +int m_menuMainRomListPos_x; +int m_menuMainRomListPos_y; +int m_menuMainRomListSpacing; + + + + +/** +* The Rom List menu buttons. The size can be variable so we use a list +*/ +//list m_romListButtons;//list +//no menu buttons, we will use plain textures +list m_romListButtons; + +/** +* The current mode the rom list is in +*/ +int m_displayMode; + +/** +* The current loaded state the rom list is in +*/ +bool m_bRomListLoadedState; + +int m_romListBeginRender; +int m_romListEndRender; +int m_romListSelectedRom; +int m_romListOffset; + +}; + +extern CMenuMain g_menuMain; diff --git a/xbox1/RetroLaunch/MenuManager.cpp b/xbox1/RetroLaunch/MenuManager.cpp new file mode 100644 index 0000000000..ef0853003f --- /dev/null +++ b/xbox1/RetroLaunch/MenuManager.cpp @@ -0,0 +1,98 @@ +/** + * RetroLaunch 2012 + * + * 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 2 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, write to the Free Software Foundation, Inc., + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. To contact the + * authors: Surreal64 CE Team (http://www.emuxtras.net) + */ + +#include "MenuManager.h" +#include "MenuMain.h" +#include "Debug.h" +#include "Input.h" + + +CMenuManager g_menuManager; + +CMenuManager::CMenuManager() +{ +} + +CMenuManager::~CMenuManager() +{ +} + +bool CMenuManager::Create() +{ + //Create the MenuManager, set to Main Menu + g_debug.Print("Create MenuManager, set state to MENU_MAIN"); + SetMenuState(MENU_MAIN); + + return true; +} + +bool CMenuManager::SetMenuState(int nMenuID) +{ + m_pMenuID = nMenuID; + + switch (m_pMenuID) { + case MENU_MAIN: + //Create the Main Menu + g_menuMain.Create(); + break; + } + return true; +} + +void CMenuManager::Update() +{ + //Process overall input + ProcessInput(); + + switch (m_pMenuID) { + case MENU_MAIN: + + // Process menu specific input + g_menuMain.ProcessInput(); + + // Render the Main Menu + g_menuMain.Render(); + break; + } + +} + + +void CMenuManager::ProcessInput() +{ + //ADD: ProcessTarget -> switch to another menu + + //Return to Dashboard + if(g_input.IsLTriggerPressed() && + g_input.IsRTriggerPressed() && + g_input.IsButtonPressed(XboxBlack) && + g_input.IsButtonPressed(XboxWhite) && + g_input.IsButtonPressed(XboxBack) && + g_input.IsButtonPressed(XboxStart)) + { + + LD_LAUNCH_DASHBOARD LaunchData = { XLD_LAUNCH_DASHBOARD_MAIN_MENU }; + XLaunchNewImage( NULL, (LAUNCH_DATA*)&LaunchData ); + + } +} + + + +int CMenuManager::GetMenuState() +{ + return m_pMenuID; +} + diff --git a/xbox1/RetroLaunch/MenuManager.h b/xbox1/RetroLaunch/MenuManager.h new file mode 100644 index 0000000000..0e888299aa --- /dev/null +++ b/xbox1/RetroLaunch/MenuManager.h @@ -0,0 +1,50 @@ +/** + * RetroLaunch 2012 + * + * 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 2 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, write to the Free Software Foundation, Inc., + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. To contact the + * authors: Surreal64 CE Team (http://www.emuxtras.net) + */ + +#pragma once + +#include "Global.h" + +enum eMenuState +{ + MENU_MAIN = 0, + MENU_SETTINGS_SELECT, + MENU_SETTINGS_EMU, + MENU_SETTINGS_AUDIO, + MENU_SETTINGS_SKIN, + MENU_LAUNCHER +}; + + +class CMenuManager +{ +public: +CMenuManager(); +~CMenuManager(); + +bool Create(); +bool SetMenuState(int nMenuID); +int GetMenuState(); +bool Destroy(); +void Update(); +void ProcessInput(); + + +private: +int m_pMenuID; + +}; + +extern CMenuManager g_menuManager; \ No newline at end of file diff --git a/xbox1/RetroLaunch/RetroLaunch.sln b/xbox1/RetroLaunch/RetroLaunch.sln new file mode 100644 index 0000000000..d2b910dcca --- /dev/null +++ b/xbox1/RetroLaunch/RetroLaunch.sln @@ -0,0 +1,33 @@ +Microsoft Visual Studio Solution File, Format Version 8.00 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "RetroLaunch", "RetroLaunch.vcproj", "{14134C4E-7FD3-46F0-AD16-C32D952978EA}" + ProjectSection(ProjectDependencies) = postProject + EndProjectSection +EndProject +Global + GlobalSection(SolutionConfiguration) = preSolution + Debug = Debug + Profile = Profile + Profile_FastCap = Profile_FastCap + Release = Release + Release_LTCG = Release_LTCG + ReleaseTest = ReleaseTest + EndGlobalSection + GlobalSection(ProjectConfiguration) = postSolution + {14134C4E-7FD3-46F0-AD16-C32D952978EA}.Debug.ActiveCfg = Debug|Xbox + {14134C4E-7FD3-46F0-AD16-C32D952978EA}.Debug.Build.0 = Debug|Xbox + {14134C4E-7FD3-46F0-AD16-C32D952978EA}.Profile.ActiveCfg = Profile|Xbox + {14134C4E-7FD3-46F0-AD16-C32D952978EA}.Profile.Build.0 = Profile|Xbox + {14134C4E-7FD3-46F0-AD16-C32D952978EA}.Profile_FastCap.ActiveCfg = Profile_FastCap|Xbox + {14134C4E-7FD3-46F0-AD16-C32D952978EA}.Profile_FastCap.Build.0 = Profile_FastCap|Xbox + {14134C4E-7FD3-46F0-AD16-C32D952978EA}.Release.ActiveCfg = Release|Xbox + {14134C4E-7FD3-46F0-AD16-C32D952978EA}.Release.Build.0 = Release|Xbox + {14134C4E-7FD3-46F0-AD16-C32D952978EA}.Release_LTCG.ActiveCfg = Release_LTCG|Xbox + {14134C4E-7FD3-46F0-AD16-C32D952978EA}.Release_LTCG.Build.0 = Release_LTCG|Xbox + {14134C4E-7FD3-46F0-AD16-C32D952978EA}.ReleaseTest.ActiveCfg = ReleaseTest|Xbox + {14134C4E-7FD3-46F0-AD16-C32D952978EA}.ReleaseTest.Build.0 = ReleaseTest|Xbox + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + EndGlobalSection + GlobalSection(ExtensibilityAddIns) = postSolution + EndGlobalSection +EndGlobal diff --git a/xbox1/RetroLaunch/RetroLaunch.vcproj b/xbox1/RetroLaunch/RetroLaunch.vcproj new file mode 100644 index 0000000000..f2f561e265 --- /dev/null +++ b/xbox1/RetroLaunch/RetroLaunch.vcproj @@ -0,0 +1,447 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/xbox1/RetroLaunch/Rom.cpp b/xbox1/RetroLaunch/Rom.cpp new file mode 100644 index 0000000000..c05c93818c --- /dev/null +++ b/xbox1/RetroLaunch/Rom.cpp @@ -0,0 +1,76 @@ +/** + * Surreal 64 Launcher (C) 2003 + * + * 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 2 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, write to the Free Software Foundation, Inc., + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. To contact the + * authors: email: buttza@hotmail.com, lantus@lantus-x.com + */ + +#include "Rom.h" +//#include "BoxArtTable.h" + +Rom::Rom() +{ + m_bLoaded = false; +} + +Rom::~Rom(void) +{ +} + +bool Rom::Load(const string &szFilename) +{ + if (m_bLoaded) + return true; + + m_szFilename = szFilename; + + // get the filename for the box art image + //FIXME: Add BoxArtTable.cpp/h, open iso file, grab header, extract ID ie. T-6003G, use for boxartfilename + { + m_szBoxArtFilename = "D:\\boxart\\default.jpg"; //g_boxArtTable.GetBoxArtFilename(m_dwCrc1); + } + + m_bLoaded = true; + + return true; +} + +bool Rom::LoadFromCache(const string &szFilename, const string &szBoxArtFilename) +{ + m_szFilename = szFilename; + m_szBoxArtFilename = szBoxArtFilename; + + m_bLoaded = true; + + return true; +} + +string Rom::GetFileName() +{ + return m_szFilename; +} + +string Rom::GetBoxArtFilename() +{ + return m_szBoxArtFilename; +} + +string Rom::GetComments() +{ + //return string(m_iniEntry->szComments); + return "blah"; +} + + +CSurface &Rom::GetTexture() +{ + return m_texture; +} \ No newline at end of file diff --git a/xbox1/RetroLaunch/Rom.h b/xbox1/RetroLaunch/Rom.h new file mode 100644 index 0000000000..11dce13713 --- /dev/null +++ b/xbox1/RetroLaunch/Rom.h @@ -0,0 +1,44 @@ +/** + * Surreal 64 Launcher (C) 2003 + * + * 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 2 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, write to the Free Software Foundation, Inc., + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. To contact the + * authors: email: buttza@hotmail.com, lantus@lantus-x.com + */ + +#pragma once + +#include "Global.h" +#include "IniFile.h" +#include "Surface.h" + +class Rom +{ +public: + Rom(); + ~Rom(); + + bool Load(const string &szFilename); + bool LoadFromCache(const string &szFilename, const string &szBoxArtFilename); + + string GetFileName(); + string GetBoxArtFilename(); + string GetComments(); + + CSurface &GetTexture(); + +private: + string m_szFilename; + string m_szBoxArtFilename; + + bool m_bLoaded; + + CSurface m_texture; +}; diff --git a/xbox1/RetroLaunch/RomList.cpp b/xbox1/RetroLaunch/RomList.cpp new file mode 100644 index 0000000000..b22381959a --- /dev/null +++ b/xbox1/RetroLaunch/RomList.cpp @@ -0,0 +1,309 @@ +/** + * RetroLaunch 2012 + * + * 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 2 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, write to the Free Software Foundation, Inc., + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. To contact the + * authors: Surreal64 CE Team (http://www.emuxtras.net) + */ + +#include "RomList.h" + +RomList g_romList; + +bool RLessThan(Rom *elem1, Rom *elem2) +{ + return (elem1->GetFileName() < elem2->GetFileName()); +} + +RomList::RomList(void) +{ + m_romListMode = All; + m_iBaseIndex = 0; + m_bLoaded = false; + m_szRomPath = "D:\\Roms\\"; +} + +RomList::~RomList(void) +{ + Destroy(); +} + +void RomList::Load() +{ + ifstream cacheFile; + + cacheFile.open("T:\\RomlistCache.dat"); + + // try and open the cache file, if it doesnt exist, generate the rom list + if (!cacheFile.is_open()) + { + Build(); + } + else + { + while (!cacheFile.eof()) + { + string szFilename; + string szBoxArtFilename; + + getline(cacheFile, szFilename); + getline(cacheFile, szBoxArtFilename); + + Rom *rom = new Rom(); + + bool bSuccess = rom->LoadFromCache(szFilename, szBoxArtFilename); + + if (bSuccess) + m_romList.push_back(rom); + else + delete rom; + } + + cacheFile.close(); + } + + m_bLoaded = true; +} + +void RomList::Save() +{ + vector::iterator i; + ofstream cacheFile; + + // open/overwrite the rom cache + cacheFile.open("T:\\RomlistCache.dat"); + + for (i = m_romList.begin(); i != m_romList.end(); i++) + { + Rom *rom = *i; + + cacheFile << rom->GetFileName() << endl; + cacheFile << rom->GetBoxArtFilename() << endl; + } + + cacheFile.close(); +} + +void RomList::Refresh() +{ + Destroy(); + DeleteFile("T:\\RomlistCache.dat"); + DeleteFile("T:\\RomlistState.dat"); +} + +bool RomList::IsLoaded() +{ + return m_bLoaded; +} + +void RomList::SetRomListMode(int mode) +{ + m_iBaseIndex = 0; + m_romListMode = mode; +} + +int RomList::GetRomListMode() +{ + return m_romListMode; +} + +void RomList::AddRomToList(Rom *rom, int mode) +{ + vector *pList; + + switch (mode) + { + case All: + pList = &m_romList; + break; + default: + return; + } + + // look to see if the rom is already in the list, we dont want duplicates + for (int i = 0; i < static_cast(pList->size()); i++) + { + if (rom == (*pList)[i]) + return; + } + + pList->push_back(rom); + sort(pList->begin(), pList->end(), RLessThan); +} + +void RomList::RemoveRomFromList(Rom *rom, int mode) +{ + vector *pList; + + switch (mode) + { + case All: + pList = &m_romList; + break; + default: + return; + } + + vector::iterator i; + + // look to see if the rom is already in the list, we dont want duplicates + for (i = pList->begin(); i != pList->end(); i++) + { + if (rom == *i) + { + pList->erase(i); + return; + } + } +} + +int RomList::GetBaseIndex() +{ + if (m_iBaseIndex > GetRomListSize() - 1) + m_iBaseIndex = GetRomListSize() - 1; + if (m_iBaseIndex < 0) + m_iBaseIndex = 0; + + return m_iBaseIndex; +} + +void RomList::SetBaseIndex(int index) +{ + if (index > GetRomListSize() - 1) + index = GetRomListSize() - 1; + if (index < 0) + index = 0; + + m_iBaseIndex = index; +} + +int RomList::GetRomListSize() +{ + switch (m_romListMode) + { + case All: + return m_romList.size(); + } + + return 0; +} + +Rom *RomList::GetRomAt(int index) +{ + switch (m_romListMode) + { + case All: + return m_romList[index]; + } + + return 0; +} + +int RomList::FindRom(Rom *rom, int mode) +{ + vector *pList; + + switch (mode) + { + case All: + pList = &m_romList; + break; + default: + return -1; + } + + for (int i = 0; i < static_cast(pList->size()); i++) + { + if (rom == (*pList)[i]) + return i; + } + + return -1; +} + +void RomList::CleanUpTextures() +{ + if (!IsLoaded()) + return; + + // keep the 25 textures above and below the base index + for (int i = 0; i < m_iBaseIndex - 25; i++) + { + m_romList[i]->GetTexture().Destroy(); + } + + for (int i = m_iBaseIndex + 25; i < GetRomListSize(); i++) + { + m_romList[i]->GetTexture().Destroy(); + } +} + +void RomList::DestroyAllTextures() +{ + vector::iterator i; + + for (i = m_romList.begin(); i != m_romList.end(); i++) + { + Rom *rom = *i; + rom->GetTexture().Destroy(); + } +} + +void RomList::Build() +{ + WIN32_FIND_DATA fd; + + HANDLE hFF = FindFirstFile((m_szRomPath + "*.*").c_str(), &fd); + + do + { + char ext[_MAX_EXT]; + + // get the filename extension + _splitpath((m_szRomPath + fd.cFileName).c_str(), + NULL, NULL, NULL, ext); + + if ( + stricmp(ext, ".bin") == 0 + || stricmp(ext, ".cue") == 0 + || stricmp(ext, ".iso") == 0 + || stricmp(ext, ".mdf") == 0 + || stricmp(ext, ".gba") == 0 + ) + { + Rom *rom = new Rom(); + bool bSuccess = rom->Load((m_szRomPath + fd.cFileName).c_str()); + + if (bSuccess) + m_romList.push_back(rom); + else + delete rom; + } + } while (FindNextFile(hFF, &fd)); + + sort(m_romList.begin(), m_romList.end(), RLessThan); + + m_bLoaded = true; +} + +void RomList::Destroy() +{ + m_bLoaded = false; + m_iBaseIndex = 0; + + vector::iterator i; + + for (i = m_romList.begin(); i != m_romList.end(); i++) + { + delete *i; + } + + m_romList.clear(); +} diff --git a/xbox1/RetroLaunch/RomList.h b/xbox1/RetroLaunch/RomList.h new file mode 100644 index 0000000000..3fa19615d4 --- /dev/null +++ b/xbox1/RetroLaunch/RomList.h @@ -0,0 +1,72 @@ +/** + * RetroLaunch 2012 + * + * 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 2 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, write to the Free Software Foundation, Inc., + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. To contact the + * authors: Surreal64 CE Team (http://www.emuxtras.net) + */ + +#pragma once + +#include "Global.h" +#include "Rom.h" + +enum RomListMode +{ + All +}; + +class RomList +{ +public: + RomList(void); + virtual ~RomList(void); + + void Load(); + void Save(); + void Refresh(); + + bool IsLoaded(); + + void SetRomListMode(int mode); + int GetRomListMode(); + + void AddRomToList(Rom *rom, int mode); + void RemoveRomFromList(Rom *rom, int mode); + + int GetBaseIndex(); + void SetBaseIndex(int index); + + int GetRomListSize(); + + Rom *GetRomAt(int index); + int FindRom(Rom *rom, int mode); + + void CleanUpTextures(); + void DestroyAllTextures(); + + int m_iBaseIndex; + + vector m_romList; + +private: + void Build(); + void Destroy(); + +private: + + bool m_bLoaded; + + int m_romListMode; + + string m_szRomPath; +}; + +extern RomList g_romList; \ No newline at end of file diff --git a/xbox1/RetroLaunch/SimpleIni.h b/xbox1/RetroLaunch/SimpleIni.h new file mode 100644 index 0000000000..882289f6fc --- /dev/null +++ b/xbox1/RetroLaunch/SimpleIni.h @@ -0,0 +1,3262 @@ +/** @mainpage + + +
Library SimpleIni +
File SimpleIni.h +
Author Brodie Thiesfield [code at jellycan dot com] +
Source http://code.jellycan.com/simpleini/ +
Version 4.13 +
+ + Jump to the @link CSimpleIniTempl CSimpleIni @endlink interface documentation. + + @section intro INTRODUCTION + + This component allows an INI-style configuration file to be used on both + Windows and Linux/Unix. It is fast, simple and source code using this + component will compile unchanged on either OS. + + + @section features FEATURES + + - MIT Licence allows free use in all software (including GPL and commercial) + - multi-platform (Windows 95/98/ME/NT/2K/XP/2003, Windows CE, Linux, Unix) + - loading and saving of INI-style configuration files + - configuration files can have any newline format on all platforms + - liberal acceptance of file format + - key/values with no section + - removal of whitespace around sections, keys and values + - support for multi-line values (values with embedded newline characters) + - optional support for multiple keys with the same name + - optional case-insensitive sections and keys (for ASCII characters only) + - saves files with sections and keys in the same order as they were loaded + - preserves comments on the file, section and keys where possible. + - supports both char or wchar_t programming interfaces + - supports both MBCS (system locale) and UTF-8 file encodings + - system locale does not need to be UTF-8 on Linux/Unix to load UTF-8 file + - support for non-ASCII characters in section, keys, values and comments + - support for non-standard character types or file encodings + via user-written converter classes + - support for adding/modifying values programmatically + - compiles cleanly in the following compilers: + - Windows/VC6 (warning level 3) + - Windows/VC.NET 2003 (warning level 4) + - Windows/VC 2005 (warning level 4) + - Linux/gcc (-Wall) + + + @section usage USAGE SUMMARY + + -# Define the appropriate symbol for the converter you wish to use and + include the SimpleIni.h header file. If no specific converter is defined + then the default converter is used. The default conversion mode uses + SI_CONVERT_WIN32 on Windows and SI_CONVERT_GENERIC on all other + platforms. If you are using ICU then SI_CONVERT_ICU is supported on all + platforms. + -# Declare an instance the appropriate class. Note that the following + definitions are just shortcuts for commonly used types. Other types + (PRUnichar, unsigned short, unsigned char) are also possible. + +
Interface Case-sensitive Load UTF-8 Load MBCS Typedef +
SI_CONVERT_GENERIC +
char No Yes Yes #1 CSimpleIniA +
char Yes Yes Yes CSimpleIniCaseA +
wchar_t No Yes Yes CSimpleIniW +
wchar_t Yes Yes Yes CSimpleIniCaseW +
SI_CONVERT_WIN32 +
char No No #2 Yes CSimpleIniA +
char Yes Yes Yes CSimpleIniCaseA +
wchar_t No Yes Yes CSimpleIniW +
wchar_t Yes Yes Yes CSimpleIniCaseW +
SI_CONVERT_ICU +
char No Yes Yes CSimpleIniA +
char Yes Yes Yes CSimpleIniCaseA +
UChar No Yes Yes CSimpleIniW +
UChar Yes Yes Yes CSimpleIniCaseW +
+ #1 On Windows you are better to use CSimpleIniA with SI_CONVERT_WIN32.
+ #2 Only affects Windows. On Windows this uses MBCS functions and + so may fold case incorrectly leading to uncertain results. + -# Call Load() or LoadFile() to load and parse the INI configuration file + -# Access and modify the data of the file using the following functions + +
GetAllSections Return all section names +
GetAllKeys Return all key names within a section +
GetAllValues Return all values within a section & key +
GetSection Return all key names and values in a section +
GetSectionSize Return the number of keys in a section +
GetValue Return a value for a section & key +
SetValue Add or update a value for a section & key +
Delete Remove a section, or a key from a section +
+ -# Call Save() or SaveFile() to save the INI configuration data + + @section iostreams IO STREAMS + + SimpleIni supports reading from and writing to STL IO streams. Enable this + by defining SI_SUPPORT_IOSTREAMS before including the SimpleIni.h header + file. Ensure that if the streams are backed by a file (e.g. ifstream or + ofstream) then the flag ios_base::binary has been used when the file was + opened. + + @section multiline MULTI-LINE VALUES + + Values that span multiple lines are created using the following format. + +
+        key = <<
+
+    Note the following:
+    - The text used for ENDTAG can be anything and is used to find
+      where the multi-line text ends.
+    - The newline after ENDTAG in the start tag, and the newline
+      before ENDTAG in the end tag is not included in the data value.
+    - The ending tag must be on it's own line with no whitespace before
+      or after it.
+    - The multi-line value is modified at load so that each line in the value
+      is delimited by a single '\\n' character on all platforms. At save time
+      it will be converted into the newline format used by the current
+      platform.
+
+    @section comments COMMENTS
+
+    Comments are preserved in the file within the following restrictions:
+    - Every file may have a single "file comment". It must start with the
+      first character in the file, and will end with the first non-comment
+      line in the file.
+    - Every section may have a single "section comment". It will start
+      with the first comment line following the file comment, or the last
+      data entry. It ends at the beginning of the section.
+    - Every key may have a single "key comment". This comment will start
+      with the first comment line following the section start, or the file
+      comment if there is no section name.
+    - Comments are set at the time that the file, section or key is first
+      created. The only way to modify a comment on a section or a key is to
+      delete that entry and recreate it with the new comment. There is no
+      way to change the file comment.
+
+    @section save SAVE ORDER
+
+    The sections and keys are written out in the same order as they were
+    read in from the file. Sections and keys added to the data after the
+    file has been loaded will be added to the end of the file when it is
+    written. There is no way to specify the location of a section or key
+    other than in first-created, first-saved order.
+
+    @section notes NOTES
+
+    - To load UTF-8 data on Windows 95, you need to use Microsoft Layer for
+      Unicode, or SI_CONVERT_GENERIC, or SI_CONVERT_ICU.
+    - When using SI_CONVERT_GENERIC, ConvertUTF.c must be compiled and linked.
+    - When using SI_CONVERT_ICU, ICU header files must be on the include
+      path and icuuc.lib must be linked in.
+    - To load a UTF-8 file on Windows AND expose it with SI_CHAR == char,
+      you should use SI_CONVERT_GENERIC.
+    - The collation (sorting) order used for sections and keys returned from
+      iterators is NOT DEFINED. If collation order of the text is important
+      then it should be done yourself by either supplying a replacement
+      SI_STRLESS class, or by sorting the strings external to this library.
+    - Usage of the  header on Windows can be disabled by defining
+      SI_NO_MBCS. This is defined automatically on Windows CE platforms.
+
+
+    @section licence MIT LICENCE
+
+    The licence text below is the boilerplate "MIT Licence" used from:
+    http://www.opensource.org/licenses/mit-license.php
+
+    Copyright (c) 2006-2008, Brodie Thiesfield
+
+    Permission is hereby granted, free of charge, to any person obtaining a copy
+    of this software and associated documentation files (the "Software"), to deal
+    in the Software without restriction, including without limitation the rights
+    to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+    copies of the Software, and to permit persons to whom the Software is furnished
+    to do so, subject to the following conditions:
+
+    The above copyright notice and this permission notice shall be included in
+    all copies or substantial portions of the Software.
+
+    THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+    IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
+    FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
+    COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
+    IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+    CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+*/
+
+#ifndef INCLUDED_SimpleIni_h
+#define INCLUDED_SimpleIni_h
+
+#if defined(_MSC_VER) && (_MSC_VER >= 1020)
+# pragma once
+#endif
+
+// Disable these warnings in MSVC:
+//  4127 "conditional expression is constant" as the conversion classes trigger
+//  it with the statement if (sizeof(SI_CHAR) == sizeof(char)). This test will
+//  be optimized away in a release build.
+//  4503 'insert' : decorated name length exceeded, name was truncated
+//  4702 "unreachable code" as the MS STL header causes it in release mode.
+//  Again, the code causing the warning will be cleaned up by the compiler.
+//  4786 "identifier truncated to 256 characters" as this is thrown hundreds
+//  of times VC6 as soon as STL is used.
+#ifdef _MSC_VER
+# pragma warning (push)
+# pragma warning (disable: 4127 4503 4702 4786)
+#endif
+
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+
+#ifdef SI_SUPPORT_IOSTREAMS
+# include 
+#endif // SI_SUPPORT_IOSTREAMS
+
+#ifdef _DEBUG
+# ifndef assert
+#  include 
+# endif
+# define SI_ASSERT(x)   assert(x)
+#else
+# define SI_ASSERT(x)
+#endif
+
+enum SI_Error {
+    SI_OK       =  0,   //!< No error
+    SI_UPDATED  =  1,   //!< An existing value was updated
+    SI_INSERTED =  2,   //!< A new value was inserted
+
+    // note: test for any error with (retval < 0)
+    SI_FAIL     = -1,   //!< Generic failure
+    SI_NOMEM    = -2,   //!< Out of memory error
+    SI_FILE     = -3    //!< File error (see errno for detail error)
+};
+
+#define SI_UTF8_SIGNATURE     "\xEF\xBB\xBF"
+
+#ifdef _WIN32
+# define SI_NEWLINE_A   "\r\n"
+# define SI_NEWLINE_W   L"\r\n"
+#else // !_WIN32
+# define SI_NEWLINE_A   "\n"
+# define SI_NEWLINE_W   L"\n"
+#endif // _WIN32
+
+#if defined(SI_CONVERT_ICU)
+# include 
+#endif
+
+#if defined(_WIN32)
+# define SI_HAS_WIDE_FILE
+# define SI_WCHAR_T     wchar_t
+#elif defined(SI_CONVERT_ICU)
+# define SI_HAS_WIDE_FILE
+# define SI_WCHAR_T     UChar
+#endif
+
+
+// ---------------------------------------------------------------------------
+//                              MAIN TEMPLATE CLASS
+// ---------------------------------------------------------------------------
+
+/** Simple INI file reader.
+
+    This can be instantiated with the choice of unicode or native characterset,
+    and case sensitive or insensitive comparisons of section and key names.
+    The supported combinations are pre-defined with the following typedefs:
+
+    
+        
Interface Case-sensitive Typedef +
char No CSimpleIniA +
char Yes CSimpleIniCaseA +
wchar_t No CSimpleIniW +
wchar_t Yes CSimpleIniCaseW +
+ + Note that using other types for the SI_CHAR is supported. For instance, + unsigned char, unsigned short, etc. Note that where the alternative type + is a different size to char/wchar_t you may need to supply new helper + classes for SI_STRLESS and SI_CONVERTER. + */ +template +class CSimpleIniTempl +{ +public: + /** key entry */ + struct Entry { + const SI_CHAR * pItem; + const SI_CHAR * pComment; + int nOrder; + + Entry(const SI_CHAR * a_pszItem = NULL, int a_nOrder = 0) + : pItem(a_pszItem) + , pComment(NULL) + , nOrder(a_nOrder) + { } + Entry(const SI_CHAR * a_pszItem, const SI_CHAR * a_pszComment, int a_nOrder) + : pItem(a_pszItem) + , pComment(a_pszComment) + , nOrder(a_nOrder) + { } + Entry(const Entry & rhs) { operator=(rhs); } + Entry & operator=(const Entry & rhs) { + pItem = rhs.pItem; + pComment = rhs.pComment; + nOrder = rhs.nOrder; + return *this; + } + +#if defined(_MSC_VER) && _MSC_VER <= 1200 + /** STL of VC6 doesn't allow me to specify my own comparator for list::sort() */ + bool operator<(const Entry & rhs) const { return LoadOrder()(*this, rhs); } + bool operator>(const Entry & rhs) const { return LoadOrder()(rhs, *this); } +#endif + + /** Strict less ordering by name of key only */ + struct KeyOrder : std::binary_function { + bool operator()(const Entry & lhs, const Entry & rhs) const { + const static SI_STRLESS isLess = SI_STRLESS(); + return isLess(lhs.pItem, rhs.pItem); + } + }; + + /** Strict less ordering by order, and then name of key */ + struct LoadOrder : std::binary_function { + bool operator()(const Entry & lhs, const Entry & rhs) const { + if (lhs.nOrder != rhs.nOrder) { + return lhs.nOrder < rhs.nOrder; + } + return KeyOrder()(lhs.pItem, rhs.pItem); + } + }; + }; + + /** map keys to values */ + typedef std::multimap TKeyVal; + + /** map sections to key/value map */ + typedef std::map TSection; + + /** set of dependent string pointers. Note that these pointers are + dependent on memory owned by CSimpleIni. + */ + typedef std::list TNamesDepend; + + /** interface definition for the OutputWriter object to pass to Save() + in order to output the INI file data. + */ + class OutputWriter { + public: + OutputWriter() { } + virtual ~OutputWriter() { } + virtual void Write(const char * a_pBuf) = 0; + private: + OutputWriter(const OutputWriter &); // disable + OutputWriter & operator=(const OutputWriter &); // disable + }; + + /** OutputWriter class to write the INI data to a file */ + class FileWriter : public OutputWriter { + FILE * m_file; + public: + FileWriter(FILE * a_file) : m_file(a_file) { } + void Write(const char * a_pBuf) { + fputs(a_pBuf, m_file); + } + private: + FileWriter(const FileWriter &); // disable + FileWriter & operator=(const FileWriter &); // disable + }; + + /** OutputWriter class to write the INI data to a string */ + class StringWriter : public OutputWriter { + std::string & m_string; + public: + StringWriter(std::string & a_string) : m_string(a_string) { } + void Write(const char * a_pBuf) { + m_string.append(a_pBuf); + } + private: + StringWriter(const StringWriter &); // disable + StringWriter & operator=(const StringWriter &); // disable + }; + +#ifdef SI_SUPPORT_IOSTREAMS + /** OutputWriter class to write the INI data to an ostream */ + class StreamWriter : public OutputWriter { + std::ostream & m_ostream; + public: + StreamWriter(std::ostream & a_ostream) : m_ostream(a_ostream) { } + void Write(const char * a_pBuf) { + m_ostream << a_pBuf; + } + private: + StreamWriter(const StreamWriter &); // disable + StreamWriter & operator=(const StreamWriter &); // disable + }; +#endif // SI_SUPPORT_IOSTREAMS + + /** Characterset conversion utility class to convert strings to the + same format as is used for the storage. + */ + class Converter : private SI_CONVERTER { + public: + Converter(bool a_bStoreIsUtf8) : SI_CONVERTER(a_bStoreIsUtf8) { + m_scratch.resize(1024); + } + Converter(const Converter & rhs) { operator=(rhs); } + Converter & operator=(const Converter & rhs) { + m_scratch = rhs.m_scratch; + return *this; + } + bool ConvertToStore(const SI_CHAR * a_pszString) { + size_t uLen = SizeToStore(a_pszString); + if (uLen == (size_t)(-1)) { + return false; + } + while (uLen > m_scratch.size()) { + m_scratch.resize(m_scratch.size() * 2); + } + return SI_CONVERTER::ConvertToStore( + a_pszString, + const_cast(m_scratch.data()), + m_scratch.size()); + } + const char * Data() { return m_scratch.data(); } + private: + std::string m_scratch; + }; + +public: + /*-----------------------------------------------------------------------*/ + + /** Default constructor. + + @param a_bIsUtf8 See the method SetUnicode() for details. + @param a_bMultiKey See the method SetMultiKey() for details. + @param a_bMultiLine See the method SetMultiLine() for details. + */ + CSimpleIniTempl( + bool a_bIsUtf8 = false, + bool a_bMultiKey = false, + bool a_bMultiLine = false + ); + + /** Destructor */ + ~CSimpleIniTempl(); + + /** Deallocate all memory stored by this object */ + void Reset(); + + /*-----------------------------------------------------------------------*/ + /** @{ @name Settings */ + + /** Set the storage format of the INI data. This affects both the loading + and saving of the INI data using all of the Load/Save API functions. + This value cannot be changed after any INI data has been loaded. + + If the file is not set to Unicode (UTF-8), then the data encoding is + assumed to be the OS native encoding. This encoding is the system + locale on Linux/Unix and the legacy MBCS encoding on Windows NT/2K/XP. + If the storage format is set to Unicode then the file will be loaded + as UTF-8 encoded data regardless of the native file encoding. If + SI_CHAR == char then all of the char* parameters take and return UTF-8 + encoded data regardless of the system locale. + + \param a_bIsUtf8 Assume UTF-8 encoding for the source? + */ + void SetUnicode(bool a_bIsUtf8 = true) { + if (!m_pData) m_bStoreIsUtf8 = a_bIsUtf8; + } + + /** Get the storage format of the INI data. */ + bool IsUnicode() const { return m_bStoreIsUtf8; } + + /** Should multiple identical keys be permitted in the file. If set to false + then the last value encountered will be used as the value of the key. + If set to true, then all values will be available to be queried. For + example, with the following input: + +
+        [section]
+        test=value1
+        test=value2
+        
+ + Then with SetMultiKey(true), both of the values "value1" and "value2" + will be returned for the key test. If SetMultiKey(false) is used, then + the value for "test" will only be "value2". This value may be changed + at any time. + + \param a_bAllowMultiKey Allow multi-keys in the source? + */ + void SetMultiKey(bool a_bAllowMultiKey = true) { + m_bAllowMultiKey = a_bAllowMultiKey; + } + + /** Get the storage format of the INI data. */ + bool IsMultiKey() const { return m_bAllowMultiKey; } + + /** Should data values be permitted to span multiple lines in the file. If + set to false then the multi-line construct << + SI_CHAR FORMAT + char same format as when loaded (MBCS or UTF-8) + wchar_t UTF-8 + other UTF-8 + + + Note that comments from the original data is preserved as per the + documentation on comments. The order of the sections and values + from the original file will be preserved. + + Any data prepended or appended to the output device must use the the + same format (MBCS or UTF-8). You may use the GetConverter() method to + convert text to the correct format regardless of the output format + being used by SimpleIni. + + To add a BOM to UTF-8 data, write it out manually at the very beginning + like is done in SaveFile when a_bUseBOM is true. + + @param a_oOutput Output writer to write the data to. + + @param a_bAddSignature Prepend the UTF-8 BOM if the output data is in + UTF-8 format. If it is not UTF-8 then this value is + ignored. Do not set this to true if anything has + already been written to the OutputWriter. + + @return SI_Error See error definitions + */ + SI_Error Save( + OutputWriter & a_oOutput, + bool a_bAddSignature = false + ) const; + +#ifdef SI_SUPPORT_IOSTREAMS + /** Save the INI data to an ostream. See Save() for details. + + @param a_ostream String to have the INI data appended to. + + @param a_bAddSignature Prepend the UTF-8 BOM if the output data is in + UTF-8 format. If it is not UTF-8 then this value is + ignored. Do not set this to true if anything has + already been written to the stream. + + @return SI_Error See error definitions + */ + SI_Error Save( + std::ostream & a_ostream, + bool a_bAddSignature = false + ) const + { + StreamWriter writer(a_ostream); + return Save(writer, a_bAddSignature); + } +#endif // SI_SUPPORT_IOSTREAMS + + /** Append the INI data to a string. See Save() for details. + + @param a_sBuffer String to have the INI data appended to. + + @param a_bAddSignature Prepend the UTF-8 BOM if the output data is in + UTF-8 format. If it is not UTF-8 then this value is + ignored. Do not set this to true if anything has + already been written to the string. + + @return SI_Error See error definitions + */ + SI_Error Save( + std::string & a_sBuffer, + bool a_bAddSignature = false + ) const + { + StringWriter writer(a_sBuffer); + return Save(writer, a_bAddSignature); + } + + /*-----------------------------------------------------------------------*/ + /** @} + @{ @name Accessing INI Data */ + + /** Retrieve all section names. The list is returned as an STL vector of + names and can be iterated or searched as necessary. Note that the + sort order of the returned strings is NOT DEFINED. You can sort + the names into the load order if desired. Search this file for ".sort" + for an example. + + NOTE! This structure contains only pointers to strings. The actual + string data is stored in memory owned by CSimpleIni. Ensure that the + CSimpleIni object is not destroyed or Reset() while these pointers + are in use! + + @param a_names Vector that will receive all of the section + names. See note above! + */ + void GetAllSections( + TNamesDepend & a_names + ) const; + + /** Retrieve all unique key names in a section. The sort order of the + returned strings is NOT DEFINED. You can sort the names into the load + order if desired. Search this file for ".sort" for an example. Only + unique key names are returned. + + NOTE! This structure contains only pointers to strings. The actual + string data is stored in memory owned by CSimpleIni. Ensure that the + CSimpleIni object is not destroyed or Reset() while these strings + are in use! + + @param a_pSection Section to request data for + @param a_names List that will receive all of the key + names. See note above! + + @return true Section was found. + @return false Matching section was not found. + */ + bool GetAllKeys( + const SI_CHAR * a_pSection, + TNamesDepend & a_names + ) const; + + /** Retrieve all values for a specific key. This method can be used when + multiple keys are both enabled and disabled. Note that the sort order + of the returned strings is NOT DEFINED. You can sort the names into + the load order if desired. Search this file for ".sort" for an example. + + NOTE! The returned values are pointers to string data stored in memory + owned by CSimpleIni. Ensure that the CSimpleIni object is not destroyed + or Reset while you are using this pointer! + + @param a_pSection Section to search + @param a_pKey Key to search for + @param a_values List to return if the key is not found + + @return true Key was found. + @return false Matching section/key was not found. + */ + bool GetAllValues( + const SI_CHAR * a_pSection, + const SI_CHAR * a_pKey, + TNamesDepend & a_values + ) const; + + /** Query the number of keys in a specific section. Note that if multiple + keys are enabled, then this value may be different to the number of + keys returned by GetAllKeys. + + @param a_pSection Section to request data for + + @return -1 Section does not exist in the file + @return >=0 Number of keys in the section + */ + int GetSectionSize( + const SI_CHAR * a_pSection + ) const; + + /** Retrieve all key and value pairs for a section. The data is returned + as a pointer to an STL map and can be iterated or searched as + desired. Note that multiple entries for the same key may exist when + multiple keys have been enabled. + + NOTE! This structure contains only pointers to strings. The actual + string data is stored in memory owned by CSimpleIni. Ensure that the + CSimpleIni object is not destroyed or Reset() while these strings + are in use! + + @param a_pSection Name of the section to return + @return boolean Was a section matching the supplied + name found. + */ + const TKeyVal * GetSection( + const SI_CHAR * a_pSection + ) const; + + /** Retrieve the value for a specific key. If multiple keys are enabled + (see SetMultiKey) then only the first value associated with that key + will be returned, see GetAllValues for getting all values with multikey. + + NOTE! The returned value is a pointer to string data stored in memory + owned by CSimpleIni. Ensure that the CSimpleIni object is not destroyed + or Reset while you are using this pointer! + + @param a_pSection Section to search + @param a_pKey Key to search for + @param a_pDefault Value to return if the key is not found + @param a_pHasMultiple Optionally receive notification of if there are + multiple entries for this key. + + @return a_pDefault Key was not found in the section + @return other Value of the key + */ + const SI_CHAR * GetValue( + const SI_CHAR * a_pSection, + const SI_CHAR * a_pKey, + const SI_CHAR * a_pDefault = NULL, + bool * a_pHasMultiple = NULL + ) const; + + /** Retrieve a numeric value for a specific key. If multiple keys are enabled + (see SetMultiKey) then only the first value associated with that key + will be returned, see GetAllValues for getting all values with multikey. + + @param a_pSection Section to search + @param a_pKey Key to search for + @param a_nDefault Value to return if the key is not found + @param a_pHasMultiple Optionally receive notification of if there are + multiple entries for this key. + + @return a_nDefault Key was not found in the section + @return other Value of the key + */ + long GetLongValue( + const SI_CHAR * a_pSection, + const SI_CHAR * a_pKey, + long a_nDefault = 0, + bool * a_pHasMultiple = NULL + ) const; + + /** Retrieve a boolean value for a specific key. If multiple keys are enabled + (see SetMultiKey) then only the first value associated with that key + will be returned, see GetAllValues for getting all values with multikey. + + Strings starting with "t", "y", "on" or "1" are returned as logically true. + Strings starting with "f", "n", "of" or "0" are returned as logically false. + For all other values the default is returned. Character comparisons are + case-insensitive. + + @param a_pSection Section to search + @param a_pKey Key to search for + @param a_bDefault Value to return if the key is not found + @param a_pHasMultiple Optionally receive notification of if there are + multiple entries for this key. + + @return a_nDefault Key was not found in the section + @return other Value of the key + */ + bool GetBoolValue( + const SI_CHAR * a_pSection, + const SI_CHAR * a_pKey, + bool a_bDefault = false, + bool * a_pHasMultiple = NULL + ) const; + + /** Add or update a section or value. This will always insert + when multiple keys are enabled. + + @param a_pSection Section to add or update + @param a_pKey Key to add or update. Set to NULL to + create an empty section. + @param a_pValue Value to set. Set to NULL to create an + empty section. + @param a_pComment Comment to be associated with the section or the + key. If a_pKey is NULL then it will be associated + with the section, otherwise the key. Note that a + comment may be set ONLY when the section or key is + first created (i.e. when this function returns the + value SI_INSERTED). If you wish to create a section + with a comment then you need to create the section + separately to the key. The comment string must be + in full comment form already (have a comment + character starting every line). + @param a_bForceReplace Should all existing values in a multi-key INI + file be replaced with this entry. This option has + no effect if not using multi-key files. The + difference between Delete/SetValue and SetValue + with a_bForceReplace = true, is that the load + order and comment will be preserved this way. + + @return SI_Error See error definitions + @return SI_UPDATED Value was updated + @return SI_INSERTED Value was inserted + */ + SI_Error SetValue( + const SI_CHAR * a_pSection, + const SI_CHAR * a_pKey, + const SI_CHAR * a_pValue, + const SI_CHAR * a_pComment = NULL, + bool a_bForceReplace = false + ) + { + return AddEntry(a_pSection, a_pKey, a_pValue, a_pComment, a_bForceReplace, true); + } + + /** Add or update a numeric value. This will always insert + when multiple keys are enabled. + + @param a_pSection Section to add or update + @param a_pKey Key to add or update. + @param a_nValue Value to set. + @param a_pComment Comment to be associated with the key. See the + notes on SetValue() for comments. + @param a_bUseHex By default the value will be written to the file + in decimal format. Set this to true to write it + as hexadecimal. + @param a_bForceReplace Should all existing values in a multi-key INI + file be replaced with this entry. This option has + no effect if not using multi-key files. The + difference between Delete/SetLongValue and + SetLongValue with a_bForceReplace = true, is that + the load order and comment will be preserved this + way. + + @return SI_Error See error definitions + @return SI_UPDATED Value was updated + @return SI_INSERTED Value was inserted + */ + SI_Error SetLongValue( + const SI_CHAR * a_pSection, + const SI_CHAR * a_pKey, + long a_nValue, + const SI_CHAR * a_pComment = NULL, + bool a_bUseHex = false, + bool a_bForceReplace = false + ); + + /** Add or update a boolean value. This will always insert + when multiple keys are enabled. + + @param a_pSection Section to add or update + @param a_pKey Key to add or update. + @param a_bValue Value to set. + @param a_pComment Comment to be associated with the key. See the + notes on SetValue() for comments. + @param a_bForceReplace Should all existing values in a multi-key INI + file be replaced with this entry. This option has + no effect if not using multi-key files. The + difference between Delete/SetBoolValue and + SetBoolValue with a_bForceReplace = true, is that + the load order and comment will be preserved this + way. + + @return SI_Error See error definitions + @return SI_UPDATED Value was updated + @return SI_INSERTED Value was inserted + */ + SI_Error SetBoolValue( + const SI_CHAR * a_pSection, + const SI_CHAR * a_pKey, + bool a_bValue, + const SI_CHAR * a_pComment = NULL, + bool a_bForceReplace = false + ); + + /** Delete an entire section, or a key from a section. Note that the + data returned by GetSection is invalid and must not be used after + anything has been deleted from that section using this method. + Note when multiple keys is enabled, this will delete all keys with + that name; there is no way to selectively delete individual key/values + in this situation. + + @param a_pSection Section to delete key from, or if + a_pKey is NULL, the section to remove. + @param a_pKey Key to remove from the section. Set to + NULL to remove the entire section. + @param a_bRemoveEmpty If the section is empty after this key has + been deleted, should the empty section be + removed? + + @return true Key or section was deleted. + @return false Key or section was not found. + */ + bool Delete( + const SI_CHAR * a_pSection, + const SI_CHAR * a_pKey, + bool a_bRemoveEmpty = false + ); + + /*-----------------------------------------------------------------------*/ + /** @} + @{ @name Converter */ + + /** Return a conversion object to convert text to the same encoding + as is used by the Save(), SaveFile() and SaveString() functions. + Use this to prepare the strings that you wish to append or prepend + to the output INI data. + */ + Converter GetConverter() const { + return Converter(m_bStoreIsUtf8); + } + + /*-----------------------------------------------------------------------*/ + /** @} */ + +private: + // copying is not permitted + CSimpleIniTempl(const CSimpleIniTempl &); // disabled + CSimpleIniTempl & operator=(const CSimpleIniTempl &); // disabled + + /** Parse the data looking for a file comment and store it if found. + */ + SI_Error FindFileComment( + SI_CHAR *& a_pData, + bool a_bCopyStrings + ); + + /** Parse the data looking for the next valid entry. The memory pointed to + by a_pData is modified by inserting NULL characters. The pointer is + updated to the current location in the block of text. + */ + bool FindEntry( + SI_CHAR *& a_pData, + const SI_CHAR *& a_pSection, + const SI_CHAR *& a_pKey, + const SI_CHAR *& a_pVal, + const SI_CHAR *& a_pComment + ) const; + + /** Add the section/key/value to our data. + + @param a_pSection Section name. Sections will be created if they + don't already exist. + @param a_pKey Key name. May be NULL to create an empty section. + Existing entries will be updated. New entries will + be created. + @param a_pValue Value for the key. + @param a_pComment Comment to be associated with the section or the + key. If a_pKey is NULL then it will be associated + with the section, otherwise the key. This must be + a string in full comment form already (have a + comment character starting every line). + @param a_bForceReplace Should all existing values in a multi-key INI + file be replaced with this entry. This option has + no effect if not using multi-key files. The + difference between Delete/AddEntry and AddEntry + with a_bForceReplace = true, is that the load + order and comment will be preserved this way. + @param a_bCopyStrings Should copies of the strings be made or not. + If false then the pointers will be used as is. + */ + SI_Error AddEntry( + const SI_CHAR * a_pSection, + const SI_CHAR * a_pKey, + const SI_CHAR * a_pValue, + const SI_CHAR * a_pComment, + bool a_bForceReplace, + bool a_bCopyStrings + ); + + /** Is the supplied character a whitespace character? */ + inline bool IsSpace(SI_CHAR ch) const { + return (ch == ' ' || ch == '\t' || ch == '\r' || ch == '\n'); + } + + /** Does the supplied character start a comment line? */ + inline bool IsComment(SI_CHAR ch) const { + return (ch == ';' || ch == '#'); + } + + + /** Skip over a newline character (or characters) for either DOS or UNIX */ + inline void SkipNewLine(SI_CHAR *& a_pData) const { + a_pData += (*a_pData == '\r' && *(a_pData+1) == '\n') ? 2 : 1; + } + + /** Make a copy of the supplied string, replacing the original pointer */ + SI_Error CopyString(const SI_CHAR *& a_pString); + + /** Delete a string from the copied strings buffer if necessary */ + void DeleteString(const SI_CHAR * a_pString); + + /** Internal use of our string comparison function */ + bool IsLess(const SI_CHAR * a_pLeft, const SI_CHAR * a_pRight) const { + const static SI_STRLESS isLess = SI_STRLESS(); + return isLess(a_pLeft, a_pRight); + } + + bool IsMultiLineTag(const SI_CHAR * a_pData) const; + bool IsMultiLineData(const SI_CHAR * a_pData) const; + bool LoadMultiLineText( + SI_CHAR *& a_pData, + const SI_CHAR *& a_pVal, + const SI_CHAR * a_pTagName, + bool a_bAllowBlankLinesInComment = false + ) const; + bool IsNewLineChar(SI_CHAR a_c) const; + + bool OutputMultiLineText( + OutputWriter & a_oOutput, + Converter & a_oConverter, + const SI_CHAR * a_pText + ) const; + +private: + /** Copy of the INI file data in our character format. This will be + modified when parsed to have NULL characters added after all + interesting string entries. All of the string pointers to sections, + keys and values point into this block of memory. + */ + SI_CHAR * m_pData; + + /** Length of the data that we have stored. Used when deleting strings + to determine if the string is stored here or in the allocated string + buffer. + */ + size_t m_uDataLen; + + /** File comment for this data, if one exists. */ + const SI_CHAR * m_pFileComment; + + /** Parsed INI data. Section -> (Key -> Value). */ + TSection m_data; + + /** This vector stores allocated memory for copies of strings that have + been supplied after the file load. It will be empty unless SetValue() + has been called. + */ + TNamesDepend m_strings; + + /** Is the format of our datafile UTF-8 or MBCS? */ + bool m_bStoreIsUtf8; + + /** Are multiple values permitted for the same key? */ + bool m_bAllowMultiKey; + + /** Are data values permitted to span multiple lines? */ + bool m_bAllowMultiLine; + + /** Should spaces be written out surrounding the equals sign? */ + bool m_bSpaces; + + /** Next order value, used to ensure sections and keys are output in the + same order that they are loaded/added. + */ + int m_nOrder; +}; + +// --------------------------------------------------------------------------- +// IMPLEMENTATION +// --------------------------------------------------------------------------- + +template +CSimpleIniTempl::CSimpleIniTempl( + bool a_bIsUtf8, + bool a_bAllowMultiKey, + bool a_bAllowMultiLine + ) + : m_pData(0) + , m_uDataLen(0) + , m_pFileComment(NULL) + , m_bStoreIsUtf8(a_bIsUtf8) + , m_bAllowMultiKey(a_bAllowMultiKey) + , m_bAllowMultiLine(a_bAllowMultiLine) + , m_bSpaces(true) + , m_nOrder(0) +{ } + +template +CSimpleIniTempl::~CSimpleIniTempl() +{ + Reset(); +} + +template +void +CSimpleIniTempl::Reset() +{ + // remove all data + delete[] m_pData; + m_pData = NULL; + m_uDataLen = 0; + m_pFileComment = NULL; + if (!m_data.empty()) { + m_data.erase(m_data.begin(), m_data.end()); + } + + // remove all strings + if (!m_strings.empty()) { + typename TNamesDepend::iterator i = m_strings.begin(); + for (; i != m_strings.end(); ++i) { + delete[] const_cast(i->pItem); + } + m_strings.erase(m_strings.begin(), m_strings.end()); + } +} + +template +SI_Error +CSimpleIniTempl::LoadFile( + const char * a_pszFile + ) +{ + FILE * fp = NULL; +#if __STDC_WANT_SECURE_LIB__ && !_WIN32_WCE + fopen_s(&fp, a_pszFile, "rb"); +#else // !__STDC_WANT_SECURE_LIB__ + fp = fopen(a_pszFile, "rb"); +#endif // __STDC_WANT_SECURE_LIB__ + if (!fp) { + return SI_FILE; + } + SI_Error rc = LoadFile(fp); + fclose(fp); + return rc; +} + +#ifdef SI_HAS_WIDE_FILE +template +SI_Error +CSimpleIniTempl::LoadFile( + const SI_WCHAR_T * a_pwszFile + ) +{ +#ifdef _WIN32 + FILE * fp = NULL; +#if __STDC_WANT_SECURE_LIB__ && !_WIN32_WCE + _wfopen_s(&fp, a_pwszFile, L"rb"); +#else // !__STDC_WANT_SECURE_LIB__ + fp = _wfopen(a_pwszFile, L"rb"); +#endif // __STDC_WANT_SECURE_LIB__ + if (!fp) return SI_FILE; + SI_Error rc = LoadFile(fp); + fclose(fp); + return rc; +#else // !_WIN32 (therefore SI_CONVERT_ICU) + char szFile[256]; + u_austrncpy(szFile, a_pwszFile, sizeof(szFile)); + return LoadFile(szFile); +#endif // _WIN32 +} +#endif // SI_HAS_WIDE_FILE + +template +SI_Error +CSimpleIniTempl::LoadFile( + FILE * a_fpFile + ) +{ + // load the raw file data + int retval = fseek(a_fpFile, 0, SEEK_END); + if (retval != 0) { + return SI_FILE; + } + long lSize = ftell(a_fpFile); + if (lSize < 0) { + return SI_FILE; + } + if (lSize == 0) { + return SI_OK; + } + char * pData = new char[lSize]; + if (!pData) { + return SI_NOMEM; + } + fseek(a_fpFile, 0, SEEK_SET); + size_t uRead = fread(pData, sizeof(char), lSize, a_fpFile); + if (uRead != (size_t) lSize) { + delete[] pData; + return SI_FILE; + } + + // convert the raw data to unicode + SI_Error rc = Load(pData, uRead); + delete[] pData; + return rc; +} + +template +SI_Error +CSimpleIniTempl::Load( + const char * a_pData, + size_t a_uDataLen + ) +{ + SI_CONVERTER converter(m_bStoreIsUtf8); + + if (a_uDataLen == 0) { + return SI_OK; + } + + // consume the UTF-8 BOM if it exists + if (m_bStoreIsUtf8 && a_uDataLen >= 3) { + if (memcmp(a_pData, SI_UTF8_SIGNATURE, 3) == 0) { + a_pData += 3; + a_uDataLen -= 3; + } + } + + // determine the length of the converted data + size_t uLen = converter.SizeFromStore(a_pData, a_uDataLen); + if (uLen == (size_t)(-1)) { + return SI_FAIL; + } + + // allocate memory for the data, ensure that there is a NULL + // terminator wherever the converted data ends + SI_CHAR * pData = new SI_CHAR[uLen+1]; + if (!pData) { + return SI_NOMEM; + } + memset(pData, 0, sizeof(SI_CHAR)*(uLen+1)); + + // convert the data + if (!converter.ConvertFromStore(a_pData, a_uDataLen, pData, uLen)) { + delete[] pData; + return SI_FAIL; + } + + // parse it + const static SI_CHAR empty = 0; + SI_CHAR * pWork = pData; + const SI_CHAR * pSection = ∅ + const SI_CHAR * pItem = NULL; + const SI_CHAR * pVal = NULL; + const SI_CHAR * pComment = NULL; + + // We copy the strings if we are loading data into this class when we + // already have stored some. + bool bCopyStrings = (m_pData != NULL); + + // find a file comment if it exists, this is a comment that starts at the + // beginning of the file and continues until the first blank line. + SI_Error rc = FindFileComment(pWork, bCopyStrings); + if (rc < 0) return rc; + + // add every entry in the file to the data table + while (FindEntry(pWork, pSection, pItem, pVal, pComment)) { + rc = AddEntry(pSection, pItem, pVal, pComment, false, bCopyStrings); + if (rc < 0) return rc; + } + + // store these strings if we didn't copy them + if (bCopyStrings) { + delete[] pData; + } + else { + m_pData = pData; + m_uDataLen = uLen+1; + } + + return SI_OK; +} + +#ifdef SI_SUPPORT_IOSTREAMS +template +SI_Error +CSimpleIniTempl::Load( + std::istream & a_istream + ) +{ + std::string strData; + char szBuf[512]; + do { + a_istream.get(szBuf, sizeof(szBuf), '\0'); + strData.append(szBuf); + } + while (a_istream.good()); + return Load(strData); +} +#endif // SI_SUPPORT_IOSTREAMS + +template +SI_Error +CSimpleIniTempl::FindFileComment( + SI_CHAR *& a_pData, + bool a_bCopyStrings + ) +{ + // there can only be a single file comment + if (m_pFileComment) { + return SI_OK; + } + + // Load the file comment as multi-line text, this will modify all of + // the newline characters to be single \n chars + if (!LoadMultiLineText(a_pData, m_pFileComment, NULL, false)) { + return SI_OK; + } + + // copy the string if necessary + if (a_bCopyStrings) { + SI_Error rc = CopyString(m_pFileComment); + if (rc < 0) return rc; + } + + return SI_OK; +} + +template +bool +CSimpleIniTempl::FindEntry( + SI_CHAR *& a_pData, + const SI_CHAR *& a_pSection, + const SI_CHAR *& a_pKey, + const SI_CHAR *& a_pVal, + const SI_CHAR *& a_pComment + ) const +{ + a_pComment = NULL; + + SI_CHAR * pTrail = NULL; + while (*a_pData) { + // skip spaces and empty lines + while (*a_pData && IsSpace(*a_pData)) { + ++a_pData; + } + if (!*a_pData) { + break; + } + + // skip processing of comment lines but keep a pointer to + // the start of the comment. + if (IsComment(*a_pData)) { + LoadMultiLineText(a_pData, a_pComment, NULL, true); + continue; + } + + // process section names + if (*a_pData == '[') { + // skip leading spaces + ++a_pData; + while (*a_pData && IsSpace(*a_pData)) { + ++a_pData; + } + + // find the end of the section name (it may contain spaces) + // and convert it to lowercase as necessary + a_pSection = a_pData; + while (*a_pData && *a_pData != ']' && !IsNewLineChar(*a_pData)) { + ++a_pData; + } + + // if it's an invalid line, just skip it + if (*a_pData != ']') { + continue; + } + + // remove trailing spaces from the section + pTrail = a_pData - 1; + while (pTrail >= a_pSection && IsSpace(*pTrail)) { + --pTrail; + } + ++pTrail; + *pTrail = 0; + + // skip to the end of the line + ++a_pData; // safe as checked that it == ']' above + while (*a_pData && !IsNewLineChar(*a_pData)) { + ++a_pData; + } + + a_pKey = NULL; + a_pVal = NULL; + return true; + } + + // find the end of the key name (it may contain spaces) + // and convert it to lowercase as necessary + a_pKey = a_pData; + while (*a_pData && *a_pData != '=' && !IsNewLineChar(*a_pData)) { + ++a_pData; + } + + // if it's an invalid line, just skip it + if (*a_pData != '=') { + continue; + } + + // empty keys are invalid + if (a_pKey == a_pData) { + while (*a_pData && !IsNewLineChar(*a_pData)) { + ++a_pData; + } + continue; + } + + // remove trailing spaces from the key + pTrail = a_pData - 1; + while (pTrail >= a_pKey && IsSpace(*pTrail)) { + --pTrail; + } + ++pTrail; + *pTrail = 0; + + // skip leading whitespace on the value + ++a_pData; // safe as checked that it == '=' above + while (*a_pData && !IsNewLineChar(*a_pData) && IsSpace(*a_pData)) { + ++a_pData; + } + + // find the end of the value which is the end of this line + a_pVal = a_pData; + while (*a_pData && !IsNewLineChar(*a_pData)) { + ++a_pData; + } + + // remove trailing spaces from the value + pTrail = a_pData - 1; + if (*a_pData) { // prepare for the next round + SkipNewLine(a_pData); + } + while (pTrail >= a_pVal && IsSpace(*pTrail)) { + --pTrail; + } + ++pTrail; + *pTrail = 0; + + // check for multi-line entries + if (m_bAllowMultiLine && IsMultiLineTag(a_pVal)) { + // skip the "<<<" to get the tag that will end the multiline + const SI_CHAR * pTagName = a_pVal + 3; + return LoadMultiLineText(a_pData, a_pVal, pTagName); + } + + // return the standard entry + return true; + } + + return false; +} + +template +bool +CSimpleIniTempl::IsMultiLineTag( + const SI_CHAR * a_pVal + ) const +{ + // check for the "<<<" prefix for a multi-line entry + if (*a_pVal++ != '<') return false; + if (*a_pVal++ != '<') return false; + if (*a_pVal++ != '<') return false; + return true; +} + +template +bool +CSimpleIniTempl::IsMultiLineData( + const SI_CHAR * a_pData + ) const +{ + // data is multi-line if it has any of the following features: + // * whitespace prefix + // * embedded newlines + // * whitespace suffix + + // empty string + if (!*a_pData) { + return false; + } + + // check for prefix + if (IsSpace(*a_pData)) { + return true; + } + + // embedded newlines + while (*a_pData) { + if (IsNewLineChar(*a_pData)) { + return true; + } + ++a_pData; + } + + // check for suffix + if (IsSpace(*--a_pData)) { + return true; + } + + return false; +} + +template +bool +CSimpleIniTempl::IsNewLineChar( + SI_CHAR a_c + ) const +{ + return (a_c == '\n' || a_c == '\r'); +} + +template +bool +CSimpleIniTempl::LoadMultiLineText( + SI_CHAR *& a_pData, + const SI_CHAR *& a_pVal, + const SI_CHAR * a_pTagName, + bool a_bAllowBlankLinesInComment + ) const +{ + // we modify this data to strip all newlines down to a single '\n' + // character. This means that on Windows we need to strip out some + // characters which will make the data shorter. + // i.e. LINE1-LINE1\r\nLINE2-LINE2\0 will become + // LINE1-LINE1\nLINE2-LINE2\0 + // The pDataLine entry is the pointer to the location in memory that + // the current line needs to start to run following the existing one. + // This may be the same as pCurrLine in which case no move is needed. + SI_CHAR * pDataLine = a_pData; + SI_CHAR * pCurrLine; + + // value starts at the current line + a_pVal = a_pData; + + // find the end tag. This tag must start in column 1 and be + // followed by a newline. No whitespace removal is done while + // searching for this tag. + SI_CHAR cEndOfLineChar = *a_pData; + for(;;) { + // if we are loading comments then we need a comment character as + // the first character on every line + if (!a_pTagName && !IsComment(*a_pData)) { + // if we aren't allowing blank lines then we're done + if (!a_bAllowBlankLinesInComment) { + break; + } + + // if we are allowing blank lines then we only include them + // in this comment if another comment follows, so read ahead + // to find out. + SI_CHAR * pCurr = a_pData; + int nNewLines = 0; + while (IsSpace(*pCurr)) { + if (IsNewLineChar(*pCurr)) { + ++nNewLines; + SkipNewLine(pCurr); + } + else { + ++pCurr; + } + } + + // we have a comment, add the blank lines to the output + // and continue processing from here + if (IsComment(*pCurr)) { + for (; nNewLines > 0; --nNewLines) *pDataLine++ = '\n'; + a_pData = pCurr; + continue; + } + + // the comment ends here + break; + } + + // find the end of this line + pCurrLine = a_pData; + while (*a_pData && !IsNewLineChar(*a_pData)) ++a_pData; + + // move this line down to the location that it should be if necessary + if (pDataLine < pCurrLine) { + size_t nLen = (size_t) (a_pData - pCurrLine); + memmove(pDataLine, pCurrLine, nLen * sizeof(SI_CHAR)); + pDataLine[nLen] = '\0'; + } + + // end the line with a NULL + cEndOfLineChar = *a_pData; + *a_pData = 0; + + // if are looking for a tag then do the check now. This is done before + // checking for end of the data, so that if we have the tag at the end + // of the data then the tag is removed correctly. + if (a_pTagName && + (!IsLess(pDataLine, a_pTagName) && !IsLess(a_pTagName, pDataLine))) + { + break; + } + + // if we are at the end of the data then we just automatically end + // this entry and return the current data. + if (!cEndOfLineChar) { + return true; + } + + // otherwise we need to process this newline to ensure that it consists + // of just a single \n character. + pDataLine += (a_pData - pCurrLine); + *a_pData = cEndOfLineChar; + SkipNewLine(a_pData); + *pDataLine++ = '\n'; + } + + // if we didn't find a comment at all then return false + if (a_pVal == a_pData) { + a_pVal = NULL; + return false; + } + + // the data (which ends at the end of the last line) needs to be + // null-terminated BEFORE before the newline character(s). If the + // user wants a new line in the multi-line data then they need to + // add an empty line before the tag. + *--pDataLine = '\0'; + + // if looking for a tag and if we aren't at the end of the data, + // then move a_pData to the start of the next line. + if (a_pTagName && cEndOfLineChar) { + SI_ASSERT(IsNewLineChar(cEndOfLineChar)); + *a_pData = cEndOfLineChar; + SkipNewLine(a_pData); + } + + return true; +} + +template +SI_Error +CSimpleIniTempl::CopyString( + const SI_CHAR *& a_pString + ) +{ + size_t uLen = 0; + if (sizeof(SI_CHAR) == sizeof(char)) { + uLen = strlen((const char *)a_pString); + } + else if (sizeof(SI_CHAR) == sizeof(wchar_t)) { + uLen = wcslen((const wchar_t *)a_pString); + } + else { + for ( ; a_pString[uLen]; ++uLen) /*loop*/ ; + } + ++uLen; // NULL character + SI_CHAR * pCopy = new SI_CHAR[uLen]; + if (!pCopy) { + return SI_NOMEM; + } + memcpy(pCopy, a_pString, sizeof(SI_CHAR)*uLen); + m_strings.push_back(pCopy); + a_pString = pCopy; + return SI_OK; +} + +template +SI_Error +CSimpleIniTempl::AddEntry( + const SI_CHAR * a_pSection, + const SI_CHAR * a_pKey, + const SI_CHAR * a_pValue, + const SI_CHAR * a_pComment, + bool a_bForceReplace, + bool a_bCopyStrings + ) +{ + SI_Error rc; + bool bInserted = false; + + SI_ASSERT(!a_pComment || IsComment(*a_pComment)); + + // if we are copying strings then make a copy of the comment now + // because we will need it when we add the entry. + if (a_bCopyStrings && a_pComment) { + rc = CopyString(a_pComment); + if (rc < 0) return rc; + } + + // create the section entry if necessary + typename TSection::iterator iSection = m_data.find(a_pSection); + if (iSection == m_data.end()) { + // if the section doesn't exist then we need a copy as the + // string needs to last beyond the end of this function + if (a_bCopyStrings) { + rc = CopyString(a_pSection); + if (rc < 0) return rc; + } + + // only set the comment if this is a section only entry + Entry oSection(a_pSection, ++m_nOrder); + if (a_pComment && (!a_pKey || !a_pValue)) { + oSection.pComment = a_pComment; + } + + typename TSection::value_type oEntry(oSection, TKeyVal()); + typedef typename TSection::iterator SectionIterator; + std::pair i = m_data.insert(oEntry); + iSection = i.first; + bInserted = true; + } + if (!a_pKey || !a_pValue) { + // section only entries are specified with pItem and pVal as NULL + return bInserted ? SI_INSERTED : SI_UPDATED; + } + + // check for existence of the key + TKeyVal & keyval = iSection->second; + typename TKeyVal::iterator iKey = keyval.find(a_pKey); + + // remove all existing entries but save the load order and + // comment of the first entry + int nLoadOrder = ++m_nOrder; + if (iKey != keyval.end() && m_bAllowMultiKey && a_bForceReplace) { + const SI_CHAR * pComment = NULL; + while (iKey != keyval.end() && !IsLess(a_pKey, iKey->first.pItem)) { + if (iKey->first.nOrder < nLoadOrder) { + nLoadOrder = iKey->first.nOrder; + pComment = iKey->first.pComment; + } + ++iKey; + } + if (pComment) { + DeleteString(a_pComment); + a_pComment = pComment; + CopyString(a_pComment); + } + Delete(a_pSection, a_pKey); + iKey = keyval.end(); + } + + // make string copies if necessary + bool bForceCreateNewKey = m_bAllowMultiKey && !a_bForceReplace; + if (a_bCopyStrings) { + if (bForceCreateNewKey || iKey == keyval.end()) { + // if the key doesn't exist then we need a copy as the + // string needs to last beyond the end of this function + // because we will be inserting the key next + rc = CopyString(a_pKey); + if (rc < 0) return rc; + } + + // we always need a copy of the value + rc = CopyString(a_pValue); + if (rc < 0) return rc; + } + + // create the key entry + if (iKey == keyval.end() || bForceCreateNewKey) { + Entry oKey(a_pKey, nLoadOrder); + if (a_pComment) { + oKey.pComment = a_pComment; + } + typename TKeyVal::value_type oEntry(oKey, (const SI_CHAR *) NULL); + iKey = keyval.insert(oEntry); + bInserted = true; + } + iKey->second = a_pValue; + return bInserted ? SI_INSERTED : SI_UPDATED; +} + +template +const SI_CHAR * +CSimpleIniTempl::GetValue( + const SI_CHAR * a_pSection, + const SI_CHAR * a_pKey, + const SI_CHAR * a_pDefault, + bool * a_pHasMultiple + ) const +{ + if (a_pHasMultiple) { + *a_pHasMultiple = false; + } + if (!a_pSection || !a_pKey) { + return a_pDefault; + } + typename TSection::const_iterator iSection = m_data.find(a_pSection); + if (iSection == m_data.end()) { + return a_pDefault; + } + typename TKeyVal::const_iterator iKeyVal = iSection->second.find(a_pKey); + if (iKeyVal == iSection->second.end()) { + return a_pDefault; + } + + // check for multiple entries with the same key + if (m_bAllowMultiKey && a_pHasMultiple) { + typename TKeyVal::const_iterator iTemp = iKeyVal; + if (++iTemp != iSection->second.end()) { + if (!IsLess(a_pKey, iTemp->first.pItem)) { + *a_pHasMultiple = true; + } + } + } + + return iKeyVal->second; +} + +template +long +CSimpleIniTempl::GetLongValue( + const SI_CHAR * a_pSection, + const SI_CHAR * a_pKey, + long a_nDefault, + bool * a_pHasMultiple + ) const +{ + // return the default if we don't have a value + const SI_CHAR * pszValue = GetValue(a_pSection, a_pKey, NULL, a_pHasMultiple); + if (!pszValue || !*pszValue) return a_nDefault; + + // convert to UTF-8/MBCS which for a numeric value will be the same as ASCII + char szValue[64] = { 0 }; + SI_CONVERTER c(m_bStoreIsUtf8); + if (!c.ConvertToStore(pszValue, szValue, sizeof(szValue))) { + return a_nDefault; + } + + // handle the value as hex if prefaced with "0x" + long nValue = a_nDefault; + char * pszSuffix = szValue; + if (szValue[0] == '0' && (szValue[1] == 'x' || szValue[1] == 'X')) { + if (!szValue[2]) return a_nDefault; + nValue = strtol(&szValue[2], &pszSuffix, 16); + } + else { + nValue = strtol(szValue, &pszSuffix, 10); + } + + // any invalid strings will return the default value + if (*pszSuffix) { + return a_nDefault; + } + + return nValue; +} + +template +SI_Error +CSimpleIniTempl::SetLongValue( + const SI_CHAR * a_pSection, + const SI_CHAR * a_pKey, + long a_nValue, + const SI_CHAR * a_pComment, + bool a_bUseHex, + bool a_bForceReplace + ) +{ + // use SetValue to create sections + if (!a_pSection || !a_pKey) return SI_FAIL; + + // convert to an ASCII string + char szInput[64]; +#if __STDC_WANT_SECURE_LIB__ && !_WIN32_WCE + sprintf_s(szInput, a_bUseHex ? "0x%lx" : "%ld", a_nValue); +#else // !__STDC_WANT_SECURE_LIB__ + sprintf(szInput, a_bUseHex ? "0x%lx" : "%ld", a_nValue); +#endif // __STDC_WANT_SECURE_LIB__ + + // convert to output text + SI_CHAR szOutput[64]; + SI_CONVERTER c(m_bStoreIsUtf8); + c.ConvertFromStore(szInput, strlen(szInput) + 1, + szOutput, sizeof(szOutput) / sizeof(SI_CHAR)); + + // actually add it + return AddEntry(a_pSection, a_pKey, szOutput, a_pComment, a_bForceReplace, true); +} + +template +bool +CSimpleIniTempl::GetBoolValue( + const SI_CHAR * a_pSection, + const SI_CHAR * a_pKey, + bool a_bDefault, + bool * a_pHasMultiple + ) const +{ + // return the default if we don't have a value + const SI_CHAR * pszValue = GetValue(a_pSection, a_pKey, NULL, a_pHasMultiple); + if (!pszValue || !*pszValue) return a_bDefault; + + // we only look at the minimum number of characters + switch (pszValue[0]) { + case 't': case 'T': // true + case 'y': case 'Y': // yes + case '1': // 1 (one) + return true; + + case 'f': case 'F': // false + case 'n': case 'N': // no + case '0': // 0 (zero) + return false; + + case 'o': case 'O': + if (pszValue[1] == 'n' || pszValue[1] == 'N') return true; // on + if (pszValue[1] == 'f' || pszValue[1] == 'F') return false; // off + break; + } + + // no recognized value, return the default + return a_bDefault; +} + +template +SI_Error +CSimpleIniTempl::SetBoolValue( + const SI_CHAR * a_pSection, + const SI_CHAR * a_pKey, + bool a_bValue, + const SI_CHAR * a_pComment, + bool a_bForceReplace + ) +{ + // use SetValue to create sections + if (!a_pSection || !a_pKey) return SI_FAIL; + + // convert to an ASCII string + const char * pszInput = a_bValue ? "true" : "false"; + + // convert to output text + SI_CHAR szOutput[64]; + SI_CONVERTER c(m_bStoreIsUtf8); + c.ConvertFromStore(pszInput, strlen(pszInput) + 1, + szOutput, sizeof(szOutput) / sizeof(SI_CHAR)); + + // actually add it + return AddEntry(a_pSection, a_pKey, szOutput, a_pComment, a_bForceReplace, true); +} + +template +bool +CSimpleIniTempl::GetAllValues( + const SI_CHAR * a_pSection, + const SI_CHAR * a_pKey, + TNamesDepend & a_values + ) const +{ + a_values.clear(); + + if (!a_pSection || !a_pKey) { + return false; + } + typename TSection::const_iterator iSection = m_data.find(a_pSection); + if (iSection == m_data.end()) { + return false; + } + typename TKeyVal::const_iterator iKeyVal = iSection->second.find(a_pKey); + if (iKeyVal == iSection->second.end()) { + return false; + } + + // insert all values for this key + a_values.push_back(Entry(iKeyVal->second, iKeyVal->first.pComment, iKeyVal->first.nOrder)); + if (m_bAllowMultiKey) { + ++iKeyVal; + while (iKeyVal != iSection->second.end() && !IsLess(a_pKey, iKeyVal->first.pItem)) { + a_values.push_back(Entry(iKeyVal->second, iKeyVal->first.pComment, iKeyVal->first.nOrder)); + ++iKeyVal; + } + } + + return true; +} + +template +int +CSimpleIniTempl::GetSectionSize( + const SI_CHAR * a_pSection + ) const +{ + if (!a_pSection) { + return -1; + } + + typename TSection::const_iterator iSection = m_data.find(a_pSection); + if (iSection == m_data.end()) { + return -1; + } + const TKeyVal & section = iSection->second; + + // if multi-key isn't permitted then the section size is + // the number of keys that we have. + if (!m_bAllowMultiKey || section.empty()) { + return (int) section.size(); + } + + // otherwise we need to count them + int nCount = 0; + const SI_CHAR * pLastKey = NULL; + typename TKeyVal::const_iterator iKeyVal = section.begin(); + for (int n = 0; iKeyVal != section.end(); ++iKeyVal, ++n) { + if (!pLastKey || IsLess(pLastKey, iKeyVal->first.pItem)) { + ++nCount; + pLastKey = iKeyVal->first.pItem; + } + } + return nCount; +} + +template +const typename CSimpleIniTempl::TKeyVal * +CSimpleIniTempl::GetSection( + const SI_CHAR * a_pSection + ) const +{ + if (a_pSection) { + typename TSection::const_iterator i = m_data.find(a_pSection); + if (i != m_data.end()) { + return &(i->second); + } + } + return 0; +} + +template +void +CSimpleIniTempl::GetAllSections( + TNamesDepend & a_names + ) const +{ + a_names.clear(); + typename TSection::const_iterator i = m_data.begin(); + for (int n = 0; i != m_data.end(); ++i, ++n ) { + a_names.push_back(i->first); + } +} + +template +bool +CSimpleIniTempl::GetAllKeys( + const SI_CHAR * a_pSection, + TNamesDepend & a_names + ) const +{ + a_names.clear(); + + if (!a_pSection) { + return false; + } + + typename TSection::const_iterator iSection = m_data.find(a_pSection); + if (iSection == m_data.end()) { + return false; + } + + const TKeyVal & section = iSection->second; + const SI_CHAR * pLastKey = NULL; + typename TKeyVal::const_iterator iKeyVal = section.begin(); + for (int n = 0; iKeyVal != section.end(); ++iKeyVal, ++n ) { + if (!pLastKey || IsLess(pLastKey, iKeyVal->first.pItem)) { + a_names.push_back(iKeyVal->first); + pLastKey = iKeyVal->first.pItem; + } + } + + return true; +} + +template +SI_Error +CSimpleIniTempl::SaveFile( + const char * a_pszFile, + bool a_bAddSignature + ) const +{ + FILE * fp = NULL; +#if __STDC_WANT_SECURE_LIB__ && !_WIN32_WCE + fopen_s(&fp, a_pszFile, "wb"); +#else // !__STDC_WANT_SECURE_LIB__ + fp = fopen(a_pszFile, "wb"); +#endif // __STDC_WANT_SECURE_LIB__ + if (!fp) return SI_FILE; + SI_Error rc = SaveFile(fp, a_bAddSignature); + fclose(fp); + return rc; +} + +#ifdef SI_HAS_WIDE_FILE +template +SI_Error +CSimpleIniTempl::SaveFile( + const SI_WCHAR_T * a_pwszFile, + bool a_bAddSignature + ) const +{ +#ifdef _WIN32 + FILE * fp = NULL; +#if __STDC_WANT_SECURE_LIB__ && !_WIN32_WCE + _wfopen_s(&fp, a_pwszFile, L"wb"); +#else // !__STDC_WANT_SECURE_LIB__ + fp = _wfopen(a_pwszFile, L"wb"); +#endif // __STDC_WANT_SECURE_LIB__ + if (!fp) return SI_FILE; + SI_Error rc = SaveFile(fp, a_bAddSignature); + fclose(fp); + return rc; +#else // !_WIN32 (therefore SI_CONVERT_ICU) + char szFile[256]; + u_austrncpy(szFile, a_pwszFile, sizeof(szFile)); + return SaveFile(szFile, a_bAddSignature); +#endif // _WIN32 +} +#endif // SI_HAS_WIDE_FILE + +template +SI_Error +CSimpleIniTempl::SaveFile( + FILE * a_pFile, + bool a_bAddSignature + ) const +{ + FileWriter writer(a_pFile); + return Save(writer, a_bAddSignature); +} + +template +SI_Error +CSimpleIniTempl::Save( + OutputWriter & a_oOutput, + bool a_bAddSignature + ) const +{ + Converter convert(m_bStoreIsUtf8); + + // add the UTF-8 signature if it is desired + if (m_bStoreIsUtf8 && a_bAddSignature) { + a_oOutput.Write(SI_UTF8_SIGNATURE); + } + + // get all of the sections sorted in load order + TNamesDepend oSections; + GetAllSections(oSections); +#if defined(_MSC_VER) && _MSC_VER <= 1200 + oSections.sort(); +#elif defined(__BORLANDC__) + oSections.sort(Entry::LoadOrder()); +#else + oSections.sort(typename Entry::LoadOrder()); +#endif + + // write the file comment if we have one + bool bNeedNewLine = false; + if (m_pFileComment) { + if (!OutputMultiLineText(a_oOutput, convert, m_pFileComment)) { + return SI_FAIL; + } + bNeedNewLine = true; + } + + // iterate through our sections and output the data + typename TNamesDepend::const_iterator iSection = oSections.begin(); + for ( ; iSection != oSections.end(); ++iSection ) { + // write out the comment if there is one + if (iSection->pComment) { + if (bNeedNewLine) { + a_oOutput.Write(SI_NEWLINE_A); + a_oOutput.Write(SI_NEWLINE_A); + } + if (!OutputMultiLineText(a_oOutput, convert, iSection->pComment)) { + return SI_FAIL; + } + bNeedNewLine = false; + } + + if (bNeedNewLine) { + a_oOutput.Write(SI_NEWLINE_A); + a_oOutput.Write(SI_NEWLINE_A); + bNeedNewLine = false; + } + + // write the section (unless there is no section name) + if (*iSection->pItem) { + if (!convert.ConvertToStore(iSection->pItem)) { + return SI_FAIL; + } + a_oOutput.Write("["); + a_oOutput.Write(convert.Data()); + a_oOutput.Write("]"); + a_oOutput.Write(SI_NEWLINE_A); + } + + // get all of the keys sorted in load order + TNamesDepend oKeys; + GetAllKeys(iSection->pItem, oKeys); +#if defined(_MSC_VER) && _MSC_VER <= 1200 + oKeys.sort(); +#elif defined(__BORLANDC__) + oKeys.sort(Entry::LoadOrder()); +#else + oKeys.sort(typename Entry::LoadOrder()); +#endif + + // write all keys and values + typename TNamesDepend::const_iterator iKey = oKeys.begin(); + for ( ; iKey != oKeys.end(); ++iKey) { + // get all values for this key + TNamesDepend oValues; + GetAllValues(iSection->pItem, iKey->pItem, oValues); + + typename TNamesDepend::const_iterator iValue = oValues.begin(); + for ( ; iValue != oValues.end(); ++iValue) { + // write out the comment if there is one + if (iValue->pComment) { + a_oOutput.Write(SI_NEWLINE_A); + if (!OutputMultiLineText(a_oOutput, convert, iValue->pComment)) { + return SI_FAIL; + } + } + + // write the key + if (!convert.ConvertToStore(iKey->pItem)) { + return SI_FAIL; + } + a_oOutput.Write(convert.Data()); + + // write the value + if (!convert.ConvertToStore(iValue->pItem)) { + return SI_FAIL; + } + a_oOutput.Write(m_bSpaces ? " = " : "="); + if (m_bAllowMultiLine && IsMultiLineData(iValue->pItem)) { + // multi-line data needs to be processed specially to ensure + // that we use the correct newline format for the current system + a_oOutput.Write("<<pItem)) { + return SI_FAIL; + } + a_oOutput.Write("SI-END-OF-MULTILINE-TEXT"); + } + else { + a_oOutput.Write(convert.Data()); + } + a_oOutput.Write(SI_NEWLINE_A); + } + } + + bNeedNewLine = true; + } + + return SI_OK; +} + +template +bool +CSimpleIniTempl::OutputMultiLineText( + OutputWriter & a_oOutput, + Converter & a_oConverter, + const SI_CHAR * a_pText + ) const +{ + const SI_CHAR * pEndOfLine; + SI_CHAR cEndOfLineChar = *a_pText; + while (cEndOfLineChar) { + // find the end of this line + pEndOfLine = a_pText; + for (; *pEndOfLine && *pEndOfLine != '\n'; ++pEndOfLine) /*loop*/ ; + cEndOfLineChar = *pEndOfLine; + + // temporarily null terminate, convert and output the line + *const_cast(pEndOfLine) = 0; + if (!a_oConverter.ConvertToStore(a_pText)) { + return false; + } + *const_cast(pEndOfLine) = cEndOfLineChar; + a_pText += (pEndOfLine - a_pText) + 1; + a_oOutput.Write(a_oConverter.Data()); + a_oOutput.Write(SI_NEWLINE_A); + } + return true; +} + +template +bool +CSimpleIniTempl::Delete( + const SI_CHAR * a_pSection, + const SI_CHAR * a_pKey, + bool a_bRemoveEmpty + ) +{ + if (!a_pSection) { + return false; + } + + typename TSection::iterator iSection = m_data.find(a_pSection); + if (iSection == m_data.end()) { + return false; + } + + // remove a single key if we have a keyname + if (a_pKey) { + typename TKeyVal::iterator iKeyVal = iSection->second.find(a_pKey); + if (iKeyVal == iSection->second.end()) { + return false; + } + + // remove any copied strings and then the key + typename TKeyVal::iterator iDelete; + do { + iDelete = iKeyVal++; + + DeleteString(iDelete->first.pItem); + DeleteString(iDelete->second); + iSection->second.erase(iDelete); + } + while (iKeyVal != iSection->second.end() + && !IsLess(a_pKey, iKeyVal->first.pItem)); + + // done now if the section is not empty or we are not pruning away + // the empty sections. Otherwise let it fall through into the section + // deletion code + if (!a_bRemoveEmpty || !iSection->second.empty()) { + return true; + } + } + else { + // delete all copied strings from this section. The actual + // entries will be removed when the section is removed. + typename TKeyVal::iterator iKeyVal = iSection->second.begin(); + for ( ; iKeyVal != iSection->second.end(); ++iKeyVal) { + DeleteString(iKeyVal->first.pItem); + DeleteString(iKeyVal->second); + } + } + + // delete the section itself + DeleteString(iSection->first.pItem); + m_data.erase(iSection); + + return true; +} + +template +void +CSimpleIniTempl::DeleteString( + const SI_CHAR * a_pString + ) +{ + // strings may exist either inside the data block, or they will be + // individually allocated and stored in m_strings. We only physically + // delete those stored in m_strings. + if (a_pString < m_pData || a_pString >= m_pData + m_uDataLen) { + typename TNamesDepend::iterator i = m_strings.begin(); + for (;i != m_strings.end(); ++i) { + if (a_pString == i->pItem) { + delete[] const_cast(i->pItem); + m_strings.erase(i); + break; + } + } + } +} + +// --------------------------------------------------------------------------- +// CONVERSION FUNCTIONS +// --------------------------------------------------------------------------- + +// Defines the conversion classes for different libraries. Before including +// SimpleIni.h, set the converter that you wish you use by defining one of the +// following symbols. +// +// SI_CONVERT_GENERIC Use the Unicode reference conversion library in +// the accompanying files ConvertUTF.h/c +// SI_CONVERT_ICU Use the IBM ICU conversion library. Requires +// ICU headers on include path and icuuc.lib +// SI_CONVERT_WIN32 Use the Win32 API functions for conversion. + +#if !defined(SI_CONVERT_GENERIC) && !defined(SI_CONVERT_WIN32) && !defined(SI_CONVERT_ICU) +# ifdef _WIN32 +# define SI_CONVERT_WIN32 +# else +# define SI_CONVERT_GENERIC +# endif +#endif + +/** + * Generic case-sensitive less than comparison. This class returns numerically + * ordered ASCII case-sensitive text for all possible sizes and types of + * SI_CHAR. + */ +template +struct SI_GenericCase { + bool operator()(const SI_CHAR * pLeft, const SI_CHAR * pRight) const { + long cmp; + for ( ;*pLeft && *pRight; ++pLeft, ++pRight) { + cmp = (long) *pLeft - (long) *pRight; + if (cmp != 0) { + return cmp < 0; + } + } + return *pRight != 0; + } +}; + +/** + * Generic ASCII case-insensitive less than comparison. This class returns + * numerically ordered ASCII case-insensitive text for all possible sizes + * and types of SI_CHAR. It is not safe for MBCS text comparison where + * ASCII A-Z characters are used in the encoding of multi-byte characters. + */ +template +struct SI_GenericNoCase { + inline SI_CHAR locase(SI_CHAR ch) const { + return (ch < 'A' || ch > 'Z') ? ch : (ch - 'A' + 'a'); + } + bool operator()(const SI_CHAR * pLeft, const SI_CHAR * pRight) const { + long cmp; + for ( ;*pLeft && *pRight; ++pLeft, ++pRight) { + cmp = (long) locase(*pLeft) - (long) locase(*pRight); + if (cmp != 0) { + return cmp < 0; + } + } + return *pRight != 0; + } +}; + +/** + * Null conversion class for MBCS/UTF-8 to char (or equivalent). + */ +template +class SI_ConvertA { + bool m_bStoreIsUtf8; +protected: + SI_ConvertA() { } +public: + SI_ConvertA(bool a_bStoreIsUtf8) : m_bStoreIsUtf8(a_bStoreIsUtf8) { } + + /* copy and assignment */ + SI_ConvertA(const SI_ConvertA & rhs) { operator=(rhs); } + SI_ConvertA & operator=(const SI_ConvertA & rhs) { + m_bStoreIsUtf8 = rhs.m_bStoreIsUtf8; + return *this; + } + + /** Calculate the number of SI_CHAR required for converting the input + * from the storage format. The storage format is always UTF-8 or MBCS. + * + * @param a_pInputData Data in storage format to be converted to SI_CHAR. + * @param a_uInputDataLen Length of storage format data in bytes. This + * must be the actual length of the data, including + * NULL byte if NULL terminated string is required. + * @return Number of SI_CHAR required by the string when + * converted. If there are embedded NULL bytes in the + * input data, only the string up and not including + * the NULL byte will be converted. + * @return -1 cast to size_t on a conversion error. + */ + size_t SizeFromStore( + const char * a_pInputData, + size_t a_uInputDataLen) + { + (void)a_pInputData; + SI_ASSERT(a_uInputDataLen != (size_t) -1); + + // ASCII/MBCS/UTF-8 needs no conversion + return a_uInputDataLen; + } + + /** Convert the input string from the storage format to SI_CHAR. + * The storage format is always UTF-8 or MBCS. + * + * @param a_pInputData Data in storage format to be converted to SI_CHAR. + * @param a_uInputDataLen Length of storage format data in bytes. This + * must be the actual length of the data, including + * NULL byte if NULL terminated string is required. + * @param a_pOutputData Pointer to the output buffer to received the + * converted data. + * @param a_uOutputDataSize Size of the output buffer in SI_CHAR. + * @return true if all of the input data was successfully + * converted. + */ + bool ConvertFromStore( + const char * a_pInputData, + size_t a_uInputDataLen, + SI_CHAR * a_pOutputData, + size_t a_uOutputDataSize) + { + // ASCII/MBCS/UTF-8 needs no conversion + if (a_uInputDataLen > a_uOutputDataSize) { + return false; + } + memcpy(a_pOutputData, a_pInputData, a_uInputDataLen); + return true; + } + + /** Calculate the number of char required by the storage format of this + * data. The storage format is always UTF-8 or MBCS. + * + * @param a_pInputData NULL terminated string to calculate the number of + * bytes required to be converted to storage format. + * @return Number of bytes required by the string when + * converted to storage format. This size always + * includes space for the terminating NULL character. + * @return -1 cast to size_t on a conversion error. + */ + size_t SizeToStore( + const SI_CHAR * a_pInputData) + { + // ASCII/MBCS/UTF-8 needs no conversion + return strlen((const char *)a_pInputData) + 1; + } + + /** Convert the input string to the storage format of this data. + * The storage format is always UTF-8 or MBCS. + * + * @param a_pInputData NULL terminated source string to convert. All of + * the data will be converted including the + * terminating NULL character. + * @param a_pOutputData Pointer to the buffer to receive the converted + * string. + * @param a_uOutputDataSize Size of the output buffer in char. + * @return true if all of the input data, including the + * terminating NULL character was successfully + * converted. + */ + bool ConvertToStore( + const SI_CHAR * a_pInputData, + char * a_pOutputData, + size_t a_uOutputDataSize) + { + // calc input string length (SI_CHAR type and size independent) + size_t uInputLen = strlen((const char *)a_pInputData) + 1; + if (uInputLen > a_uOutputDataSize) { + return false; + } + + // ascii/UTF-8 needs no conversion + memcpy(a_pOutputData, a_pInputData, uInputLen); + return true; + } +}; + + +// --------------------------------------------------------------------------- +// SI_CONVERT_GENERIC +// --------------------------------------------------------------------------- +#ifdef SI_CONVERT_GENERIC + +#define SI_Case SI_GenericCase +#define SI_NoCase SI_GenericNoCase + +#include +#include "ConvertUTF.h" + +/** + * Converts UTF-8 to a wchar_t (or equivalent) using the Unicode reference + * library functions. This can be used on all platforms. + */ +template +class SI_ConvertW { + bool m_bStoreIsUtf8; +protected: + SI_ConvertW() { } +public: + SI_ConvertW(bool a_bStoreIsUtf8) : m_bStoreIsUtf8(a_bStoreIsUtf8) { } + + /* copy and assignment */ + SI_ConvertW(const SI_ConvertW & rhs) { operator=(rhs); } + SI_ConvertW & operator=(const SI_ConvertW & rhs) { + m_bStoreIsUtf8 = rhs.m_bStoreIsUtf8; + return *this; + } + + /** Calculate the number of SI_CHAR required for converting the input + * from the storage format. The storage format is always UTF-8 or MBCS. + * + * @param a_pInputData Data in storage format to be converted to SI_CHAR. + * @param a_uInputDataLen Length of storage format data in bytes. This + * must be the actual length of the data, including + * NULL byte if NULL terminated string is required. + * @return Number of SI_CHAR required by the string when + * converted. If there are embedded NULL bytes in the + * input data, only the string up and not including + * the NULL byte will be converted. + * @return -1 cast to size_t on a conversion error. + */ + size_t SizeFromStore( + const char * a_pInputData, + size_t a_uInputDataLen) + { + SI_ASSERT(a_uInputDataLen != (size_t) -1); + + if (m_bStoreIsUtf8) { + // worst case scenario for UTF-8 to wchar_t is 1 char -> 1 wchar_t + // so we just return the same number of characters required as for + // the source text. + return a_uInputDataLen; + } + else { + return mbstowcs(NULL, a_pInputData, a_uInputDataLen); + } + } + + /** Convert the input string from the storage format to SI_CHAR. + * The storage format is always UTF-8 or MBCS. + * + * @param a_pInputData Data in storage format to be converted to SI_CHAR. + * @param a_uInputDataLen Length of storage format data in bytes. This + * must be the actual length of the data, including + * NULL byte if NULL terminated string is required. + * @param a_pOutputData Pointer to the output buffer to received the + * converted data. + * @param a_uOutputDataSize Size of the output buffer in SI_CHAR. + * @return true if all of the input data was successfully + * converted. + */ + bool ConvertFromStore( + const char * a_pInputData, + size_t a_uInputDataLen, + SI_CHAR * a_pOutputData, + size_t a_uOutputDataSize) + { + if (m_bStoreIsUtf8) { + // This uses the Unicode reference implementation to do the + // conversion from UTF-8 to wchar_t. The required files are + // ConvertUTF.h and ConvertUTF.c which should be included in + // the distribution but are publically available from unicode.org + // at http://www.unicode.org/Public/PROGRAMS/CVTUTF/ + ConversionResult retval; + const UTF8 * pUtf8 = (const UTF8 *) a_pInputData; + if (sizeof(wchar_t) == sizeof(UTF32)) { + UTF32 * pUtf32 = (UTF32 *) a_pOutputData; + retval = ConvertUTF8toUTF32( + &pUtf8, pUtf8 + a_uInputDataLen, + &pUtf32, pUtf32 + a_uOutputDataSize, + lenientConversion); + } + else if (sizeof(wchar_t) == sizeof(UTF16)) { + UTF16 * pUtf16 = (UTF16 *) a_pOutputData; + retval = ConvertUTF8toUTF16( + &pUtf8, pUtf8 + a_uInputDataLen, + &pUtf16, pUtf16 + a_uOutputDataSize, + lenientConversion); + } + return retval == conversionOK; + } + else { + size_t retval = mbstowcs(a_pOutputData, + a_pInputData, a_uOutputDataSize); + return retval != (size_t)(-1); + } + } + + /** Calculate the number of char required by the storage format of this + * data. The storage format is always UTF-8 or MBCS. + * + * @param a_pInputData NULL terminated string to calculate the number of + * bytes required to be converted to storage format. + * @return Number of bytes required by the string when + * converted to storage format. This size always + * includes space for the terminating NULL character. + * @return -1 cast to size_t on a conversion error. + */ + size_t SizeToStore( + const SI_CHAR * a_pInputData) + { + if (m_bStoreIsUtf8) { + // worst case scenario for wchar_t to UTF-8 is 1 wchar_t -> 6 char + size_t uLen = 0; + while (a_pInputData[uLen]) { + ++uLen; + } + return (6 * uLen) + 1; + } + else { + size_t uLen = wcstombs(NULL, a_pInputData, 0); + if (uLen == (size_t)(-1)) { + return uLen; + } + return uLen + 1; // include NULL terminator + } + } + + /** Convert the input string to the storage format of this data. + * The storage format is always UTF-8 or MBCS. + * + * @param a_pInputData NULL terminated source string to convert. All of + * the data will be converted including the + * terminating NULL character. + * @param a_pOutputData Pointer to the buffer to receive the converted + * string. + * @param a_uOutputDataSize Size of the output buffer in char. + * @return true if all of the input data, including the + * terminating NULL character was successfully + * converted. + */ + bool ConvertToStore( + const SI_CHAR * a_pInputData, + char * a_pOutputData, + size_t a_uOutputDataSize + ) + { + if (m_bStoreIsUtf8) { + // calc input string length (SI_CHAR type and size independent) + size_t uInputLen = 0; + while (a_pInputData[uInputLen]) { + ++uInputLen; + } + ++uInputLen; // include the NULL char + + // This uses the Unicode reference implementation to do the + // conversion from wchar_t to UTF-8. The required files are + // ConvertUTF.h and ConvertUTF.c which should be included in + // the distribution but are publically available from unicode.org + // at http://www.unicode.org/Public/PROGRAMS/CVTUTF/ + ConversionResult retval; + UTF8 * pUtf8 = (UTF8 *) a_pOutputData; + if (sizeof(wchar_t) == sizeof(UTF32)) { + const UTF32 * pUtf32 = (const UTF32 *) a_pInputData; + retval = ConvertUTF32toUTF8( + &pUtf32, pUtf32 + uInputLen, + &pUtf8, pUtf8 + a_uOutputDataSize, + lenientConversion); + } + else if (sizeof(wchar_t) == sizeof(UTF16)) { + const UTF16 * pUtf16 = (const UTF16 *) a_pInputData; + retval = ConvertUTF16toUTF8( + &pUtf16, pUtf16 + uInputLen, + &pUtf8, pUtf8 + a_uOutputDataSize, + lenientConversion); + } + return retval == conversionOK; + } + else { + size_t retval = wcstombs(a_pOutputData, + a_pInputData, a_uOutputDataSize); + return retval != (size_t) -1; + } + } +}; + +#endif // SI_CONVERT_GENERIC + + +// --------------------------------------------------------------------------- +// SI_CONVERT_ICU +// --------------------------------------------------------------------------- +#ifdef SI_CONVERT_ICU + +#define SI_Case SI_GenericCase +#define SI_NoCase SI_GenericNoCase + +#include + +/** + * Converts MBCS/UTF-8 to UChar using ICU. This can be used on all platforms. + */ +template +class SI_ConvertW { + const char * m_pEncoding; + UConverter * m_pConverter; +protected: + SI_ConvertW() : m_pEncoding(NULL), m_pConverter(NULL) { } +public: + SI_ConvertW(bool a_bStoreIsUtf8) : m_pConverter(NULL) { + m_pEncoding = a_bStoreIsUtf8 ? "UTF-8" : NULL; + } + + /* copy and assignment */ + SI_ConvertW(const SI_ConvertW & rhs) { operator=(rhs); } + SI_ConvertW & operator=(const SI_ConvertW & rhs) { + m_pEncoding = rhs.m_pEncoding; + m_pConverter = NULL; + return *this; + } + ~SI_ConvertW() { if (m_pConverter) ucnv_close(m_pConverter); } + + /** Calculate the number of UChar required for converting the input + * from the storage format. The storage format is always UTF-8 or MBCS. + * + * @param a_pInputData Data in storage format to be converted to UChar. + * @param a_uInputDataLen Length of storage format data in bytes. This + * must be the actual length of the data, including + * NULL byte if NULL terminated string is required. + * @return Number of UChar required by the string when + * converted. If there are embedded NULL bytes in the + * input data, only the string up and not including + * the NULL byte will be converted. + * @return -1 cast to size_t on a conversion error. + */ + size_t SizeFromStore( + const char * a_pInputData, + size_t a_uInputDataLen) + { + SI_ASSERT(a_uInputDataLen != (size_t) -1); + + UErrorCode nError; + + if (!m_pConverter) { + nError = U_ZERO_ERROR; + m_pConverter = ucnv_open(m_pEncoding, &nError); + if (U_FAILURE(nError)) { + return (size_t) -1; + } + } + + nError = U_ZERO_ERROR; + ucnv_resetToUnicode(m_pConverter); + int32_t nLen = ucnv_toUChars(m_pConverter, NULL, 0, + a_pInputData, (int32_t) a_uInputDataLen, &nError); + if (nError != U_BUFFER_OVERFLOW_ERROR) { + return (size_t) -1; + } + + return (size_t) nLen; + } + + /** Convert the input string from the storage format to UChar. + * The storage format is always UTF-8 or MBCS. + * + * @param a_pInputData Data in storage format to be converted to UChar. + * @param a_uInputDataLen Length of storage format data in bytes. This + * must be the actual length of the data, including + * NULL byte if NULL terminated string is required. + * @param a_pOutputData Pointer to the output buffer to received the + * converted data. + * @param a_uOutputDataSize Size of the output buffer in UChar. + * @return true if all of the input data was successfully + * converted. + */ + bool ConvertFromStore( + const char * a_pInputData, + size_t a_uInputDataLen, + UChar * a_pOutputData, + size_t a_uOutputDataSize) + { + UErrorCode nError; + + if (!m_pConverter) { + nError = U_ZERO_ERROR; + m_pConverter = ucnv_open(m_pEncoding, &nError); + if (U_FAILURE(nError)) { + return false; + } + } + + nError = U_ZERO_ERROR; + ucnv_resetToUnicode(m_pConverter); + ucnv_toUChars(m_pConverter, + a_pOutputData, (int32_t) a_uOutputDataSize, + a_pInputData, (int32_t) a_uInputDataLen, &nError); + if (U_FAILURE(nError)) { + return false; + } + + return true; + } + + /** Calculate the number of char required by the storage format of this + * data. The storage format is always UTF-8 or MBCS. + * + * @param a_pInputData NULL terminated string to calculate the number of + * bytes required to be converted to storage format. + * @return Number of bytes required by the string when + * converted to storage format. This size always + * includes space for the terminating NULL character. + * @return -1 cast to size_t on a conversion error. + */ + size_t SizeToStore( + const UChar * a_pInputData) + { + UErrorCode nError; + + if (!m_pConverter) { + nError = U_ZERO_ERROR; + m_pConverter = ucnv_open(m_pEncoding, &nError); + if (U_FAILURE(nError)) { + return (size_t) -1; + } + } + + nError = U_ZERO_ERROR; + ucnv_resetFromUnicode(m_pConverter); + int32_t nLen = ucnv_fromUChars(m_pConverter, NULL, 0, + a_pInputData, -1, &nError); + if (nError != U_BUFFER_OVERFLOW_ERROR) { + return (size_t) -1; + } + + return (size_t) nLen + 1; + } + + /** Convert the input string to the storage format of this data. + * The storage format is always UTF-8 or MBCS. + * + * @param a_pInputData NULL terminated source string to convert. All of + * the data will be converted including the + * terminating NULL character. + * @param a_pOutputData Pointer to the buffer to receive the converted + * string. + * @param a_pOutputDataSize Size of the output buffer in char. + * @return true if all of the input data, including the + * terminating NULL character was successfully + * converted. + */ + bool ConvertToStore( + const UChar * a_pInputData, + char * a_pOutputData, + size_t a_uOutputDataSize) + { + UErrorCode nError; + + if (!m_pConverter) { + nError = U_ZERO_ERROR; + m_pConverter = ucnv_open(m_pEncoding, &nError); + if (U_FAILURE(nError)) { + return false; + } + } + + nError = U_ZERO_ERROR; + ucnv_resetFromUnicode(m_pConverter); + ucnv_fromUChars(m_pConverter, + a_pOutputData, (int32_t) a_uOutputDataSize, + a_pInputData, -1, &nError); + if (U_FAILURE(nError)) { + return false; + } + + return true; + } +}; + +#endif // SI_CONVERT_ICU + + +// --------------------------------------------------------------------------- +// SI_CONVERT_WIN32 +// --------------------------------------------------------------------------- +#ifdef SI_CONVERT_WIN32 + +#define SI_Case SI_GenericCase + +// Windows CE doesn't have errno or MBCS libraries +#ifdef _WIN32_WCE +# ifndef SI_NO_MBCS +# define SI_NO_MBCS +# endif +#endif +#ifdef _XBOX +#include +#else +#include +#endif + +#ifdef SI_NO_MBCS +# define SI_NoCase SI_GenericNoCase +#else // !SI_NO_MBCS +/** + * Case-insensitive comparison class using Win32 MBCS functions. This class + * returns a case-insensitive semi-collation order for MBCS text. It may not + * be safe for UTF-8 text returned in char format as we don't know what + * characters will be folded by the function! Therefore, if you are using + * SI_CHAR == char and SetUnicode(true), then you need to use the generic + * SI_NoCase class instead. + */ +#include +template +struct SI_NoCase { + bool operator()(const SI_CHAR * pLeft, const SI_CHAR * pRight) const { + if (sizeof(SI_CHAR) == sizeof(char)) { + return _mbsicmp((const unsigned char *)pLeft, + (const unsigned char *)pRight) < 0; + } + if (sizeof(SI_CHAR) == sizeof(wchar_t)) { + return _wcsicmp((const wchar_t *)pLeft, + (const wchar_t *)pRight) < 0; + } + return SI_GenericNoCase()(pLeft, pRight); + } +}; +#endif // SI_NO_MBCS + +/** + * Converts MBCS and UTF-8 to a wchar_t (or equivalent) on Windows. This uses + * only the Win32 functions and doesn't require the external Unicode UTF-8 + * conversion library. It will not work on Windows 95 without using Microsoft + * Layer for Unicode in your application. + */ +template +class SI_ConvertW { + UINT m_uCodePage; +protected: + SI_ConvertW() { } +public: + SI_ConvertW(bool a_bStoreIsUtf8) { + m_uCodePage = a_bStoreIsUtf8 ? CP_UTF8 : CP_ACP; + } + + /* copy and assignment */ + SI_ConvertW(const SI_ConvertW & rhs) { operator=(rhs); } + SI_ConvertW & operator=(const SI_ConvertW & rhs) { + m_uCodePage = rhs.m_uCodePage; + return *this; + } + + /** Calculate the number of SI_CHAR required for converting the input + * from the storage format. The storage format is always UTF-8 or MBCS. + * + * @param a_pInputData Data in storage format to be converted to SI_CHAR. + * @param a_uInputDataLen Length of storage format data in bytes. This + * must be the actual length of the data, including + * NULL byte if NULL terminated string is required. + * @return Number of SI_CHAR required by the string when + * converted. If there are embedded NULL bytes in the + * input data, only the string up and not including + * the NULL byte will be converted. + * @return -1 cast to size_t on a conversion error. + */ + size_t SizeFromStore( + const char * a_pInputData, + size_t a_uInputDataLen) + { + SI_ASSERT(a_uInputDataLen != (size_t) -1); + + int retval = MultiByteToWideChar( + m_uCodePage, 0, + a_pInputData, (int) a_uInputDataLen, + 0, 0); + return (size_t)(retval > 0 ? retval : -1); + } + + /** Convert the input string from the storage format to SI_CHAR. + * The storage format is always UTF-8 or MBCS. + * + * @param a_pInputData Data in storage format to be converted to SI_CHAR. + * @param a_uInputDataLen Length of storage format data in bytes. This + * must be the actual length of the data, including + * NULL byte if NULL terminated string is required. + * @param a_pOutputData Pointer to the output buffer to received the + * converted data. + * @param a_uOutputDataSize Size of the output buffer in SI_CHAR. + * @return true if all of the input data was successfully + * converted. + */ + bool ConvertFromStore( + const char * a_pInputData, + size_t a_uInputDataLen, + SI_CHAR * a_pOutputData, + size_t a_uOutputDataSize) + { + int nSize = MultiByteToWideChar( + m_uCodePage, 0, + a_pInputData, (int) a_uInputDataLen, + (wchar_t *) a_pOutputData, (int) a_uOutputDataSize); + return (nSize > 0); + } + + /** Calculate the number of char required by the storage format of this + * data. The storage format is always UTF-8. + * + * @param a_pInputData NULL terminated string to calculate the number of + * bytes required to be converted to storage format. + * @return Number of bytes required by the string when + * converted to storage format. This size always + * includes space for the terminating NULL character. + * @return -1 cast to size_t on a conversion error. + */ + size_t SizeToStore( + const SI_CHAR * a_pInputData) + { + int retval = WideCharToMultiByte( + m_uCodePage, 0, + (const wchar_t *) a_pInputData, -1, + 0, 0, 0, 0); + return (size_t) (retval > 0 ? retval : -1); + } + + /** Convert the input string to the storage format of this data. + * The storage format is always UTF-8 or MBCS. + * + * @param a_pInputData NULL terminated source string to convert. All of + * the data will be converted including the + * terminating NULL character. + * @param a_pOutputData Pointer to the buffer to receive the converted + * string. + * @param a_pOutputDataSize Size of the output buffer in char. + * @return true if all of the input data, including the + * terminating NULL character was successfully + * converted. + */ + bool ConvertToStore( + const SI_CHAR * a_pInputData, + char * a_pOutputData, + size_t a_uOutputDataSize) + { + int retval = WideCharToMultiByte( + m_uCodePage, 0, + (const wchar_t *) a_pInputData, -1, + a_pOutputData, (int) a_uOutputDataSize, 0, 0); + return retval > 0; + } +}; + +#endif // SI_CONVERT_WIN32 + + +// --------------------------------------------------------------------------- +// TYPE DEFINITIONS +// --------------------------------------------------------------------------- + +typedef CSimpleIniTempl,SI_ConvertA > CSimpleIniA; +typedef CSimpleIniTempl,SI_ConvertA > CSimpleIniCaseA; + +#if defined(SI_CONVERT_ICU) +typedef CSimpleIniTempl,SI_ConvertW > CSimpleIniW; +typedef CSimpleIniTempl,SI_ConvertW > CSimpleIniCaseW; +#else +typedef CSimpleIniTempl,SI_ConvertW > CSimpleIniW; +typedef CSimpleIniTempl,SI_ConvertW > CSimpleIniCaseW; +#endif + +#ifdef _UNICODE +# define CSimpleIni CSimpleIniW +# define CSimpleIniCase CSimpleIniCaseW +# define SI_NEWLINE SI_NEWLINE_W +#else // !_UNICODE +# define CSimpleIni CSimpleIniA +# define CSimpleIniCase CSimpleIniCaseA +# define SI_NEWLINE SI_NEWLINE_A +#endif // _UNICODE + +#ifdef _MSC_VER +# pragma warning (pop) +#endif + +#endif // INCLUDED_SimpleIni_h + diff --git a/xbox1/RetroLaunch/Surface.cpp b/xbox1/RetroLaunch/Surface.cpp new file mode 100644 index 0000000000..f4ca398534 --- /dev/null +++ b/xbox1/RetroLaunch/Surface.cpp @@ -0,0 +1,255 @@ +/** + * Surreal 64 Launcher (C) 2003 + * + * 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 2 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, write to the Free Software Foundation, Inc., + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. To contact the + * authors: email: buttza@hotmail.com, lantus@lantus-x.com + * + * Additional code and cleanups: Surreal64 CE Team (http://www.emuxtras.net) + */ + +#include "Surface.h" +#include "Debug.h" + +CSurface::CSurface() +{ + m_pTexture = NULL; + m_pVertexBuffer = NULL; + m_byOpacity = 255; + m_byR = 255; + m_byG = 255; + m_byB = 255; + m_bLoaded = false; + m_x = 0; + m_y = 0; +} + +CSurface::CSurface(const string &szFilename) +{ + CSurface(); + Create(szFilename); +} + +CSurface::~CSurface() +{ + Destroy(); +} + +bool CSurface::Create(const string &szFilename) +{ + if (m_bLoaded) + Destroy(); + + g_hResult = D3DXCreateTextureFromFileExA(g_video.m_pD3DDevice, // d3d device + ("D:\\" + szFilename).c_str(), // filename + D3DX_DEFAULT, D3DX_DEFAULT, // width/height + D3DX_DEFAULT, // mipmaps + 0, // usage + D3DFMT_A8R8G8B8, // format + D3DPOOL_MANAGED, // memory class + D3DX_DEFAULT, // texture filter + D3DX_DEFAULT, // mipmapping + 0, // colorkey + &m_imageInfo, // image info + NULL, // pallete + &m_pTexture); // texture + + if (FAILED(g_hResult)) + { + g_debug.Print("Failed: D3DXCreateTextureFromFileExA()\n"); + return false; + } + + // create a vertex buffer for the quad that will display the texture + g_hResult = g_video.m_pD3DDevice->CreateVertexBuffer(4 * sizeof(CustomVertex), + D3DUSAGE_WRITEONLY, + D3DFVF_CUSTOMVERTEX, + D3DPOOL_MANAGED, &m_pVertexBuffer); + if (FAILED(g_hResult)) + { + g_debug.Print("Failed: CreateVertexBuffer()\n"); + m_pTexture->Release(); + return false; + } + + m_bLoaded = true; + + return true; +} + +bool CSurface::Create(dword width, dword height) +{ + if (m_bLoaded) + Destroy(); + + g_hResult = g_video.m_pD3DDevice->CreateTexture(width, height, 1, 0, + D3DFMT_A8R8G8B8, D3DPOOL_MANAGED, + &m_pTexture); + + if (FAILED(g_hResult)) + { + g_debug.Print("Failed: CreateTexture()\n"); + return false; + } + + m_imageInfo.Width = width; + m_imageInfo.Height = height; + m_imageInfo.Format = D3DFMT_A8R8G8B8; + + // create a vertex buffer for the quad that will display the texture + g_hResult = g_video.m_pD3DDevice->CreateVertexBuffer(4 * sizeof(CustomVertex), + D3DUSAGE_WRITEONLY, + D3DFVF_CUSTOMVERTEX, + D3DPOOL_MANAGED, &m_pVertexBuffer); + if (FAILED(g_hResult)) + { + g_debug.Print("Failed: CreateVertexBuffer()\n"); + m_pTexture->Release(); + return false; + } + + m_bLoaded = true; + + return true; +} + +void CSurface::Destroy() +{ + // free the vertex buffer + if (m_pVertexBuffer) + { + m_pVertexBuffer->Release(); + m_pVertexBuffer = NULL; + } + + // free the texture + if (m_pTexture) + { + m_pTexture->Release(); + m_pTexture = NULL; + } + + m_bLoaded = false; +} + +bool CSurface::IsLoaded() +{ + return m_bLoaded; +} + +bool CSurface::Render() +{ + return Render(m_x, m_y); +} + +bool CSurface::Render(int x, int y) +{ + return Render(x, y, m_imageInfo.Width, m_imageInfo.Height); +} + +bool CSurface::Render(int x, int y, dword w, dword h) +{ + if (m_pTexture == NULL || m_pVertexBuffer == NULL || m_bLoaded == false) + return false; + + float fX = static_cast(x); + float fY = static_cast(y); + + // create the new vertices + CustomVertex newVerts[] = + { + // x, y, z, color, u ,v + {fX, fY, 0.0f, D3DCOLOR_ARGB(m_byOpacity, m_byR, m_byG, m_byB), 0, 0}, + {fX + w, fY, 0.0f, D3DCOLOR_ARGB(m_byOpacity, m_byR, m_byG, m_byB), 1, 0}, + {fX + w, fY + h, 0.0f, D3DCOLOR_ARGB(m_byOpacity, m_byR, m_byG, m_byB), 1, 1}, + {fX, fY + h, 0.0f, D3DCOLOR_ARGB(m_byOpacity, m_byR, m_byG, m_byB), 0, 1} + }; + + // load the existing vertices + CustomVertex *pCurVerts; + + g_hResult = m_pVertexBuffer->Lock(0, 0, (byte **)&pCurVerts, 0); + + if (FAILED(g_hResult)) + { + g_debug.Print("Failed: m_pVertexBuffer->Lock()\n"); + return false; + } + // copy the new verts over the old verts + memcpy(pCurVerts, newVerts, 4 * sizeof(CustomVertex)); + + m_pVertexBuffer->Unlock(); + + + g_video.m_pD3DDevice->SetRenderState(D3DRS_ALPHABLENDENABLE, TRUE); + g_video.m_pD3DDevice->SetRenderState(D3DRS_SRCBLEND, D3DBLEND_SRCALPHA); + g_video.m_pD3DDevice->SetRenderState(D3DRS_DESTBLEND, D3DBLEND_INVSRCALPHA); + + // also blend the texture with the set alpha value + g_video.m_pD3DDevice->SetTextureStageState(0, D3DTSS_ALPHAOP, D3DTOP_MODULATE); + g_video.m_pD3DDevice->SetTextureStageState(0, D3DTSS_ALPHAARG1, D3DTA_DIFFUSE); + g_video.m_pD3DDevice->SetTextureStageState(0, D3DTSS_ALPHAARG2, D3DTA_TEXTURE); + + // draw the quad + g_video.m_pD3DDevice->SetTexture(0, m_pTexture); + g_video.m_pD3DDevice->SetStreamSource(0, m_pVertexBuffer, sizeof(CustomVertex)); + g_video.m_pD3DDevice->SetVertexShader(D3DFVF_CUSTOMVERTEX); +#ifdef _XBOX + g_video.m_pD3DDevice->DrawPrimitive(D3DPT_QUADLIST, 0, 1); +#else + //FIXME: vertices order ! + g_video.m_pD3DDevice->DrawPrimitive(D3DPT_TRIANGLELIST, 0, 2); +#endif + return true; +} + +void CSurface::SetOpacity(byte opacity) +{ + m_byOpacity = opacity; +} + +void CSurface::SetTint(byte r, byte g, byte b) +{ + m_byR = r; + m_byG = g; + m_byB = b; +} + +void CSurface::MoveTo(int x, int y) +{ + m_x = x; + m_y = y; +} + +dword CSurface::GetWidth() +{ + if (m_pTexture == NULL || m_pVertexBuffer == NULL) + return 0; + + return m_imageInfo.Width; +} + +dword CSurface::GetHeight() +{ + if (m_pTexture == NULL || m_pVertexBuffer == NULL) + return 0; + + return m_imageInfo.Height; +} + +byte CSurface::GetOpacity() +{ + return m_byOpacity; +} + +IDirect3DTexture8 *CSurface::GetTexture() +{ + return m_pTexture; +} diff --git a/xbox1/RetroLaunch/Surface.h b/xbox1/RetroLaunch/Surface.h new file mode 100644 index 0000000000..752ee12f93 --- /dev/null +++ b/xbox1/RetroLaunch/Surface.h @@ -0,0 +1,90 @@ +/** + * Surreal 64 Launcher (C) 2003 + * + * 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 2 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, write to the Free Software Foundation, Inc., + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. To contact the + * authors: email: buttza@hotmail.com, lantus@lantus-x.com + * + * Additional code and cleanups: Surreal64 CE Team (http://www.emuxtras.net) + */ + +#pragma once + +#include "Global.h" +#include "Video.h" + +class CSurface +{ +public: + CSurface(); + CSurface(const string &szFilename); + ~CSurface(); + + /** + * Do functions + */ + bool Create(const string &szFilename); + bool Create(dword width, dword height); + void Destroy(); + + bool IsLoaded(); + + bool Render(); + bool Render(int x, int y); + bool Render(int x, int y, dword w, dword h); + + /** + * Set functions + */ + void SetOpacity(byte opacity); + void SetTint(byte r, byte g, byte b); + + void MoveTo(int x, int y); + + /** + * Get functions + */ + dword GetWidth(); + dword GetHeight(); + + byte GetOpacity(); + + IDirect3DTexture8 *GetTexture(); + +private: + /** + * A d3d texture object that will contain the loaded texture + * and a d3d vertex buffer object that will contain the vertex + * buffer for the quad which will display the texture + */ + IDirect3DTexture8 *m_pTexture; + IDirect3DVertexBuffer8 *m_pVertexBuffer; + + /** + * The default render position of the texture + */ + int m_x, m_y; + + /** + * The width and height of the texture + */ + D3DXIMAGE_INFO m_imageInfo; + + /** + * The opacity of the texture + */ + byte m_byOpacity; + byte m_byR, m_byG, m_byB; + + /** + * Whether the texture has been created or not + */ + bool m_bLoaded; +}; diff --git a/xbox1/RetroLaunch/Undocumented.h b/xbox1/RetroLaunch/Undocumented.h new file mode 100644 index 0000000000..927280241d --- /dev/null +++ b/xbox1/RetroLaunch/Undocumented.h @@ -0,0 +1,1373 @@ +// Thanks and credit to everlame, Team Evox, and Woodoo. +// +// This file contains declarations for accessing the internal NT API. +// Some calls have changed from NT, so be careful! +// +// For the most part, all NT calls use ANSI instead of Unicode strings now. +// +// It is not known which flags work. You will have to experiment. + +#ifndef __XBOX_INTERNAL_H__ +#define __XBOX_INTERNAL_H__ + +#include + +// Do extern "C" for C++ +#if defined(__cplusplus) && !defined(XBOXINTERNAL_NO_EXTERN_C) +extern "C" { +#endif // __cplusplus + + +// Don't do __declspec(dllimport) for things like emulators +#if defined(NTSYSAPI) && defined(DONT_IMPORT_INTERNAL) +#undef NTSYSAPI +#endif +#ifdef DONT_IMPORT_INTERNAL +#define NTSYSAPI +#endif + +// The normal headers don't have this...? +#define FASTCALL __fastcall + +// The usual NTSTATUS +typedef LONG NTSTATUS; + +// The usual NT_SUCCESS +#define NT_SUCCESS(Status) ((NTSTATUS)(Status) >= 0) + +// Just for documentation +#define EXPORTNUM(x) + + +// Needed for object structures and related things +typedef CONST SHORT CSHORT; + + +// String types +typedef CHAR *PSZ; +typedef CONST CHAR *PCSZ; + +// ANSI_STRING +// Differences from NT: None. +typedef struct _STRING { + USHORT Length; + USHORT MaximumLength; + PCHAR Buffer; +} STRING; +typedef STRING *PSTRING; + +typedef STRING ANSI_STRING; +typedef PSTRING PANSI_STRING; + + +// IO Status Block type (UNVERIFIED) +// Differences from NT: None. +typedef struct _IO_STATUS_BLOCK { + union { + NTSTATUS Status; + PVOID Pointer; + }; + + ULONG_PTR Information; +} IO_STATUS_BLOCK, *PIO_STATUS_BLOCK; + +// APC routine +typedef +VOID +(NTAPI *PIO_APC_ROUTINE) ( + IN PVOID ApcContext, + IN PIO_STATUS_BLOCK IoStatusBlock, + IN ULONG Reserved + ); + + +// Header for dispatcher objects +// Differences from NT: None. +typedef struct _DISPATCHER_HEADER { + UCHAR Type; + UCHAR Absolute; + UCHAR Size; + UCHAR Inserted; + LONG SignalState; + LIST_ENTRY WaitListHead; +} DISPATCHER_HEADER; + + +// Object types +#define NotificationTimerObject 8 +#define SynchronizationTimerObject 9 +#define DpcObject 19 + + +// Object Attributes type +// Differences from NT: There are no Length, SecurityDescriptor, or +// SecurityQualityOfService fields. Also, ObjectName is ANSI, not +// Unicode. +typedef struct _OBJECT_ATTRIBUTES { + HANDLE RootDirectory; + PANSI_STRING ObjectName; + ULONG Attributes; +} OBJECT_ATTRIBUTES; +typedef OBJECT_ATTRIBUTES *POBJECT_ATTRIBUTES; + +// Flags for OBJECT_ATTRIBUTES::Attributes +#define OBJ_INHERIT 0x00000002L +#define OBJ_PERMANENT 0x00000010L +#define OBJ_EXCLUSIVE 0x00000020L +#define OBJ_CASE_INSENSITIVE 0x00000040L +#define OBJ_OPENIF 0x00000080L +#define OBJ_OPENLINK 0x00000100L +#define OBJ_KERNEL_HANDLE 0x00000200L +#define OBJ_VALID_ATTRIBUTES 0x000003F2L + +// Initializes an OBJECT_ATTRIBUTES. +// Works as if it were this function: +// VOID +// InitializeObjectAttributes( +// OUT POBJECT_ATTRIBUTES p, +// IN PANSI_STRING n, +// IN ULONG a, +// IN HANDLE r +// ) +// Differences from NT: SECURITY_DESCRIPTOR support is gone. +#define InitializeObjectAttributes( p, n, a, r ) { \ + (p)->RootDirectory = r; \ + (p)->Attributes = a; \ + (p)->ObjectName = n; \ + } + + +// CreateDisposition values for NtCreateFile() +#define FILE_SUPERSEDE 0x00000000 +#define FILE_OPEN 0x00000001 +#define FILE_CREATE 0x00000002 +#define FILE_OPEN_IF 0x00000003 +#define FILE_OVERWRITE 0x00000004 +#define FILE_OVERWRITE_IF 0x00000005 +#define FILE_MAXIMUM_DISPOSITION 0x00000005 + +// CreateOption values for NtCreateFile() +// FILE_NON_DIRECTORY_FILE | FILE_SYNCHRONOUS_IO_NONALERT is what CreateFile +// uses for most things when translating to NtCreateFile. +#define FILE_DIRECTORY_FILE 0x00000001 +#define FILE_WRITE_THROUGH 0x00000002 +#define FILE_SEQUENTIAL_ONLY 0x00000004 +#define FILE_NO_INTERMEDIATE_BUFFERING 0x00000008 +#define FILE_SYNCHRONOUS_IO_ALERT 0x00000010 +#define FILE_SYNCHRONOUS_IO_NONALERT 0x00000020 +#define FILE_NON_DIRECTORY_FILE 0x00000040 +#define FILE_CREATE_TREE_CONNECTION 0x00000080 +#define FILE_COMPLETE_IF_OPLOCKED 0x00000100 +#define FILE_NO_EA_KNOWLEDGE 0x00000200 +#define FILE_OPEN_FOR_RECOVERY 0x00000400 +#define FILE_RANDOM_ACCESS 0x00000800 +#define FILE_DELETE_ON_CLOSE 0x00001000 +#define FILE_OPEN_BY_FILE_ID 0x00002000 +#define FILE_OPEN_FOR_BACKUP_INTENT 0x00004000 +#define FILE_NO_COMPRESSION 0x00008000 +#define FILE_RESERVE_OPFILTER 0x00100000 +#define FILE_OPEN_REPARSE_POINT 0x00200000 +#define FILE_OPEN_NO_RECALL 0x00400000 +#define FILE_OPEN_FOR_FREE_SPACE_QUERY 0x00800000 +#define FILE_COPY_STRUCTURED_STORAGE 0x00000041 +#define FILE_STRUCTURED_STORAGE 0x00000441 +#define FILE_VALID_OPTION_FLAGS 0x00ffffff +#define FILE_VALID_PIPE_OPTION_FLAGS 0x00000032 +#define FILE_VALID_MAILSLOT_OPTION_FLAGS 0x00000032 +#define FILE_VALID_SET_FLAGS 0x00000036 + + +// NtQueryVolumeInformation / NtSetVolumeInformation stuff +// Type of information to retrieve; FileFsSizeInformation and +// FileFsDeviceInformation are the only ones confirmed to work. +typedef enum _FSINFOCLASS { + FileFsVolumeInformation = 1, + FileFsLabelInformation, + FileFsSizeInformation, + FileFsDeviceInformation, + FileFsAttributeInformation, + FileFsControlInformation, + FileFsFullSizeInformation, + FileFsObjectInformation +} FS_INFORMATION_CLASS, *PFS_INFORMATION_CLASS; + +// Structure of FileFsSizeInformation +typedef struct _FILE_FS_SIZE_INFORMATION { + LARGE_INTEGER TotalAllocationUnits; + LARGE_INTEGER AvailableAllocationUnits; + ULONG SectorsPerAllocationUnit; + ULONG BytesPerSector; +} FILE_FS_SIZE_INFORMATION, *PFILE_FS_SIZE_INFORMATION; + +#define DEVICE_TYPE ULONG + +// Structure of FileFsDeviceInformation +typedef struct _FILE_FS_DEVICE_INFORMATION { + DEVICE_TYPE DeviceType; + ULONG Characteristics; +} FILE_FS_DEVICE_INFORMATION, *PFILE_FS_DEVICE_INFORMATION; + +// DEVICE_TYPEs (I took a guess as to which the XBOX might have.) +#define FILE_DEVICE_CD_ROM 0x00000002 +#define FILE_DEVICE_CD_ROM_FILE_SYSTEM 0x00000003 +#define FILE_DEVICE_CONTROLLER 0x00000004 +#define FILE_DEVICE_DISK 0x00000007 +#define FILE_DEVICE_DISK_FILE_SYSTEM 0x00000008 +#define FILE_DEVICE_FILE_SYSTEM 0x00000009 +#define FILE_DEVICE_NULL 0x00000015 +#define FILE_DEVICE_SCREEN 0x0000001c +#define FILE_DEVICE_SOUND 0x0000001d +#define FILE_DEVICE_UNKNOWN 0x00000022 +#define FILE_DEVICE_VIDEO 0x00000023 +#define FILE_DEVICE_VIRTUAL_DISK 0x00000024 +#define FILE_DEVICE_FULLSCREEN_VIDEO 0x00000034 + +// Characteristics +#define FILE_REMOVABLE_MEDIA 0x00000001 +#define FILE_READ_ONLY_DEVICE 0x00000002 +#define FILE_FLOPPY_DISKETTE 0x00000004 +#define FILE_WRITE_ONCE_MEDIA 0x00000008 +#define FILE_REMOTE_DEVICE 0x00000010 +#define FILE_DEVICE_IS_MOUNTED 0x00000020 +#define FILE_VIRTUAL_VOLUME 0x00000040 +#define FILE_AUTOGENERATED_DEVICE_NAME 0x00000080 +#define FILE_DEVICE_SECURE_OPEN 0x00000100 + + + +// Physical address +// Differences from NT: 32 bit address instead of 64. +typedef ULONG PHYSICAL_ADDRESS, *PPHYSICAL_ADDRESS; + + +// NtCreateFile/NtOpenFile stuff +#define FILE_SUPERSEDED 0x00000000 +#define FILE_OPENED 0x00000001 +#define FILE_CREATED 0x00000002 +#define FILE_OVERWRITTEN 0x00000003 +#define FILE_EXISTS 0x00000004 +#define FILE_DOES_NOT_EXIST 0x00000005 + +// NtReadFile/NtWriteFile stuff +#define FILE_WRITE_TO_END_OF_FILE 0xffffffff +#define FILE_USE_FILE_POINTER_POSITION 0xfffffffe + + + +// DeviceIoControl stuff + +// Device types +#define FILE_DEVICE_CD_ROM 0x00000002 +#define FILE_DEVICE_CD_ROM_FILE_SYSTEM 0x00000003 +#define FILE_DEVICE_CONTROLLER 0x00000004 +#define FILE_DEVICE_SCSI FILE_DEVICE_CONTROLLER +#define IOCTL_SCSI_BASE FILE_DEVICE_CONTROLLER +#define FILE_DEVICE_DISK 0x00000007 +#define FILE_DEVICE_DISK_FILE_SYSTEM 0x00000008 +#define FILE_DEVICE_DVD 0x00000033 + +// Access types +#define FILE_ANY_ACCESS 0 +#define FILE_READ_ACCESS ( 0x0001 ) // file & pipe +#define FILE_WRITE_ACCESS ( 0x0002 ) // file & pipe + +// Method types +#define METHOD_BUFFERED 0 +#define METHOD_IN_DIRECT 1 +#define METHOD_OUT_DIRECT 2 +#define METHOD_NEITHER 3 + +// The all-important CTL_CODE +#define CTL_CODE( DeviceType, Function, Method, Access ) ( \ + ((DeviceType) << 16) | ((Access) << 14) | ((Function) << 2) | (Method) \ +) + +// IDE/SCSI codes +// IOCTL_SCSI_PASS_THROUGH_DIRECT is the only one known to be used. +// Differences from NT: None. +#define IOCTL_SCSI_PASS_THROUGH CTL_CODE(IOCTL_SCSI_BASE, 0x0401, METHOD_BUFFERED, FILE_READ_ACCESS | FILE_WRITE_ACCESS) +#define IOCTL_SCSI_MINIPORT CTL_CODE(IOCTL_SCSI_BASE, 0x0402, METHOD_BUFFERED, FILE_READ_ACCESS | FILE_WRITE_ACCESS) +#define IOCTL_SCSI_GET_INQUIRY_DATA CTL_CODE(IOCTL_SCSI_BASE, 0x0403, METHOD_BUFFERED, FILE_ANY_ACCESS) +#define IOCTL_SCSI_GET_CAPABILITIES CTL_CODE(IOCTL_SCSI_BASE, 0x0404, METHOD_BUFFERED, FILE_ANY_ACCESS) +#define IOCTL_SCSI_PASS_THROUGH_DIRECT CTL_CODE(IOCTL_SCSI_BASE, 0x0405, METHOD_BUFFERED, FILE_READ_ACCESS | FILE_WRITE_ACCESS) +#define IOCTL_SCSI_GET_ADDRESS CTL_CODE(IOCTL_SCSI_BASE, 0x0406, METHOD_BUFFERED, FILE_ANY_ACCESS) +#define IOCTL_SCSI_RESCAN_BUS CTL_CODE(IOCTL_SCSI_BASE, 0x0407, METHOD_BUFFERED, FILE_ANY_ACCESS) +#define IOCTL_SCSI_GET_DUMP_POINTERS CTL_CODE(IOCTL_SCSI_BASE, 0x0408, METHOD_BUFFERED, FILE_ANY_ACCESS) +#define IOCTL_SCSI_FREE_DUMP_POINTERS CTL_CODE(IOCTL_SCSI_BASE, 0x0409, METHOD_BUFFERED, FILE_ANY_ACCESS) +#define IOCTL_IDE_PASS_THROUGH CTL_CODE(IOCTL_SCSI_BASE, 0x040a, METHOD_BUFFERED, FILE_READ_ACCESS | FILE_WRITE_ACCESS) + +// Special XBOX code? +#define IOCTL_CDROM_AUTHENTICATE_DISK CTL_CODE(FILE_DEVICE_CD_ROM, 0x0020, METHOD_BUFFERED, FILE_READ_ACCESS) + +// Structure for IOCTL_SCSI_PASS_THROUGH_DIRECT +// Differences from NT: None, believe it or not. +typedef struct _SCSI_PASS_THROUGH_DIRECT { + /*000*/ USHORT Length; + /*002*/ UCHAR ScsiStatus; + /*003*/ UCHAR PathId; + /*004*/ UCHAR TargetId; + /*005*/ UCHAR Lun; + /*006*/ UCHAR CdbLength; + /*007*/ UCHAR SenseInfoLength; + /*008*/ UCHAR DataIn; + /*00C*/ ULONG DataTransferLength; + /*010*/ ULONG TimeOutValue; + /*014*/ PVOID DataBuffer; + /*018*/ ULONG SenseInfoOffset; + /*01C*/ UCHAR Cdb[16]; +}SCSI_PASS_THROUGH_DIRECT, *PSCSI_PASS_THROUGH_DIRECT; + +// DataIn fields for IOCTL_SCSI_PASS_THROUGH_DIRECT +#define SCSI_IOCTL_DATA_OUT 0 +#define SCSI_IOCTL_DATA_IN 1 +#define SCSI_IOCTL_DATA_UNSPECIFIED 2 + +// Kernel object type (unsure about the structure...) +typedef struct _OBJECT_TYPE +{ + // Same prototype as ExAllocatePoolWithTag, because that's the usual one + PVOID + (NTAPI *AllocationFunction)( + SIZE_T NumberOfBytes, + ULONG Tag + ); + + // Same prototype as ExFreePool, because that's the usual one + VOID + (NTAPI *FreeFunction)( + IN PVOID P + ); + + // The prototypes of these are unknown + void *CloseFunction; + void *DeleteFunction; + void *ParseFunction; + + // Unknown DWORD... Size of this object type maybe? + void *DefaultObjectMaybe; + + // 4 letter tag for this object type + CHAR Tag[4]; +} OBJECT_TYPE; +typedef OBJECT_TYPE *POBJECT_TYPE; + +// Object types +extern POBJECT_TYPE IoFileObjectType; +extern POBJECT_TYPE ExEventObjectType; +extern POBJECT_TYPE ExSemaphoreObjectType; +extern POBJECT_TYPE IoCompletionObjectType; +extern POBJECT_TYPE IoDeviceObjectType; + + +// *_OBJECT and related structures (mostly opaque since I'm lazy) +typedef struct _DRIVER_OBJECT { + CSHORT Type; + CSHORT Size; + struct _DEVICE_OBJECT *DeviceObject; + // ... +} DRIVER_OBJECT; +typedef DRIVER_OBJECT *PDRIVER_OBJECT; + +typedef struct _DEVICE_OBJECT { + CSHORT Type; + USHORT Size; + LONG ReferenceCount; + PDRIVER_OBJECT DriverObject; + // ... +} DEVICE_OBJECT; +typedef DEVICE_OBJECT *PDEVICE_OBJECT; + +typedef struct _FILE_OBJECT { + CSHORT Type; + CSHORT Size; + PDEVICE_OBJECT DeviceObject; + // ... +} FILE_OBJECT; +typedef FILE_OBJECT *PFILE_OBJECT; + + +// Thread information structures + +// IRQL +typedef UCHAR KIRQL, *PKIRQL; +#define PASSIVE_LEVEL 0 // Passive release level +#define LOW_LEVEL 0 // Lowest interrupt level +#define APC_LEVEL 1 // APC interrupt level +#define DISPATCH_LEVEL 2 // Dispatcher level + +// Thread entry point +// NOTE: This is not a standard call! You can't call this function from C code! +// You push registers like stdcall, but ebp + 4 must point to the first argument before the call! +// +// Differences from NT: 2 parameters instead of 1; strange calling convention +typedef +VOID +(NTAPI *PKSTART_ROUTINE) ( + IN PVOID StartContext1, + IN PVOID StartContext2 + ); + +// Structure of a critical section +// Same as the XBOX's RTL_CRITICAL_SECTION, but with the more explicit header +typedef struct _KCRITICAL_SECTION +{ + // 000 Dispatcher header + DISPATCHER_HEADER Header; + // 010 Lock count of the critical section + LONG LockCount; + // 014 Recursion count of the critical section + LONG RecursionCount; + // 018 Thread ID of the thread that currently owns this critical section + ULONG OwningThread; +} KCRITICAL_SECTION, *PKCRITICAL_SECTION; + +// Structure of a thread object +typedef struct _KTHREAD +{ + // 000 Dispatcher header + DISPATCHER_HEADER Header; + // 010 Unknown + BYTE unknown[0x18]; + // 028 Pointer to TLS data + PVOID TlsData; + // ??? just padding - real size is unknown + BYTE unknown2[0x100]; +} KTHREAD, *PKTHREAD; + +// Structure of the data at FS +typedef struct _FS_STRUCTURE +{ + // 000 Current exception handler information + PVOID *ExceptionFrame; + // 004 Pointer to current TLS data top + PVOID TlsDataTop; + // 008 + BYTE unknown2[0x1C]; + // 024 Current IRQL of the OS + KIRQL CurrentIrql; + // 028 Thread structure of the current thread + PKTHREAD ThreadObject; + // ??? just padding - real size is unknown + BYTE unknown3[0x100]; +} FS_STRUCTURE, *PFS_STRUCTURE; + +// DPC routine +typedef +VOID +(*PKDEFERRED_ROUTINE) ( + IN struct _KDPC *Dpc, + IN PVOID DeferredContext, + IN PVOID SystemArgument1, + IN PVOID SystemArgument2 + ); + +// DPC information +// It's not known which of these fields are used on XBOX. +typedef struct _KDPC { + CSHORT Type; + UCHAR Number; + UCHAR Importance; + LIST_ENTRY DpcListEntry; + PKDEFERRED_ROUTINE DeferredRoutine; + PVOID DeferredContext; + PVOID SystemArgument1; + PVOID SystemArgument2; + PULONG_PTR Lock; +} KDPC, *PKDPC; + + +// Timers +typedef enum _TIMER_TYPE { + NotificationTimer, + SynchronizationTimer + } TIMER_TYPE; + +typedef struct _KTIMER { + DISPATCHER_HEADER Header; + ULARGE_INTEGER DueTime; + LIST_ENTRY TimerListEntry; + struct _KDPC *Dpc; + LONG Period; +} KTIMER, *PKTIMER; + + + +// XBE stuff +// Not used in any exported kernel calls, but still useful. + + +// XBE header information +typedef struct _XBE_HEADER { + // 000 "XBEH" + CHAR Magic[4]; + // 004 RSA digital signature of the entire header area + UCHAR HeaderSignature[256]; + // 104 Base address of XBE image (must be 0x00010000?) + PVOID BaseAddress; + // 108 Size of all headers combined - other headers must be within this + ULONG HeaderSize; + // 10C Size of entire image + ULONG ImageSize; + // 110 Size of this header (always 0x178?) + ULONG XbeHeaderSize; + // 114 Image timestamp - unknown format + ULONG Timestamp; + // 118 Pointer to certificate data (must be within HeaderSize) + struct _XBE_CERTIFICATE *Certificate; + // 11C Number of sections + DWORD NumSections; + // 120 Pointer to section headers (must be within HeaderSize) + struct _XBE_SECTION *Sections; + // 124 Initialization flags + ULONG InitFlags; + // 128 Entry point (XOR'd; see xboxhacker.net) + PVOID EntryPoint; + // 12C Pointer to TLS directory + struct _XBE_TLS_DIRECTORY *TlsDirectory; + // 130 Stack commit size + ULONG StackCommit; + // 134 Heap reserve size + ULONG HeapReserve; + // 138 Heap commit size + ULONG HeapCommit; + // 13C PE base address (?) + PVOID PeBaseAddress; + // 140 PE image size (?) + ULONG PeImageSize; + // 144 PE checksum (?) + ULONG PeChecksum; + // 148 PE timestamp (?) + ULONG PeTimestamp; + // 14C PC path and filename to EXE file from which XBE is derived + PCSZ PcExePath; + // 150 PC filename (last part of PcExePath) from which XBE is derived + PCSZ PcExeFilename; + // 154 PC filename (Unicode version of PcExeFilename) + PWSTR PcExeFilenameUnicode; + // 158 Pointer to kernel thunk table (XOR'd; EFB1F152 debug) + ULONG_PTR *KernelThunkTable; + // 15C Non-kernel import table (debug only) + PVOID DebugImportTable; + // 160 Number of library headers + ULONG NumLibraries; + // 164 Pointer to library headers + struct _XBE_LIBRARY *Libraries; + // 168 Pointer to kernel library header + struct _XBE_LIBRARY *KernelLibrary; + // 16C Pointer to XAPI library + struct _XBE_LIBRARY *XapiLibrary; + // 170 Pointer to logo bitmap (NULL = use default of Microsoft) + PVOID LogoBitmap; + // 174 Size of logo bitmap + ULONG LogoBitmapSize; + // 178 +} XBE_HEADER, *PXBE_HEADER; + +// Certificate structure +typedef struct _XBE_CERTIFICATE { + // 000 Size of certificate + ULONG Size; + // 004 Certificate timestamp (unknown format) + ULONG Timestamp; + // 008 Title ID + ULONG TitleId; + // 00C Name of the game (Unicode) + WCHAR TitleName[40]; + // 05C Alternate title ID's (0-terminated) + ULONG AlternateTitleIds[16]; + // 09C Allowed media types - 1 bit match between XBE and media = boots + ULONG MediaTypes; + // 0A0 Allowed game regions - 1 bit match between this and XBOX = boots + ULONG GameRegion; + // 0A4 Allowed game ratings - 1 bit match between this and XBOX = boots + ULONG GameRating; + // 0A8 Disk number (?) + ULONG DiskNumber; + // 0AC Version (?) + ULONG Version; + // 0B0 LAN key for this game + UCHAR LanKey[16]; + // 0C0 Signature key for this game + UCHAR SignatureKey[16]; + // 0D0 Signature keys for the alternate title ID's + UCHAR AlternateSignatureKeys[16][16]; + // 1D0 +} XBE_CERTIFICATE, *PXBE_CERTIFICATE; + +// Section headers +typedef struct _XBE_SECTION { + // 000 Flags + ULONG Flags; + // 004 Virtual address (where this section loads in RAM) + PVOID VirtualAddress; + // 008 Virtual size (size of section in RAM; after FileSize it's 00'd) + ULONG VirtualSize; + // 00C File address (where in the file from which this section comes) + ULONG FileAddress; + // 010 File size (size of the section in the XBE file) + ULONG FileSize; + // 014 Pointer to section name + PCSZ SectionName; + // 018 Section reference count - when >= 1, section is loaded + LONG SectionReferenceCount; + // 01C Pointer to head shared page reference count + WORD *HeadReferenceCount; + // 020 Pointer to tail shared page reference count + WORD *TailReferenceCount; + // 024 SHA hash. Hash DWORD containing FileSize, then hash section. + DWORD ShaHash[5]; + // 038 +} XBE_SECTION, *PXBE_SECTION; + +// TLS directory information needed later +// Library version data needed later + +// Initialization flags +#define XBE_INIT_MOUNT_UTILITY 0x00000001 +#define XBE_INIT_FORMAT_UTILITY 0x00000002 +#define XBE_INIT_64M_RAM_ONLY 0x00000004 +#define XBE_INIT_DONT_SETUP_HDD 0x00000008 + +// Region codes +#define XBE_REGION_US_CANADA 0x00000001 +#define XBE_REGION_JAPAN 0x00000002 +#define XBE_REGION_ELSEWHERE 0x00000004 +#define XBE_REGION_DEBUG 0x80000000 + +// Media types +#define XBE_MEDIA_HDD 0x00000001 +#define XBE_MEDIA_XBOX_DVD 0x00000002 +#define XBE_MEDIA_ANY_CD_OR_DVD 0x00000004 +#define XBE_MEDIA_CD 0x00000008 +#define XBE_MEDIA_1LAYER_DVDROM 0x00000010 +#define XBE_MEDIA_2LAYER_DVDROM 0x00000020 +#define XBE_MEDIA_1LAYER_DVDR 0x00000040 +#define XBE_MEDIA_2LAYER_DVDR 0x00000080 +#define XBE_MEDIA_USB 0x00000100 +#define XBE_MEDIA_ALLOW_UNLOCKED_HDD 0x40000000 + +// Section flags +#define XBE_SEC_WRITABLE 0x00000001 +#define XBE_SEC_PRELOAD 0x00000002 +#define XBE_SEC_EXECUTABLE 0x00000004 +#define XBE_SEC_INSERTED_FILE 0x00000008 +#define XBE_SEC_RO_HEAD_PAGE 0x00000010 +#define XBE_SEC_RO_TAIL_PAGE 0x00000020 + + +// x86 page size +#define PAGE_SIZE 0x1000 + + +// Native NT API calls on the XBOX + + +// PAGE_ALIGN: +// Returns an address rounded down to the nearest page boundary. +// +// Differences from NT: None. +#define PAGE_ALIGN(Va) ((PVOID)((ULONG_PTR)(Va) & ~(PAGE_SIZE - 1))) + + +// RtlInitAnsiString: +// Fills an ANSI_STRING structure to use the specified string. +// +// Differences from NT: None. +NTSYSAPI +EXPORTNUM(289) +VOID +NTAPI +RtlInitAnsiString( + OUT PANSI_STRING DestinationString, + IN PCSZ SourceString + ); + + +// NtCreateFile: +// Creates or opens a file or device object. +// +// Differences from NT: The EaBuffer and EaLength options are gone. +// OBJECT_ATTRIBUTES uses ANSI_STRING, so only ANSI filenames work. +NTSYSAPI +EXPORTNUM(190) +NTSTATUS +NTAPI +NtCreateFile( + OUT PHANDLE FileHandle, + IN ACCESS_MASK DesiredAccess, + IN POBJECT_ATTRIBUTES ObjectAttributes, + OUT PIO_STATUS_BLOCK IoStatusBlock, + IN PLARGE_INTEGER AllocationSize OPTIONAL, + IN ULONG FileAttributes, + IN ULONG ShareAccess, + IN ULONG CreateDisposition, + IN ULONG CreateOptions + ); + +// NtOpenFile: +// Opens a file or device object. Same as calling: +// NtCreateFile(FileHandle, DesiredAccess, ObjectAttributes, +// IoStatusBlock, NULL, 0, ShareAccess, OPEN_EXISTING, OpenOptions); +// +// Differences from NT: See NtCreateFile. +NTSYSAPI +EXPORTNUM(202) +NTSTATUS +NTAPI +NtOpenFile( + OUT PHANDLE FileHandle, + IN ACCESS_MASK DesiredAccess, + IN POBJECT_ATTRIBUTES ObjectAttributes, + OUT PIO_STATUS_BLOCK IoStatusBlock, + IN ULONG ShareAccess, + IN ULONG OpenOptions + ); + +// NtReadFile: +// Reads a file. +// +// Differences from NT: There is no Key parameter. +NTSYSAPI +EXPORTNUM(219) +NTSTATUS +NTAPI +NtReadFile( + IN HANDLE FileHandle, + IN HANDLE Event OPTIONAL, + IN PIO_APC_ROUTINE ApcRoutine OPTIONAL, + IN PVOID ApcContext OPTIONAL, + OUT PIO_STATUS_BLOCK IoStatusBlock, + OUT PVOID Buffer, + IN ULONG Length, + IN PLARGE_INTEGER ByteOffset + ); + +// NtWriteFile: +// Writes a file. +// +// Differences from NT: There is no Key parameter. +NTSYSAPI +EXPORTNUM(236) +NTSTATUS +NTAPI +NtWriteFile( + IN HANDLE FileHandle, + IN HANDLE Event OPTIONAL, + IN PIO_APC_ROUTINE ApcRoutine OPTIONAL, + IN PVOID ApcContext OPTIONAL, + OUT PIO_STATUS_BLOCK IoStatusBlock, + IN PVOID Buffer, + IN ULONG Length, + IN PLARGE_INTEGER ByteOffset + ); + +// NtQueryVolumeInformation: +// Queries information about a file system. This is not documented by +// Microsoft even under NT. +// +// Differences from NT: None known. +NTSYSAPI +EXPORTNUM(218) +NTSTATUS +NTAPI +NtQueryVolumeInformationFile( + IN HANDLE FileHandle, + OUT PIO_STATUS_BLOCK IoStatusBlock, + OUT PVOID VolumeInformation, + IN ULONG VolumeInformationLength, + IN FS_INFORMATION_CLASS VolumeInformationClass + ); + +// NtDeviceIoControl: +// Does an IOCTL on a device. +// +// Differences from NT: None known. +NTSYSAPI +EXPORTNUM(196) +NTSTATUS +NTAPI +NtDeviceIoControlFile( + IN HANDLE FileHandle, + IN HANDLE Event OPTIONAL, + IN PIO_APC_ROUTINE ApcRoutine OPTIONAL, + IN PVOID ApcContext OPTIONAL, + OUT PIO_STATUS_BLOCK IoStatusBlock, + IN ULONG IoControlCode, + IN PVOID InputBuffer OPTIONAL, + IN ULONG InputBufferLength, + OUT PVOID OutputBuffer OPTIONAL, + IN ULONG OutputBufferLength + ); + +// NtClose: +// Closes a file or other handle. +// +// Differences from NT: None. +NTSYSAPI +EXPORTNUM(187) +NTSTATUS +NTAPI +NtClose( + IN HANDLE Handle + ); + +// NtAllocateVirtualMemory: +// Allocates virtual memory. +// +// Differences from NT: There is no ProcessHandle parameter. +NTSYSAPI +EXPORTNUM(184) +NTSTATUS +NTAPI +NtAllocateVirtualMemory( + IN OUT PVOID *BaseAddress, + IN ULONG ZeroBits, + IN OUT PULONG AllocationSize, + IN ULONG AllocationType, + IN ULONG Protect + ); + +// NtFreeVirtualMemory: +// Frees virtual memory. +// +// Differences from NT: There is no ProcessHandle parameter. +NTSYSAPI +EXPORTNUM(199) +NTSTATUS +NTAPI +NtFreeVirtualMemory( + IN OUT PVOID *BaseAddress, + IN OUT PULONG FreeSize, + IN ULONG FreeType + ); + + +// Kernel-level routines + + +// KeBugCheck: +// Bug checks the kernel. +// Same as KeBugCheckEx(BugCheckCode, 0, 0, 0, 0); +// +// Differences from NT: None, other than the reaction. +NTSYSAPI +EXPORTNUM(95) +VOID +NTAPI +KeBugCheck( + IN ULONG BugCheckCode + ); + +// KeBugCheckEx: +// Bug checks the kernel. +// +// Differences from NT: None, other than the reaction. +NTSYSAPI +EXPORTNUM(96) +VOID +NTAPI +KeBugCheckEx( + IN ULONG BugCheckCode, + IN ULONG_PTR BugCheckParameter1, + IN ULONG_PTR BugCheckParameter2, + IN ULONG_PTR BugCheckParameter3, + IN ULONG_PTR BugCheckParameter4 + ); + +// KeInitializeDpc: +// Initializes a DPC structure. +// +// Differences from NT: This function sets less fields than the NT version. +NTSYSAPI +EXPORTNUM(107) +VOID +NTAPI +KeInitializeDpc( + IN PKDPC Dpc, + IN PKDEFERRED_ROUTINE DeferredRoutine, + IN PVOID DeferredContext + ); + +// KeInitializeTimerEx: +// Initializes a timer. +// +// Differences from NT: None. +NTSYSAPI +EXPORTNUM(113) +VOID +KeInitializeTimerEx( + IN OUT PKTIMER Timer, + IN TIMER_TYPE Type + ); + +// KeRaiseIrql: +// Raises IRQL to some value. +// +// Differences from NT: None. +#define KeRaiseIrql KfRaiseIrql +NTSYSAPI +EXPORTNUM(190) +VOID +__fastcall +KfRaiseIrql( + IN KIRQL NewIrql, + OUT PKIRQL OldIrql + ); + +// KeRaiseIrqlToDpcLevel: +// Raises IRQL to DISPATCH_LEVEL. Like KeRaiseIrql except returns old level directly. +// +// Differences from NT: None. +NTSYSAPI +EXPORTNUM(129) +KIRQL +NTAPI +KeRaiseIrqlToDpcLevel( + VOID + ); + +// KeLowerIrql: +// Lowers IRQL. +#define KeLowerIrql KfLowerIrql +NTSYSAPI +EXPORTNUM(161) +VOID +__fastcall +KfLowerIrql( + IN KIRQL NewIrql + ); + + +// MmMapIoSpace: +// Maps a physical address area into the virtual address space. +// DO NOT USE MEMORY MAPPED WITH THIS AS A BUFFER TO OTHER CALLS. For +// example, don't WriteFile or NtWriteFile these buffers. Copy them first. +// +// Differences from NT: PhysicalAddress is 32 bit, not 64. ProtectionType +// specifies the page protections, but it's a Win32 PAGE_ macro instead +// of the normal NT enumeration. PAGE_READWRITE is probably what you +// want... +NTSYSAPI +EXPORTNUM(177) +PVOID +NTAPI +MmMapIoSpace( + IN PHYSICAL_ADDRESS PhysicalAddress, + IN ULONG NumberOfBytes, + IN ULONG ProtectionType + ); + +// MmGetPhysicalAddress: +// Translates a virtual address into a physical address. +// +// Differences from NT: PhysicalAddress is 32 bit, not 64. +NTSYSAPI +EXPORTNUM(173) +PHYSICAL_ADDRESS +NTAPI +MmGetPhysicalAddress( + IN PVOID BaseAddress + ); + +// MmUnmapIoSpace: +// Unmaps a virtual address mapping made by MmMapIoSpace. +// +// Differences from NT: None. +NTSYSAPI +EXPORTNUM(183) +PVOID +NTAPI +MmUnmapIoSpace( + IN PVOID BaseAddress, + IN ULONG NumberOfBytes + ); + +// MmAllocateContiguousMemory: +// Allocates a range of physically contiguous, cache-aligned memory from the +// non-paged pool (= main pool on XBOX). +// +// Differences from NT: HighestAcceptableAddress was deleted, opting instead +// to not care about the highest address. +NTSYSAPI +EXPORTNUM(165) +PVOID +NTAPI +MmAllocateContiguousMemory( + IN ULONG NumberOfBytes + ); + +// MmFreeContiguousMemory: +// Frees memory allocated with MmAllocateContiguousMemory. +// +// Differences from NT: None. +NTSYSAPI +EXPORTNUM(171) +VOID +NTAPI +MmFreeContiguousMemory( + IN PVOID BaseAddress + ); + + +// DbgPrint +// Displays a message on the debugger. +// +// Differences from NT: Only how this information is displayed changed. +NTSYSAPI +EXPORTNUM(8) +ULONG +__cdecl +DbgPrint( + IN PCSZ Format, + ... + ); + + +// ExAllocatePoolWithTag: +// Allocates memory from the memory pool. The Tag parameter is a 4-letter +// character constant to which to associate the allocation. +// +// Differences from NT: There is no PoolType field, as the XBOX only has 1 +// pool, the non-paged pool. +NTSYSAPI +EXPORTNUM(15) +PVOID +NTAPI +ExAllocatePoolWithTag( + IN SIZE_T NumberOfBytes, + IN ULONG Tag + ); + +// ExFreePool: +// Frees memory allocated by ExAllocatePool* functions. +// +// Differences from NT: None. +NTSYSAPI +EXPORTNUM(17) +VOID +NTAPI +ExFreePool( + IN PVOID P + ); + + +// IoCreateSymbolicLink: +// Creates a symbolic link in the object namespace. +// NtCreateSymbolicLinkObject is much harder to use than this simple +// function, so just use this one. +// +// Differences from NT: Uses ANSI_STRING instead of UNICODE_STRING. +NTSYSAPI +EXPORTNUM(67) +NTSTATUS +NTAPI +IoCreateSymbolicLink( + IN PANSI_STRING SymbolicLinkName, + IN PANSI_STRING DeviceName + ); + +// IoDeleteSymbolicLink: +// Creates a symbolic link in the object namespace. Deleting symbolic links +// through the Nt* functions is a pain, so use this instead. +// +// Differences from NT: Uses ANSI_STRING instead of UNICODE_STRING. +NTSYSAPI +EXPORTNUM(69) +NTSTATUS +NTAPI +IoDeleteSymbolicLink( + IN PANSI_STRING SymbolicLinkName + ); + + +// ObReferenceObjectByHandle: +// Turns a handle into a kernel object pointer. The ObjectType parameter +// specifies what type of object it is. This function also increments the +// object's reference count. +// +// Differences from NT: There are no DesiredAccess, AccessMode, or +// HandleInformation parameters. +NTSYSAPI +EXPORTNUM(246) +NTSTATUS +NTAPI +ObReferenceObjectByHandle( + IN HANDLE Handle, + IN POBJECT_TYPE ObjectType OPTIONAL, + OUT PVOID *Object + ); + +// ObfReferenceObject/ObReferenceObject: +// Increments the object's reference count. +// +// Differences from NT: None. +#define ObReferenceObject(Object) ObfReferenceObject(Object) +NTSYSAPI +EXPORTNUM(251) +VOID +FASTCALL +ObfReferenceObject( + IN PVOID Object + ); + +// ObfDereferenceObject/ObDereferenceObject: +// Decrements the object's reference count, deleting it if it is now unused. +// +// Differences from NT: None. +#define ObDereferenceObject(a) ObfDereferenceObject(a) +NTSYSAPI +EXPORTNUM(250) +VOID +FASTCALL +ObfDereferenceObject( + IN PVOID Object + ); + + +// PsTerminateSystemThread: +// Exits the current system thread. Must be called from a system thread. +// +// Differences from NT: None. +NTSYSAPI +EXPORTNUM(258) +__declspec(noreturn) +NTSTATUS +PsTerminateSystemThread( + NTSTATUS ExitCode + ); + + + +// Kernel routines only in the XBOX + +// IoSynchronousDeviceIoControlRequest: +// NICE. Makes kernel driver stuff sooooo much easier. This does a +// blocking IOCTL on the specified device. +// +// New to the XBOX. +NTSYSAPI +EXPORTNUM(84) +NTSTATUS +NTAPI +IoSynchronousDeviceIoControlRequest( + IN ULONG IoControlCode, + IN PDEVICE_OBJECT DeviceObject, + IN PVOID InputBuffer OPTIONAL, + IN ULONG InputBufferLength, + OUT PVOID OutputBuffer OPTIONAL, + IN ULONG OutputBufferLength, + OUT PDWORD unknown_use_zero OPTIONAL, + IN BOOLEAN InternalDeviceIoControl + ); + +// ExQueryNonVolatileSettings +// Queries saved information, such as the region code. +// +// New to the XBOX. +NTSYSAPI +EXPORTNUM(24) +NTSTATUS +NTAPI +ExQueryNonVolatileSetting( + IN ULONG ValueIndex, + OUT PULONG Type, + OUT PVOID Value, + IN ULONG ValueLength, + OUT PULONG ResultLength OPTIONAL + ); + +// ExSaveNonVolatileSettings +// Writes saved information, such as the region code. +// +// New to the XBOX. +NTSYSAPI +EXPORTNUM(29) +NTSTATUS +NTAPI +ExSaveNonVolatileSetting( + IN ULONG ValueIndex, + IN PULONG Type OPTIONAL, + IN PVOID Value, + IN ULONG ValueLength + ); + +// HalEnableSecureTrayEject: +// Notifies the SMBUS that ejecting the DVD-ROM should not reset the system. +// Note that this function can't really be called directly... +// +// New to the XBOX. +NTSYSAPI +EXPORTNUM(365) +VOID +NTAPI +HalEnableSecureTrayEject( + VOID + ); + +// XeLoadSection: +// Adds one to the reference count of the specified section and loads if the +// count is now above zero. +// +// New to the XBOX. +NTSYSAPI +EXPORTNUM(327) +NTSTATUS +NTAPI +XeLoadSection( + IN OUT PXBE_SECTION section + ); + +// XeUnloadSection: +// Subtracts one from the reference count of the specified section and loads +// if the count is now below zero. +// +// New to the XBOX. +NTSYSAPI +EXPORTNUM(328) +NTSTATUS +NTAPI +XeUnloadSection( + IN OUT PXBE_SECTION section + ); + +// RtlRip: +// Traps to the debugger with a certain message, then crashes. +// +// New to the XBOX. +NTSYSAPI +EXPORTNUM(352) +VOID +NTAPI +RtlRip( + IN PCSZ Part1, + IN PCSZ Part2, + IN PCSZ Part3 + ); + +// PsCreateSystemThread: +// Creates a system thread. Same as: +// PsCreateSystemThreadEx(ThreadHandle, NULL, 0x3000, 0, ThreadId, StartContext1, +// StartContext2, FALSE, DebugStack, PspSystemThreadStartup); +// +// New to the XBOX. (It is too different from NT to be considered the same) +NTSYSAPI +EXPORTNUM(254) +NTSTATUS +NTAPI +PsCreateSystemThread( + OUT PHANDLE ThreadHandle, + OUT PULONG ThreadId OPTIONAL, + IN PVOID StartContext1, + IN PVOID StartContext2, + IN BOOLEAN DebugStack + ); + +// PsCreateSystemThreadEx: +// Creates a system thread. +// ThreadHandle: Receives the thread handle +// ObjectAttributes: Unsure how this works (everything I've seen uses NULL) +// KernelStackSize: Size of the allocation for both stack and TLS data +// TlsDataSize: Size within KernelStackSize to use as TLS data +// ThreadId: Receives the thread ID number +// StartContext1: Parameter 1 to StartRoutine +// StartContext2: Parameter 2 to StartRoutine +// CreateSuspended: TRUE to create the thread as a suspended thread +// DebugStack: TRUE to allocate the stack from Debug Kit memory +// StartRoutine: Called when the thread is created +// +// New to the XBOX. +NTSYSAPI +EXPORTNUM(255) +NTSTATUS +NTAPI +PsCreateSystemThreadEx( + OUT PHANDLE ThreadHandle, + IN PVOID ObjectAttributes OPTIONAL, + IN ULONG KernelStackSize, + IN ULONG TlsDataSize, + OUT PULONG ThreadId OPTIONAL, + IN PVOID StartContext1, + IN PVOID StartContext2, + IN BOOLEAN CreateSuspended, + IN BOOLEAN DebugStack, + IN PKSTART_ROUTINE StartRoutine + ); + + + +// Error codes +#define STATUS_SUCCESS 0x00000000 +#define STATUS_UNSUCCESSFUL 0xC0000001 +#define STATUS_UNRECOGNIZED_MEDIA 0xC0000014 +// The SCSI input buffer was too large (not necessarily an error!) +#define STATUS_DATA_OVERRUN 0xC000003C +#define STATUS_INVALID_IMAGE_FORMAT 0xC000007B +#define STATUS_INSUFFICIENT_RESOURCES 0xC000009A +#define STATUS_TOO_MANY_SECRETS 0xC0000156 +#define STATUS_REGION_MISMATCH 0xC0050001 + +// End extern "C" for C++ +#if defined(__cplusplus) && !defined(XBOXINTERNAL_NO_EXTERN_C) +}; +#endif // __cplusplus + +#include + +// Thanks and credit go to Team Evox +typedef struct +{ + DWORD Data_00; // Check Block Start + DWORD Data_04; + DWORD Data_08; + DWORD Data_0c; + DWORD Data_10; // Check Block End + + DWORD V1_IP; // 0x14 + DWORD V1_Subnetmask; // 0x18 + DWORD V1_Defaultgateway; // 0x1c + DWORD V1_DNS1; // 0x20 + DWORD V1_DNS2; // 0x24 + + DWORD Data_28; // Check Block Start + DWORD Data_2c; + DWORD Data_30; + DWORD Data_34; + DWORD Data_38; // Check Block End + + DWORD V2_Tag; // V2 Tag "XBV2" + + DWORD Flag; // 0x40 + DWORD Data_44; + + DWORD V2_IP; // 0x48 + DWORD V2_Subnetmask; // 0x4c + DWORD V2_Defaultgateway; // 0x50 + DWORD V2_DNS1; // 0x54 + DWORD V2_DNS2; // 0x58 + + DWORD Data_xx[0x200-0x5c]; + +} TXNetConfigParams,*PTXNetConfigParams; + + +extern "C" +{ + // Thanks and credit go to Woodoo + extern VOID WINAPI HalInitiateShutdown(VOID); + extern VOID WINAPI HalWriteSMBusValue(BYTE, BYTE, BOOL, BYTE); + extern VOID WINAPI HalReadSMCTrayState(DWORD* state, DWORD* count); + + // Thanks and credit go to Team Evox + extern VOID WINAPI HalReturnToFirmware(DWORD); + + extern INT WINAPI XNetLoadConfigParams(LPBYTE); + extern INT WINAPI XNetSaveConfigParams(LPBYTE); + + extern INT WINAPI XWriteTitleInfoNoReboot(LPVOID,LPVOID,DWORD,DWORD,LPVOID); + + extern DWORD* LaunchDataPage; +} + + + +#endif // __XBOX_INTERNAL_H__ diff --git a/xbox1/RetroLaunch/Video.cpp b/xbox1/RetroLaunch/Video.cpp new file mode 100644 index 0000000000..04dfbd8e2a --- /dev/null +++ b/xbox1/RetroLaunch/Video.cpp @@ -0,0 +1,138 @@ +/** + * RetroLaunch 2012 + * + * 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 2 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, write to the Free Software Foundation, Inc., + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. To contact the + * authors: Surreal64 CE Team (http://www.emuxtras.net) + */ + + +#include "Video.h" +#include "IniFile.h" +#include "Debug.h" + +CVideo g_video; +HRESULT g_hResult; + +CVideo::CVideo(void) +{ +} + +CVideo::~CVideo(void) +{ +} + +bool CVideo::Create(HWND hDeviceWindow, bool bWindowed) +{ + // Create the Direct3D object (leave it DX8 or should we try DX9 for WIN32 ?) + m_pD3D = Direct3DCreate8(D3D_SDK_VERSION); + + if (m_pD3D == NULL) + return false; + + // set up the structure used to create the d3d device + D3DPRESENT_PARAMETERS d3dpp; + ZeroMemory(&d3dpp, sizeof(d3dpp)); + + d3dpp.BackBufferWidth = 640; + d3dpp.BackBufferHeight = 480; + d3dpp.BackBufferFormat = D3DFMT_A8R8G8B8; + d3dpp.BackBufferCount = 1; + //d3dpp.AutoDepthStencilFormat = D3DFMT_D16; + //d3dpp.EnableAutoDepthStencil = false; + d3dpp.SwapEffect = D3DSWAPEFFECT_DISCARD; + + //Fullscreen only + if(!bWindowed) + { + if(!g_iniFile.m_currentIniEntry.bVSync) { + d3dpp.FullScreen_PresentationInterval = D3DPRESENT_INTERVAL_IMMEDIATE; + }else{ + d3dpp.FullScreen_PresentationInterval = D3DPRESENT_INTERVAL_ONE; + } + } + + +#ifdef _XBOX + g_hResult = m_pD3D->CreateDevice(D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, NULL, + D3DCREATE_HARDWARE_VERTEXPROCESSING, + &d3dpp, &m_pD3DDevice); +#else //WIN32 + + D3DDISPLAYMODE d3ddm; + g_hResult = m_pD3D->GetAdapterDisplayMode( D3DADAPTER_DEFAULT, &d3ddm ); + d3dpp.BackBufferFormat = d3ddm.Format; + d3dpp.Windowed = bWindowed; + d3dpp.hDeviceWindow = hDeviceWindow; + + g_hResult = m_pD3D->CreateDevice(D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, hDeviceWindow, + D3DCREATE_HARDWARE_VERTEXPROCESSING, + &d3dpp, &m_pD3DDevice); +#endif //#ifdef _XBOX + + if (FAILED(g_hResult)) + { + g_debug.Print("Error: D3DCreate(), CreateDevice()"); +#ifndef _XBOX + DXTrace(__FILE__, __LINE__, g_hResult, "D3DCreate(), CreateDevice()", TRUE); +#endif + return false; + } + // use an orthogonal matrix for the projection matrix + D3DXMATRIX mat; + + D3DXMatrixOrthoOffCenterLH(&mat, 0.0f, 640.0f, 480.0f, 0.0f, 0.0f, 1.0f); + + m_pD3DDevice->SetTransform(D3DTS_PROJECTION, &mat); + + // use an identity matrix for the world and view matrices + D3DXMatrixIdentity(&mat); + m_pD3DDevice->SetTransform(D3DTS_WORLD, &mat); + m_pD3DDevice->SetTransform(D3DTS_VIEW, &mat); + + // disable lighting + m_pD3DDevice->SetRenderState(D3DRS_LIGHTING, FALSE); + + // disable z-buffer (see autodepthstencil) + m_pD3DDevice->SetRenderState(D3DRS_ZENABLE, FALSE ); + + return true; +} + + +void CVideo::BeginRender() +{ + m_pD3DDevice->Clear(0, NULL, D3DCLEAR_TARGET, + D3DCOLOR_XRGB(0, 0, 0), + 1.0f, 0); + + m_pD3DDevice->BeginScene(); +#ifdef _XBOX + m_pD3DDevice->SetFlickerFilter(g_iniFile.m_currentIniEntry.dwFlickerFilter); + m_pD3DDevice->SetSoftDisplayFilter(g_iniFile.m_currentIniEntry.bSoftDisplayFilter); +#endif +} + +void CVideo::EndRender() +{ + m_pD3DDevice->EndScene(); + + m_pD3DDevice->Present(NULL, NULL, NULL, NULL); + +} + +void CVideo::CleanUp() +{ + if( m_pD3DDevice != NULL) + m_pD3DDevice->Release(); + + if( m_pD3D != NULL) + m_pD3D->Release(); +} diff --git a/xbox1/RetroLaunch/Video.h b/xbox1/RetroLaunch/Video.h new file mode 100644 index 0000000000..500a2d205b --- /dev/null +++ b/xbox1/RetroLaunch/Video.h @@ -0,0 +1,53 @@ +/** + * RetroLaunch 2012 + * + * 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 2 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, write to the Free Software Foundation, Inc., + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. To contact the + * authors: Surreal64 CE Team (http://www.emuxtras.net) + */ + +#pragma once + +#include "Global.h" + +#define D3DFVF_CUSTOMVERTEX (D3DFVF_XYZ | D3DFVF_DIFFUSE | D3DFVF_TEX1) + +typedef struct CustomVertex +{ + float x, y, z; + dword color; + float u, v; + //float rhw; +}CustomVertex; + + +class CVideo +{ +public: + CVideo(void); + ~CVideo(void); + + bool Create(HWND hDeviceWindow, bool bWindowed); //Device creation + + void BeginRender(); + void EndRender(); + void CleanUp(); + +public: + /*Direct3D*/IDirect3D8 *m_pD3D; //D3D object + /*D3DDevice*/IDirect3DDevice8 *m_pD3DDevice; //D3D device + +private: + + //nothing +}; + +extern CVideo g_video; +extern HRESULT g_hResult; diff --git a/xbox1/RetroLaunch/dist/Media/arial.ttf b/xbox1/RetroLaunch/dist/Media/arial.ttf new file mode 100644 index 0000000000000000000000000000000000000000..25769bbf1b0751c727fcab04da0f84fa517ec61f GIT binary patch literal 65692 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 literal 0 HcmV?d00001 diff --git a/xbox1/RetroLaunch/dist/Media/menuMainBG.png b/xbox1/RetroLaunch/dist/Media/menuMainBG.png new file mode 100644 index 0000000000000000000000000000000000000000..8e6d54eee7e13d6a95fe8586631affe6465d3fd6 GIT binary patch literal 6415 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 literal 0 HcmV?d00001 diff --git a/xbox1/RetroLaunch/dist/Media/menuMainRomSelectPanel.png b/xbox1/RetroLaunch/dist/Media/menuMainRomSelectPanel.png new file mode 100644 index 0000000000000000000000000000000000000000..377a9bae5bce8227d0721fbc9a73919b824d9c21 GIT binary patch literal 3206 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 V33Da-#&v;nGt@QHse(B^`!_dA#9jaZ literal 0 HcmV?d00001 diff --git a/xbox1/RetroLaunch/dist/Roms/Add roms here.txt b/xbox1/RetroLaunch/dist/Roms/Add roms here.txt new file mode 100644 index 0000000000..e69de29bb2 diff --git a/xbox1/RetroLaunch/retroarch.h b/xbox1/RetroLaunch/retroarch.h new file mode 100644 index 0000000000..a751129251 --- /dev/null +++ b/xbox1/RetroLaunch/retroarch.h @@ -0,0 +1,5 @@ +// Automatically generated by the bundler tool from retroarch.jpg + +#define retroarch_NUM_RESOURCES 1UL + +#define retroarch_OFFSET 0UL diff --git a/xbox1/RetroLaunch/titleimage.jpg b/xbox1/RetroLaunch/titleimage.jpg new file mode 100644 index 0000000000000000000000000000000000000000..1b0bc9586da2660ba3f1cd09fb18ce5ebd01f6c8 GIT binary patch literal 8865 zcmd6Mc|278+y5E+K6c46g(9+K%{tu5k{ekf#6;P%Btn?6gd&7c%G#n(lNhpOOhlzD zStATX5;B&?Ff(VK)BSw!`}@70=k>gv-}m|J_nEoooY$PsIiKr&U7u?`%yA|W5IkXF zZ2`bwFyJio0hqHu!10(sKLD_`1(X2*-~!lSdjWRn3WCs!SM>H=0uzvgVc+xvCG*Zp(xbwctT539KM>GKDBw!A(z+k_={}`<7tiJ{a z8yhP-CkH3zFXH0n<>unz;o{`v;pgGu<%0$%w*Z`(`8b zZ!kLmAubq%j};~dun56eg<#As00CiOhvf20H2)Yd7FITP4u~ZlUT6YN5MrN|l?7s! zosA8eod8`2*o4@H_a8Ci5OMJ4l)EUZnUq_>CI4F;LCkTGtf1u+k<85_F0n^)?}3Ag zN{5uSb#(QP>Km9JJ8ofVWqrcQ`IL+6X*c(C=Y9SB0|J9CMMhnYj)}dJay2zA{o3`6 zy!=}Qg+;gT6j$DV@bFR9<0sWG>Kky4O)r~UUUzoA>F#;^?)|5s;gQj?@y`>pbENr& zuZ!Q7zOQd=ZvCWeQ+IZM;rfO1&+(tY{tGT42p0>a12)cIxL_>N5U~oevF|^^A#CQr z>3vZ|PBV#1^tarKIs&)6mLplrCt{FCTtR#G!1^y}e<1sR1D5=MLiQK1f8v?|_*h|( z@>qocB*36oW+(yw-%z_W6G(oHAw(FlGXb_&b-rVCt*CCI4oUZ8%a*1a$r_G-%&8DC431rp#(gp z;K^zqW4wnrym*?<<<~H^d7?x~NpQiNd-}X?@(eIP%8! z@7cz>dmi4h+AX}ZPr+J_|K;`Oz6eX1a2Uhi!Bk)LB<%R#=+2@jfZM5p78PXB@`%BB+KKQY!v+KlPvv?!M~UojHWKWkm{~ zL(A6$dFSrcF7KH6Y|9`3bPlM@0Jhe~3;ZyXdb4|)qOyt9Aqyd`NEce;w9#WcPV?I~OAXulygq)pCkWKU6Yo)8Z-P8RWI2)K1Jv}{t;Ot?f|uCU zOiJ{SMJ_qovgss{(?=Ax)Os7o2w=rkW(2=m_*u^B;2`zJ>!V$Ux01rI4W1y%ntL4B z-**)IwFeYnFkXWDY-uhrh*|XAIqncpHivsW=3&DVZQ5^kPbeSV$G^l*yWM42i1;x| zD2v5l{_>6u6G&+a0Y5@IkR>~=_jd-1^e|F&+yQ#iQv_d3{Yi2ZZ7Q${;a6();rB_g zjR(mVdxVDJ@9Q6z!@qlg{AZv|aEr^_pc z(-*wmo`^KS6JO4dj~#$kxkEQL5E48$V?)JYB+-5@jwmhOFkG^Vx@kE9+!q8;PH}3VsV+k zs*x=TD@@lMsa21PiFD2}8xE_wZL8Wg#?xgRC^#-G!M3O`pR8%wsl%Xs`P>t`$66vH z<))^k4*eiFir~M{E=b3W(G9E2WN@V=6pWg7!ThP25YhCn5vMoiHpH_4#eADb)=+${1s6Zq_^ z>4B?3(&dYP-^nWREE1l0zEEIyWgVsDV2YToB9tMj-XPLiLRM_&vvUodp@ zLC)+-@MEkYg<}#AbO;&lEA`aO?S4^mj7kzbFsU(dr7K2v!@d?a_S`CZ%8)I29 zn?1!%UHxKH%f$<0lTkKPrIFK)mUtg!_4kQNudA~;gWi@$1h9@FeZt0j-yoXGdNSeZ zrGoeY?Wtgg4f+$@^{;6o(O=9%H3yH&C+rN|J4DorXWczqM)p%&HJ1yOHKKB-Fad{G z9!#JBw~J*09JO?IGBzDU%$wmh+2|x+)7@q!p@2GG4SDSm&KYJc99N{~1+f*xW2iTjD` zy2P$~Qw7L9q1#$;ph($Le*63TPli&y8DgLJF0v|+?rlF|NwP7OHHPHEK^?@Vl-% zo@)#GN0%OWGJ!0Nh$!IN!UPCLm>?z~4!%b@+{6QN_h4nG_En9{iRSe^bUfRUr z&Pwz-!eBFdxo<^+(nLV+252LqHJ~H_go`$NQ=C21XZ$Wh<^Lh!<@a zMLPvj9L$u}?kg*y)X)`PN47c5`>_)eWsR0;7wr4bVP!9N31QaXgA$PM{-GH*IrCcZ zo{V7^KD7-&zkG)Y@H2=^V3&h>mkGe2Gtvm%5OOkKPJlD*a6eX>URN6v82U9AJC2?# z6(2rv_4zt5H54hhO9HQqk^7HByJ4omo-T?dVCwr{^JCr=Ke{v5ysB*@mDyxt=n;ER zPD$bRDqQo)(HkTMzBzn;4&-`Ps~(}JnE=^A{TfD?ZY+)qoShMFyiF;bz%>YZS%hr0 z+PF^F8J73Hp7-v5Z+6rOUZ_;gl6?zetrbBIMbRZ{IuZ3{gdC1|&UMeKY@8GletPC< zgTdv3DqO9rgnM&|^ob!$EkNzAVLIDA7C>irHUToh9IC&!UwwNz>BWsnQxBEjx0ygy zSrDlD4H8ZqC4^Dmc7*2eUc$6)to9u)Zw)*sXpVTBckSAa0y+%fOEo(rXt zqU-3YSp-!%>@{+wH#dawWG+O?zLhb7zZ-TCd+wFV*^BoCEVz2uTq$9uvMSKh2XRo? zr7F7iyj76O3rm;XZz0)jHlzl_&YdHn3&gLln>*u@SdL2+;LH>yQ{hhiPN>Ofx~L;k z59HonAHlpLZ=(=9wlA@Bag>`)H1+N*KNAy_d=(=+JMrk16(-;>g~5RX?W~zO)6`6R zyDOJ2?Rlp8DjMXbRVZd4!;s1D=QC=Hb8;F~T=?FY!5?=#5Y}fc{W8Ew;fL@2`uyo6X5c@hunxeja^cMgh4g-0PW!&NXo&bnO%?ClbmzDP$WVO zq3DvQJ3*ON(rh1Q);CCz2^_j&jD9<|4#gI>Stv49$2&s5Au;Kv?slM+TL%u~JYw?# zR<|;Fzs4#AM|3f&bvxvy3=p%@5y4wmpe(MZt0u^N8I9Cj>zkcTfJt+Ixkrp- z9-)?6*mmi8G07X>Cs_GhT_`IR<;gDUEwfharX8VXfFQi!G4{(#Jn4a7xN-+4S(K-`QGsnf0 ztwP4a(MUhT$in-VM9-;y#gj`Kas&v*KZjlRc2^k>-y{U1;~3TjxyGu?ac%dy8O?-sfjezq(cCpHIqdY&kUX-I>HDl#8myLyx7)B&_EU+F761 zt(Rrdooa$^DpUs6(p4(Bri%T}%g^Uj3c^{-yc%E(3G|F!?$XTjs_vTW1;t86oX@Og zuN3T!pHEuL0F{v!WPD~&P%U*lIw-G)ZN_in1+d3wbl;`qxRmMkS;i|oaen3o6TsyV z7-59HsH~skM#wp<`R7p8Mc281Oznanu8{XyQ;(EE(Wd=}2O6CFlUVTD-vDelB+GM5 z;Jr28A5S}khKk*xhq#!8xOBP*0(7*cL7Bm48}cGpqWYMX7E1LwLX1{?^LPJ(c#^vdrf!x`6GqQSyt zQ5f~kzt%*k;dCgUnQiqzjT_E_k8)vA7w<1$Y+duQ0;liZ@JGBxDuW)&yT_pr22s#c z#jY8yEqJN}p@!e1(?nua=g`9#AMqPm&s2bHL~Phr0r9c4o)KRndUEWmKV8O+#~!lU zEpoucm+)(amRN>)lmSCyv!??4-kxfBZf+qx6?qoPHPI`!Y$MSX40LP%R%PJzXlfiX z(2dT&FsJ zM3I>%2fV&g_p|@V)~kox8n5H46wYsTXhV_HwI4%vdB_QJ`_rruyARtT(;7NHlEC82 z6@ICBCkN!!BK6v{Lu;v;AQP$$?;vrYEH%?M{rxG!^Z8b76CMiQXs0~QgvYGZ%75*b z{JTo^Wdd@>ISk0c2D3ayQpmmFu{gv(l>8XLejS7KcM*!kSwyk5Kl~@=5C5r*FJ7Y> zyU$}fFHkw8buqJCLuE%vAo6FDDI(*R!*M|L-z2dg2=eck9c#T6$d-N8JIi(>8?o>| z(3~%U+@Squ@^1Wze{ROV^W9HZO90vHOxpdy8$sWc0Blb`RlgVHnp%g0JQ8yVF6zwe z$I{%`>ZL-;73n1vly&63EZm{-os(wDJ8{qCclreE49+2+N1iJNKob*L451M+C}fw) z9Un-IYRl#uw;CB9RwJtgWaqPpI_dn@oNfDumzY2!CtZ-2lF!JBDVqo)9t=H+7SUlr1ymD2xsjI&>fhPFG6`ji$O9NmcG zk3aAqA)6~D z4w~P)Xo~1)%Fwo9aGtb^;x()}RIKs6NU-5|R|S7I)*C7Pb+r>90r$Hfd>hZVoPG|VuEgnBzF03}Ww55W)Nfh$dT-mSoAzu)02RU0FV zu;x&yGMf{L$Xnl4RxnhR9=dSmn;9}xOlGzGeDyP}1b+4D+DhcIN|y0jsOtIRVD@I^ z{4>W|cibZMgt_+d378x$XUR}m&7sPn=)ygaFH9~$4_@g{#n*2Us!mhg7tdrYo_^W( zqCU5>t)XfEfhLQ?J0=1ZpNi-c~3EpJ_2=WDq6;0$iCKn%UWp~$T!&`V4aZ(()!)Z*`i8?GFEd`&srj06ANA6f@8Yi|Iz%xzdr?$c_+of`^iLVrYx%cy!h!4^fyrEU`Y(|5U2Xy>|H7P3?Qwq?&I9}0aY-(+#C zyLNd`pv1*fJ{;lPbNC9(coWF4M7BsLJ6UIv5UEBobI3i{)Cwuzd%9ZYh29-%zQoFo zQUIQ>IBoFd-jjX6y3>eVN(cEqk}1tq;9F1^kAL3YG#G_=4*AV9y%z=vQ7^V#-P)SD zSZuM0ilHDaGl8yKBCSo|fX-fL5KpX-N{@qG_Mdp;Qa6x%hX z?L~KkY>d=)&OkIkjjphqoe>E13d{8?+SaQs_3awtr}d`C3Y;%B@5P97LT^sK5C5y5 z_ZM|lU-u`erR{Lz0)$0AjjQjUk?;Rqgn93X% zqkl(_&XA+~D&}Uyp=2Z7iJCc^uwPZ)=5`d4?^~Wxe)_FemQS_>CqY{0k5W#jg=uK- zr!=57QB?HNEp;jCvt9=YSgBjvp$gF>A!l80g+_-M8%CAGp%gu(4H+259y4mwpFZJ& ztD2_Pn6IbhV`78Z=Y`wCOT>NlR5ss)&%AkOYU155k4(i)q6d);t~c;dAL52VPfi=A z786Eo^4dVpU?^6q@l4<#7cpl$T+ZTUhSJ!ALrJr~LcT<&`sc|xcy}`$%R!as_ENp| z+nAKLr}$S!8!r3?^}5u8z;F2j!kkW~(otv-G>pZo?)}1VX-kcI^5uP( zdZF{OK`imdRN1upNyD-EDae#WIw^^tz6Sh2bHw{J=7#h150lB%Tq zZ~O_isDn^$5`y;+!BC)5Jvl7iW+47ZRd3Dm-b?M^f-qbl^| zXqTnUkR9ElNhkYmcnBhQqG<(BNJ{ix0Nfa>kiuilGxR-*~KnT-npKu zsya}L6EqRpS~;^u)A^Y?9PP5XayhHjodo&xl_7M}q&BV6HR!%`(NGb3W;sy5V)*2U7wk1=gERucehC+;vRDK}a zsJ<(i{E+v>s-x&Oy)XO>puqtku^B2m@sRbV)VJ&OkfaVhGm4rk8g%@6u(=7v{mtwA z&E$5Ul!a`oshrzU-&H90L9Q%FQxW7Dx|kC&4Xai(n>^!r0xzmfImG8@!AetfmF$qw zN0a~wb`=R?UnE=+VKXuxo&d0fB-}a1ced$EP71!U$QH2oq~*^7!W?hkxv| zdN91U&ve5~!A)JXqND4Efbo^{Jlj0JU3mYb`H8hUn-H_Y3j&Ey*{~i0sRzr**0vu< zfjq|RQnUT3TQpmh&UTp)RcNfKsg25;6a+?4^CHe)XnThC3Af_}T;KiNg+#__9;_9F zB74|p1r46*1QOT_d~*UhK_P}A6TqU%oB`J#5_HL=b+u(esTOsr>Ue~Arbj#ZX>0kh zFpb#aN|NeZ+G3RvLxoJipT`pij1^A~o2bX1DC`^k8l#HK;lcI3;f}fzig`}gUfr^4%Nq00?J-?%HQ_^*t}~(E z7P+-k*j9JyZzScoS0*C!q0F(29Ee)AC65L;t;)jIIg2Zr01}=pnE$zhEOrn5S^ty;Tg&VW*?OWQTmo- z@5j};8@$YQ_GbVQ7IPf2(qmhjWYX+t4!Mj_=!Fe8m=Xhh>*SfrPL-aos>#S})w7oY*`~A9+#?>9QbCWo`@XssuOL%wS&HoDW O-z~}huk$g?&;J2#&AQP5 literal 0 HcmV?d00001