diff --git a/.gitignore b/.gitignore index 7905f786ba..8055e8c2e3 100644 --- a/.gitignore +++ b/.gitignore @@ -10,20 +10,33 @@ *.lai *.la *.a -/libs *.opensdf *.sdf -*.user -/bin/rpcs3.exe -/bin/RPCS3.log +*.suo +*.tlog +*.idb +*.pdb +*.obj +*.ilk +*.pch + +*.log +*.exe +*.elf +*.lastbuildstate +*.unsuccessfulbuild +*.res +*.dump + +/libs +/ipch +/rpcs3/Debug +/rpcs3/Release + +/wxWidgets/lib /bin/rpcs3.ini -/bin/compiled.elf /bin/FragmentProgram.txt -/bin/VertexDataArray.dump /bin/VertexProgram.txt /bin/BreakPoints.dat /bin/textures -*.suo -/rpcs3/Debug -/rpcs3/Release -/ipch +rpcs3/git-version.h diff --git a/Utilities/git-version-gen.cmd b/Utilities/git-version-gen.cmd new file mode 100644 index 0000000000..3cd4334b4a --- /dev/null +++ b/Utilities/git-version-gen.cmd @@ -0,0 +1,73 @@ +@echo off + +rem // This program is free software: you can redistribute it and/or modify +rem // it under the terms of the GNU General Public License as published by +rem // the Free Software Foundation, version 2.0 or later versions. + +rem // This program is distributed in the hope that it will be useful, +rem // but WITHOUT ANY WARRANTY; without even the implied warranty of +rem // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +rem // GNU General Public License 2.0 for more details. + +rem // A copy of the GPL 2.0 should have been included with the program. +rem // If not, see http://www.gnu.org/licenses/ + +rem // Official git repository and contact information can be found at +rem // https://github.com/hrydgard/RPCS3 and http://www.RPCS3.org/. + +setlocal ENABLEDELAYEDEXPANSION + +set GIT_VERSION_FILE=%~p0..\rpcs3\git-version.h +if not defined GIT ( + set GIT="git" +) +call %GIT% describe > NUL 2> NUL +if errorlevel 1 ( + echo Git not on path, trying default Msysgit paths + set GIT="%ProgramFiles(x86)%\Git\bin\git.exe" + call !GIT! describe > NUL 2> NUL + if errorlevel 1 ( + set GIT="%ProgramFiles%\Git\bin\git.exe" + ) +) + +if exist "%GIT_VERSION_FILE%" ( + rem // Skip updating the file if RPCS3_GIT_VERSION_NO_UPDATE is 1. + findstr /B /C:"#define RPCS3_GIT_VERSION_NO_UPDATE 1" "%GIT_VERSION_FILE%" > NUL + if not errorlevel 1 ( + goto done + ) +) + +call %GIT% describe --always > NUL 2> NUL +if errorlevel 1 ( + echo Unable to update git-version.h, git not found. + echo If you don't want to add it to your path, set the GIT environment variable. + + echo // This is a generated file. > "%GIT_VERSION_FILE%" + echo. >> "%GIT_VERSION_FILE%" + echo #define RPCS3_GIT_VERSION "unknown" >> "%GIT_VERSION_FILE%" + echo. >> "%GIT_VERSION_FILE%" + echo // If you don't want this file to update/recompile, change to 1. >> "%GIT_VERSION_FILE%" + echo #define RPCS3_GIT_VERSION_NO_UPDATE 0 >> "%GIT_VERSION_FILE%" + goto done +) + +for /F %%I IN ('call %GIT% describe --always') do set GIT_VERSION=%%I + +rem // Don't modify the file if it already has the current version. +if exist "%GIT_VERSION_FILE%" ( + findstr /C:"%GIT_VERSION%" "%GIT_VERSION_FILE%" > NUL + if not errorlevel 1 ( + goto done + ) +) + +echo // This is a generated file. > "%GIT_VERSION_FILE%" +echo. >> "%GIT_VERSION_FILE%" +echo #define RPCS3_GIT_VERSION "%GIT_VERSION%" >> "%GIT_VERSION_FILE%" +echo. >> "%GIT_VERSION_FILE%" +echo // If you don't want this file to update/recompile, change to 1. >> "%GIT_VERSION_FILE%" +echo #define RPCS3_GIT_VERSION_NO_UPDATE 0 >> "%GIT_VERSION_FILE%" + +:done diff --git a/rpcs3/Emu/Cell/PPCThread.cpp b/rpcs3/Emu/Cell/PPCThread.cpp index 45ba83d0ec..938389ab5b 100644 --- a/rpcs3/Emu/Cell/PPCThread.cpp +++ b/rpcs3/Emu/Cell/PPCThread.cpp @@ -211,7 +211,7 @@ wxArrayString PPCThread::ErrorToString(const u32 error) void PPCThread::Run() { - if(IsRunned()) Stop(); + if(IsRunning()) Stop(); if(IsPaused()) { Resume(); @@ -220,7 +220,7 @@ void PPCThread::Run() wxGetApp().SendDbgCommand(DID_START_THREAD, this); - m_status = Runned; + m_status = Running; SetPc(entry); InitStack(); @@ -237,7 +237,7 @@ void PPCThread::Resume() wxGetApp().SendDbgCommand(DID_RESUME_THREAD, this); - m_status = Runned; + m_status = Running; DoResume(); Emu.CheckStatus(); @@ -248,7 +248,7 @@ void PPCThread::Resume() void PPCThread::Pause() { - if(!IsRunned()) return; + if(!IsRunning()) return; wxGetApp().SendDbgCommand(DID_PAUSE_THREAD, this); diff --git a/rpcs3/Emu/Cell/PPCThread.h b/rpcs3/Emu/Cell/PPCThread.h index 6fd153bbce..a4f2f5ae33 100644 --- a/rpcs3/Emu/Cell/PPCThread.h +++ b/rpcs3/Emu/Cell/PPCThread.h @@ -138,7 +138,7 @@ public: wxArrayString ErrorToString() { return ErrorToString(m_error); } bool IsOk() const { return m_error == 0; } - bool IsRunned() const { return m_status == Runned; } + bool IsRunning() const { return m_status == Running; } bool IsPaused() const { return m_status == Paused; } bool IsStopped() const { return m_status == Stopped; } diff --git a/rpcs3/Emu/GS/GL/GLGSRender.cpp b/rpcs3/Emu/GS/GL/GLGSRender.cpp index cca4498174..d31847896e 100644 --- a/rpcs3/Emu/GS/GL/GLGSRender.cpp +++ b/rpcs3/Emu/GS/GL/GLGSRender.cpp @@ -141,7 +141,7 @@ void GLRSXThread::Task() const u32 get = re(p.m_ctrl->get); const u32 put = re(p.m_ctrl->put); - if(put == get || !Emu.IsRunned()) + if(put == get || !Emu.IsRunning()) { if(put == get) SemaphorePostAndWait(p.m_sem_flush); diff --git a/rpcs3/Emu/GS/Null/NullGSRender.h b/rpcs3/Emu/GS/Null/NullGSRender.h index 116e78cbbd..c4920a69bd 100644 --- a/rpcs3/Emu/GS/Null/NullGSRender.h +++ b/rpcs3/Emu/GS/Null/NullGSRender.h @@ -129,7 +129,7 @@ wxThread::ExitCode NullRSXThread::Entry() { wxCriticalSectionLocker lock(p.m_cs_main); - if(p.m_ctrl->get == p.m_ctrl->put || !Emu.IsRunned()) + if(p.m_ctrl->get == p.m_ctrl->put || !Emu.IsRunning()) { SemaphorePostAndWait(p.m_sem_flush); diff --git a/rpcs3/Emu/System.cpp b/rpcs3/Emu/System.cpp index f0155e534b..31b8fb556b 100644 --- a/rpcs3/Emu/System.cpp +++ b/rpcs3/Emu/System.cpp @@ -162,7 +162,7 @@ void Emulator::Run() if(!IsReady()) return; } - if(IsRunned()) Stop(); + if(IsRunning()) Stop(); if(IsPaused()) { Resume(); @@ -173,7 +173,7 @@ void Emulator::Run() wxCriticalSectionLocker lock(m_cs_status); //ConLog.Write("run..."); - m_status = Runned; + m_status = Running; m_vfs.Init(m_path); //m_vfs.Mount("/", vfsDevice::GetRoot(m_path), new vfsLocalFile()); @@ -214,7 +214,7 @@ void Emulator::Run() void Emulator::Pause() { - if(!IsRunned()) return; + if(!IsRunning()) return; //ConLog.Write("pause..."); wxGetApp().SendDbgCommand(DID_PAUSE_EMU); @@ -230,10 +230,10 @@ void Emulator::Resume() wxGetApp().SendDbgCommand(DID_RESUME_EMU); wxCriticalSectionLocker lock(m_cs_status); - m_status = Runned; + m_status = Running; CheckStatus(); - if(IsRunned() && Ini.CPUDecoderMode.GetValue() != 1) GetCPU().Exec(); + if(IsRunning() && Ini.CPUDecoderMode.GetValue() != 1) GetCPU().Exec(); wxGetApp().SendDbgCommand(DID_RESUMED_EMU); } @@ -268,7 +268,7 @@ void Emulator::Stop() Memory.Close(); //if(m_memory_viewer && m_memory_viewer->IsShown()) m_memory_viewer->Hide(); - wxGetApp().SendDbgCommand(DID_STOPED_EMU); + wxGetApp().SendDbgCommand(DID_STOPPED_EMU); } void Emulator::SavePoints(const wxString& path) diff --git a/rpcs3/Emu/System.h b/rpcs3/Emu/System.h index 8719e5fa73..57ffa73c32 100644 --- a/rpcs3/Emu/System.h +++ b/rpcs3/Emu/System.h @@ -136,7 +136,7 @@ public: void SavePoints(const wxString& path); void LoadPoints(const wxString& path); - __forceinline bool IsRunned() const { wxCriticalSectionLocker lock(m_cs_status); return m_status == Runned; } + __forceinline bool IsRunning() const { wxCriticalSectionLocker lock(m_cs_status); return m_status == Running; } __forceinline bool IsPaused() const { wxCriticalSectionLocker lock(m_cs_status); return m_status == Paused; } __forceinline bool IsStopped() const { wxCriticalSectionLocker lock(m_cs_status); return m_status == Stopped; } __forceinline bool IsReady() const { wxCriticalSectionLocker lock(m_cs_status); return m_status == Ready; } diff --git a/rpcs3/Gui/Debugger.cpp b/rpcs3/Gui/Debugger.cpp index a3100e1361..d6671d6111 100644 --- a/rpcs3/Gui/Debugger.cpp +++ b/rpcs3/Gui/Debugger.cpp @@ -47,7 +47,7 @@ public: void OnRun(wxCommandEvent& event) { - if(Emu.IsRunned()) + if(Emu.IsRunning()) { Emu.Pause(); } diff --git a/rpcs3/Gui/DisAsmFrame.cpp b/rpcs3/Gui/DisAsmFrame.cpp index 4fc2a446d7..ffc2266060 100644 --- a/rpcs3/Gui/DisAsmFrame.cpp +++ b/rpcs3/Gui/DisAsmFrame.cpp @@ -78,14 +78,14 @@ void DisAsmFrame::AddLine(const wxString line) { static bool finished = false; - if(finished && Emu.IsRunned()) + if(finished && Emu.IsRunning()) { count = 0; finished = false; } - else if(count >= LINES_OPCODES || !Emu.IsRunned()) + else if(count >= LINES_OPCODES || !Emu.IsRunning()) { - if(Emu.IsRunned()) Emu.Pause(); + if(Emu.IsRunning()) Emu.Pause(); finished = true; CPU.PrevPc(); return; diff --git a/rpcs3/Gui/InterpreterDisAsm.cpp b/rpcs3/Gui/InterpreterDisAsm.cpp index 48464a661a..346a03810c 100644 --- a/rpcs3/Gui/InterpreterDisAsm.cpp +++ b/rpcs3/Gui/InterpreterDisAsm.cpp @@ -232,13 +232,13 @@ void InterpreterDisAsmFrame::ShowAddr(const u64 addr) wxColour colour; - if((!CPU->IsRunned() || !Emu.IsRunned()) && PC == CPU->PC) - { - colour = wxColour("Green"); - } - else - { - colour = wxColour("White"); + if((!CPU->IsRunning() || !Emu.IsRunning()) && PC == CPU->PC) + { + colour = wxColour("Green"); + } + else + { + colour = wxColour("White"); for(u32 i=0; iExecOnce(); } - while(CPU->IsRunned() && Emu.IsRunned() && !TestDestroy() && !IsBreakPoint(CPU->PC) && dump_status == dump_enable); + while(CPU->IsRunning() && Emu.IsRunning() && !TestDestroy() && !IsBreakPoint(CPU->PC) && dump_status == dump_enable); } catch(const wxString& e) { diff --git a/rpcs3/Gui/MainFrame.cpp b/rpcs3/Gui/MainFrame.cpp index 393cea6820..d5d5a02530 100644 --- a/rpcs3/Gui/MainFrame.cpp +++ b/rpcs3/Gui/MainFrame.cpp @@ -2,6 +2,7 @@ #include "MainFrame.h" #include "CompilerELF.h" +#include "git-version.h" #include "Emu/System.h" #include "Ini.h" #include "Emu/GS/sysutil_video.h" @@ -40,7 +41,13 @@ MainFrame::MainFrame() , m_aui_mgr(this) , m_sys_menu_opened(false) { + +#ifdef _DEBUG + SetLabel(wxString::Format(_PRGNAME_ " git-" RPCS3_GIT_VERSION)); +#else SetLabel(wxString::Format(_PRGNAME_ " " _PRGVER_)); +#endif + wxMenuBar& menubar(*new wxMenuBar()); wxMenu& menu_boot(*new wxMenu()); @@ -119,87 +126,57 @@ void MainFrame::DoSettings(bool load) void MainFrame::BootGame(wxCommandEvent& WXUNUSED(event)) { - bool stoped = false; + bool stopped = false; - if(Emu.IsRunned()) + if(Emu.IsRunning()) { Emu.Pause(); - stoped = true; + stopped = true; } wxDirDialog ctrl(this, L"Select game folder", wxEmptyString); if(ctrl.ShowModal() == wxID_CANCEL) { - if(stoped) Emu.Resume(); + if(stopped) Emu.Resume(); return; } Emu.Stop(); - const wxString& elf0 = ctrl.GetPath() + "\\PS3_GAME\\USRDIR\\BOOT.BIN"; - const wxString& elf1 = ctrl.GetPath() + "\\USRDIR\\BOOT.BIN"; - const wxString& elf2 = ctrl.GetPath() + "\\BOOT.BIN"; - const wxString& self0 = ctrl.GetPath() + "\\PS3_GAME\\USRDIR\\EBOOT.BIN"; - const wxString& self1 = ctrl.GetPath() + "\\USRDIR\\EBOOT.BIN"; - const wxString& self2 = ctrl.GetPath() + "\\EBOOT.BIN"; + wxString elf[6] = { + "\\PS3_GAME\\USRDIR\\BOOT.BIN", + "\\USRDIR\\BOOT.BIN", + "\\BOOT.BIN", + "\\PS3_GAME\\USRDIR\\EBOOT.BIN", + "\\USRDIR\\EBOOT.BIN", + "\\EBOOT.BIN" + }; - if(wxFile::Access(elf0, wxFile::read)) + for(int i=0;i<6;i++) { - Emu.SetPath(elf0); - ConLog.Write("Elf: booting..."); + if(wxFile::Access(ctrl.GetPath() + elf[i], wxFile::read)) + { + Emu.SetPath(ctrl.GetPath() + elf[i]); + ConLog.Write("Elf: booting..."); + Emu.Load(); + ConLog.Write("Game: boot done."); + return; + } } - else if(wxFile::Access(elf1, wxFile::read)) - { - Emu.SetPath(elf1); - ConLog.Write("Elf: booting..."); - } - else if(wxFile::Access(elf2, wxFile::read)) - { - Emu.SetPath(elf2); - ConLog.Write("Elf: booting..."); - } - else if(wxFile::Access(self0, wxFile::read)) - { - goto _ELF_NOT_FOUND_; - Emu.SetPath(self0); - ConLog.Warning("Self: booting..."); - } - else if(wxFile::Access(self1, wxFile::read)) - { - goto _ELF_NOT_FOUND_; - Emu.SetPath(self1); - ConLog.Warning("Self: booting..."); - } - else if(wxFile::Access(self2, wxFile::read)) - { - goto _ELF_NOT_FOUND_; - Emu.SetPath(self2); - ConLog.Warning("Self: booting..."); - } - else - { - ConLog.Error("Not found ps3 game in selected folder! (%s)", ctrl.GetPath()); - return; - } - - Emu.Load(); - - ConLog.Write("Game: boot done."); + + ConLog.Error("Ps3 executable not found in selected folder (%s)", ctrl.GetPath()); return; - -_ELF_NOT_FOUND_: - ConLog.Error("Elf not found!"); } void MainFrame::BootElf(wxCommandEvent& WXUNUSED(event)) { - bool stoped = false; + bool stopped = false; - if(Emu.IsRunned()) + if(Emu.IsRunning()) { Emu.Pause(); - stoped = true; + stopped = true; } wxFileDialog ctrl(this, L"Select ELF", wxEmptyString, wxEmptyString, "*.*", @@ -207,7 +184,7 @@ void MainFrame::BootElf(wxCommandEvent& WXUNUSED(event)) if(ctrl.ShowModal() == wxID_CANCEL) { - if(stoped) Emu.Resume(); + if(stopped) Emu.Resume(); return; } @@ -223,12 +200,12 @@ void MainFrame::BootElf(wxCommandEvent& WXUNUSED(event)) void MainFrame::BootSelf(wxCommandEvent& WXUNUSED(event)) { - bool stoped = false; + bool stopped = false; - if(Emu.IsRunned()) + if(Emu.IsRunning()) { Emu.Pause(); - stoped = true; + stopped = true; } wxFileDialog ctrl(this, L"Select SELF", wxEmptyString, wxEmptyString, "*.*", @@ -236,7 +213,7 @@ void MainFrame::BootSelf(wxCommandEvent& WXUNUSED(event)) if(ctrl.ShowModal() == wxID_CANCEL) { - if(stoped) Emu.Resume(); + if(stopped) Emu.Resume(); return; } @@ -260,7 +237,7 @@ void MainFrame::Pause(wxCommandEvent& WXUNUSED(event)) { Emu.Resume(); } - else if(Emu.IsRunned()) + else if(Emu.IsRunning()) { Emu.Pause(); } @@ -289,7 +266,7 @@ void MainFrame::Config(wxCommandEvent& WXUNUSED(event)) bool paused = false; - if(Emu.IsRunned()) + if(Emu.IsRunning()) { Emu.Pause(); paused = true; @@ -403,7 +380,7 @@ void MainFrame::UpdateUI(wxCommandEvent& event) { event.Skip(); - bool is_runned, is_stopped, is_ready; + bool is_running, is_stopped, is_ready; if(event.GetEventType() == wxEVT_DBG_COMMAND) { @@ -411,14 +388,14 @@ void MainFrame::UpdateUI(wxCommandEvent& event) { case DID_START_EMU: case DID_STARTED_EMU: - is_runned = true; + is_running = true; is_stopped = false; is_ready = false; break; case DID_STOP_EMU: - case DID_STOPED_EMU: - is_runned = false; + case DID_STOPPED_EMU: + is_running = false; is_stopped = true; is_ready = false; m_sys_menu_opened = false; @@ -426,26 +403,26 @@ void MainFrame::UpdateUI(wxCommandEvent& event) case DID_PAUSE_EMU: case DID_PAUSED_EMU: - is_runned = false; + is_running = false; is_stopped = false; is_ready = false; break; case DID_RESUME_EMU: case DID_RESUMED_EMU: - is_runned = true; + is_running = true; is_stopped = false; is_ready = false; break; case DID_READY_EMU: - is_runned = false; + is_running = false; is_stopped = false; is_ready = true; break; case DID_REGISTRED_CALLBACK: - is_runned = Emu.IsRunned(); + is_running = Emu.IsRunning(); is_stopped = Emu.IsStopped(); is_ready = Emu.IsReady(); break; @@ -456,7 +433,7 @@ void MainFrame::UpdateUI(wxCommandEvent& event) } else { - is_runned = Emu.IsRunned(); + is_running = Emu.IsRunning(); is_stopped = Emu.IsStopped(); is_ready = Emu.IsReady(); } @@ -466,7 +443,7 @@ void MainFrame::UpdateUI(wxCommandEvent& event) wxMenuItem& stop = *menubar.FindItem( id_sys_stop ); wxMenuItem& send_exit = *menubar.FindItem( id_sys_send_exit ); wxMenuItem& send_open_menu = *menubar.FindItem( id_sys_send_open_menu ); - pause.SetText(is_runned ? "Pause\tCtrl + P" : is_ready ? "Start\tCtrl + C" : "Resume\tCtrl + C"); + pause.SetText(is_running ? "Pause\tCtrl + P" : is_ready ? "Start\tCtrl + C" : "Resume\tCtrl + C"); pause.Enable(!is_stopped); stop.Enable(!is_stopped); //send_exit.Enable(false); @@ -536,7 +513,7 @@ void MainFrame::OnKeyDown(wxKeyEvent& event) switch(event.GetKeyCode()) { case 'C': case 'c': if(Emu.IsPaused()) Emu.Resume(); else if(Emu.IsReady()) Emu.Run(); return; - case 'P': case 'p': if(Emu.IsRunned()) Emu.Pause(); return; + case 'P': case 'p': if(Emu.IsRunning()) Emu.Pause(); return; case 'S': case 's': if(!Emu.IsStopped()) Emu.Stop(); return; case 'R': case 'r': if(!Emu.m_path.IsEmpty()) {Emu.Stop(); Emu.Run();} return; } diff --git a/rpcs3/Ini.cpp b/rpcs3/Ini.cpp index 05a4ed6fb8..96240a09d4 100644 --- a/rpcs3/Ini.cpp +++ b/rpcs3/Ini.cpp @@ -22,9 +22,9 @@ static bool StringToBool(const wxString str) static wxString BoolToString(const bool b) { - if(b) return "enable"; + if(b) return "true"; - return "disable"; + return "false"; } static wxSize StringToSize(const wxString str) diff --git a/rpcs3/git-version.h b/rpcs3/git-version.h new file mode 100644 index 0000000000..55c272acc7 --- /dev/null +++ b/rpcs3/git-version.h @@ -0,0 +1,6 @@ +// This is a generated file. + +#define RPCS3_GIT_VERSION "cd3ad0b" + +// If you don't want this file to update/recompile, change to 1. +#define RPCS3_GIT_VERSION_NO_UPDATE 0 diff --git a/rpcs3/rpcs3.h b/rpcs3/rpcs3.h index 6d1ff5911a..94dddbae9b 100644 --- a/rpcs3/rpcs3.h +++ b/rpcs3/rpcs3.h @@ -24,7 +24,7 @@ enum DbgCommand DID_START_EMU, DID_STARTED_EMU, DID_STOP_EMU, - DID_STOPED_EMU, + DID_STOPPED_EMU, DID_PAUSE_EMU, DID_PAUSED_EMU, DID_RESUME_EMU, diff --git a/rpcs3/rpcs3.vcxproj b/rpcs3/rpcs3.vcxproj index 09fce76d29..fb39fdb24f 100644 --- a/rpcs3/rpcs3.vcxproj +++ b/rpcs3/rpcs3.vcxproj @@ -128,8 +128,7 @@ %(IgnoreSpecificDefaultLibraries) - - + $(SolutionDir)\Utilities\git-version-gen.cmd diff --git a/rpcs3/rpcs3.vcxproj.user b/rpcs3/rpcs3.vcxproj.user new file mode 100644 index 0000000000..d7fe813d8f --- /dev/null +++ b/rpcs3/rpcs3.vcxproj.user @@ -0,0 +1,19 @@ + + + + $(SolutionDir)bin\ + WindowsLocalDebugger + + + $(SolutionDir)bin\ + WindowsLocalDebugger + + + $(SolutionDir)bin\ + WindowsLocalDebugger + + + $(SolutionDir)bin\ + WindowsLocalDebugger + + \ No newline at end of file diff --git a/rpcs3/stdafx.h b/rpcs3/stdafx.h index bd6a9ff2fe..dc4a743d78 100644 --- a/rpcs3/stdafx.h +++ b/rpcs3/stdafx.h @@ -179,7 +179,7 @@ static void safe_realloc(T* ptr, uint new_size) enum Status { - Runned, + Running, Paused, Stopped, Ready, @@ -206,4 +206,4 @@ enum Status #include "rpcs3.h" #define _PRGNAME_ "RPCS3" -#define _PRGVER_ "0.0.0.4" \ No newline at end of file +#define _PRGVER_ "0.0.0.4"