mirror of
https://github.com/palera1n/palera1n.git
synced 2026-10-24 02:59:49 +02:00
feat: wxWidgets gui
This commit is contained in:
53 files changed
+2494
-296
No files matched your search
+186
@@ -0,0 +1,186 @@
|
||||
#ifdef WITH_GUI
|
||||
|
||||
#include "event.hpp"
|
||||
|
||||
#include "wx/AppFrame.hpp"
|
||||
#include "state.hpp"
|
||||
#include "sequence.hpp"
|
||||
#include "utils/log.h"
|
||||
|
||||
#include <wx/wx.h>
|
||||
#include <thread>
|
||||
|
||||
#include <libimobiledevice/libimobiledevice.h>
|
||||
#include <libimobiledevice/lockdown.h>
|
||||
#include <libirecovery.h>
|
||||
|
||||
static inline DeviceState make_fresh_state()
|
||||
{
|
||||
return DeviceState{};
|
||||
}
|
||||
|
||||
void normal_device_event_cb(
|
||||
const idevice_event_t* event,
|
||||
void* user_data)
|
||||
{
|
||||
#ifdef WITH_GUI
|
||||
auto* frame = static_cast<MainFrame*>(user_data);
|
||||
if (!frame || !event)
|
||||
return;
|
||||
#endif
|
||||
|
||||
DeviceState state = make_fresh_state();
|
||||
|
||||
if (event->event == IDEVICE_DEVICE_REMOVE)
|
||||
{
|
||||
#ifdef WITH_GUI
|
||||
send_device_state(frame, state);
|
||||
#endif
|
||||
return;
|
||||
}
|
||||
|
||||
if (event->event != IDEVICE_DEVICE_ADD)
|
||||
return;
|
||||
|
||||
idevice_t device = nullptr;
|
||||
if (idevice_new(&device, event->udid) != IDEVICE_E_SUCCESS)
|
||||
return;
|
||||
|
||||
state.connected = true;
|
||||
state.mode = DeviceMode::Normal;
|
||||
state.udid = event->udid ? event->udid : "";
|
||||
|
||||
lockdownd_client_t client = nullptr;
|
||||
|
||||
if (lockdownd_client_new_with_handshake(device, &client, "palera1n") == LOCKDOWN_E_SUCCESS)
|
||||
{
|
||||
plist_t product = nullptr;
|
||||
plist_t version = nullptr;
|
||||
plist_t ecid = nullptr;
|
||||
|
||||
char *p = nullptr, *v = nullptr;
|
||||
uint64_t ecid_val = 0;
|
||||
|
||||
lockdownd_get_value(client, nullptr, "ProductType", &product);
|
||||
lockdownd_get_value(client, nullptr, "ProductVersion", &version);
|
||||
lockdownd_get_value(client, nullptr, "UniqueChipID", &ecid);
|
||||
|
||||
if (product) plist_get_string_val(product, &p);
|
||||
if (version) plist_get_string_val(version, &v);
|
||||
if (ecid) plist_get_uint_val(ecid, &ecid_val);
|
||||
|
||||
state.productType = p ? p : "";
|
||||
state.productVersion = v ? v : "";
|
||||
state.ecid = ecid_val;
|
||||
|
||||
free(p);
|
||||
free(v);
|
||||
|
||||
if (product) plist_free(product);
|
||||
if (version) plist_free(version);
|
||||
if (ecid) plist_free(ecid);
|
||||
|
||||
lockdownd_client_free(client);
|
||||
}
|
||||
|
||||
idevice_free(device);
|
||||
|
||||
state.isSupported = SequenceIsSupported(state.productType);
|
||||
|
||||
#ifdef WITH_GUI
|
||||
send_device_state(frame, state);
|
||||
#endif
|
||||
}
|
||||
|
||||
void recovery_device_event_cb(
|
||||
const irecv_device_event_t* event,
|
||||
void* user_data)
|
||||
{
|
||||
#ifdef WITH_GUI
|
||||
auto* frame = static_cast<MainFrame*>(user_data);
|
||||
if (!frame || !event)
|
||||
return;
|
||||
#endif
|
||||
|
||||
DeviceState state = make_fresh_state();
|
||||
|
||||
if (event->type == IRECV_DEVICE_REMOVE)
|
||||
{
|
||||
#ifdef WITH_GUI
|
||||
send_device_state(frame, state);
|
||||
#endif
|
||||
return;
|
||||
}
|
||||
|
||||
if (event->type != IRECV_DEVICE_ADD)
|
||||
return;
|
||||
|
||||
state.connected = true;
|
||||
|
||||
if (event->mode == IRECV_K_DFU_MODE)
|
||||
{
|
||||
state.mode = DeviceMode::DFU;
|
||||
#ifdef WITH_GUI
|
||||
send_device_state(frame, state);
|
||||
#endif
|
||||
return;
|
||||
}
|
||||
|
||||
if (!(event->mode == IRECV_K_RECOVERY_MODE_1 ||
|
||||
event->mode == IRECV_K_RECOVERY_MODE_2 ||
|
||||
event->mode == IRECV_K_RECOVERY_MODE_3 ||
|
||||
event->mode == IRECV_K_RECOVERY_MODE_4))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
state.mode = DeviceMode::Recovery;
|
||||
|
||||
uint64_t ecid_val = 0;
|
||||
if (event->device_info && event->device_info->ecid)
|
||||
ecid_val = event->device_info->ecid;
|
||||
state.ecid = ecid_val;
|
||||
|
||||
irecv_client_t client = nullptr;
|
||||
|
||||
int attempts = 0;
|
||||
const int max_attempts = 10;
|
||||
|
||||
while (attempts < max_attempts)
|
||||
{
|
||||
if (irecv_open_with_ecid(&client, state.ecid) == IRECV_E_SUCCESS)
|
||||
break;
|
||||
|
||||
attempts++;
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(300));
|
||||
}
|
||||
|
||||
if (client)
|
||||
{
|
||||
LOG("Opened client after %d attempts", attempts);
|
||||
|
||||
irecv_device_t device = nullptr;
|
||||
if (irecv_devices_get_device_by_client(client, &device) == IRECV_E_SUCCESS && device)
|
||||
{
|
||||
LOG("device product type: %s", device->product_type);
|
||||
LOG("device display name: %s", device->display_name);
|
||||
|
||||
state.productType = device->product_type ? device->product_type : "";
|
||||
state.displayName = device->display_name ? device->display_name : "";
|
||||
}
|
||||
|
||||
irecv_close(client);
|
||||
}
|
||||
else
|
||||
{
|
||||
LOG("Failed to open irecv client after retries");
|
||||
}
|
||||
|
||||
state.isSupported = SequenceIsSupported(state.productType);
|
||||
|
||||
#ifdef WITH_GUI
|
||||
send_device_state(frame, state);
|
||||
#endif
|
||||
}
|
||||
|
||||
#endif // WITH_GUI
|
||||
@@ -0,0 +1,21 @@
|
||||
#ifdef WITH_GUI
|
||||
|
||||
#ifndef EVENT_H
|
||||
#define EVENT_H
|
||||
|
||||
#include "state.hpp"
|
||||
|
||||
#include <wx/wx.h>
|
||||
|
||||
#include <libimobiledevice/libimobiledevice.h>
|
||||
#include <libimobiledevice/lockdown.h>
|
||||
#include <libirecovery.h>
|
||||
|
||||
class MainFrame;
|
||||
|
||||
void normal_device_event_cb(const idevice_event_t* event, void* user_data);
|
||||
void recovery_device_event_cb(const irecv_device_event_t* event, void* user_data);
|
||||
|
||||
#endif // EVENT_H
|
||||
|
||||
#endif // WITH_GUI
|
||||
+10
-9
@@ -5,12 +5,6 @@
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
|
||||
#if defined(__APPLE__)
|
||||
#include <CoreFoundation/CoreFoundation.h>
|
||||
#include <IOKit/IOKitLib.h>
|
||||
#include <IOKit/usb/IOUSBLib.h>
|
||||
#endif
|
||||
|
||||
#ifdef WITH_GUI
|
||||
# include <wx/wx.h>
|
||||
#endif
|
||||
@@ -21,9 +15,16 @@
|
||||
|
||||
void print_credits() {
|
||||
printf(
|
||||
":: ========================== ::\n"
|
||||
":: palera1n: " PALERAIN_VERSION "\n"
|
||||
":: ========================== ::\n\n"
|
||||
"#\n"
|
||||
"# palera1n: " PALERAIN_VERSION "\n"
|
||||
"#\n"
|
||||
"# ======== Made by =======\n"
|
||||
"# Made by: asdfugil, kok3shidoll, claration, mineek, staturnz\n"
|
||||
"# ======== Thanks to =======\n"
|
||||
"# Thanks to: llsc12, Nebula, Lrdsnow, nikias (libimobiledevice),\n"
|
||||
"# checkra1n team (Siguza, axi0mx, littlelailo et al.),\n"
|
||||
"# Procursus Team (Hayden Seay, Cameron Katri, Keto et.al)\n"
|
||||
"# ==========================\n\n"
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,88 @@
|
||||
#ifdef WITH_GUI
|
||||
|
||||
#include <nlohmann/json.hpp>
|
||||
#include "gen/DFUHelperDeviceInfo.h"
|
||||
#include "sequence.hpp"
|
||||
|
||||
DfuSequence ParseSequence(const std::string& deviceKey)
|
||||
{
|
||||
DfuSequence seq;
|
||||
|
||||
static nlohmann::json j = nlohmann::json::parse(
|
||||
DFUHelperDeviceInfo,
|
||||
DFUHelperDeviceInfo + DFUHelperDeviceInfo_len
|
||||
);
|
||||
|
||||
auto itMap = j["devicemap"].find(deviceKey);
|
||||
if (itMap == j["devicemap"].end())
|
||||
return seq;
|
||||
|
||||
const std::string deviceType = itMap.value();
|
||||
|
||||
auto itLayout = j["devicelayout"].find(deviceType);
|
||||
if (itLayout == j["devicelayout"].end())
|
||||
return seq;
|
||||
|
||||
const auto& layout = itLayout.value();
|
||||
|
||||
seq.imageName = layout.value("image_name", "");
|
||||
seq.imageWidth = layout.value("image_width", 0);
|
||||
seq.imageHeight = layout.value("image_height", 0);
|
||||
seq.imageOffsetX = layout.value("image_offset_x", 0);
|
||||
|
||||
if (layout.contains("buttons"))
|
||||
{
|
||||
for (auto it = layout["buttons"].begin();
|
||||
it != layout["buttons"].end();
|
||||
++it)
|
||||
{
|
||||
DfuButton btn;
|
||||
|
||||
btn.id = it.key();
|
||||
|
||||
const auto& b = it.value();
|
||||
|
||||
btn.name = b.value("name", "");
|
||||
btn.x = b.value("x", 0);
|
||||
btn.y = b.value("y", 0);
|
||||
|
||||
seq.buttons.push_back(std::move(btn));
|
||||
}
|
||||
}
|
||||
|
||||
const std::string sequenceKey =
|
||||
layout.value("sequence", "");
|
||||
|
||||
auto itSeq = j["sequences"].find(sequenceKey);
|
||||
if (itSeq == j["sequences"].end())
|
||||
return seq;
|
||||
|
||||
for (const auto& s : itSeq.value())
|
||||
{
|
||||
DfuStep step;
|
||||
|
||||
step.description = s.value("description", "");
|
||||
step.duration = s.value("duration", 0);
|
||||
step.action = s.value("action", "");
|
||||
|
||||
if (s.contains("buttons"))
|
||||
{
|
||||
for (const auto& b : s["buttons"])
|
||||
step.activeButtons.push_back(
|
||||
b.get<std::string>()
|
||||
);
|
||||
}
|
||||
|
||||
seq.steps.push_back(std::move(step));
|
||||
}
|
||||
|
||||
return seq;
|
||||
}
|
||||
|
||||
bool SequenceIsSupported(const std::string& deviceKey)
|
||||
{
|
||||
DfuSequence seq = ParseSequence(deviceKey);
|
||||
return !seq.steps.empty();
|
||||
}
|
||||
|
||||
#endif // WITH_GUI
|
||||
@@ -0,0 +1,40 @@
|
||||
#ifdef WITH_GUI
|
||||
|
||||
#ifndef SEQUENCE_H
|
||||
#define SEQUENCE_H
|
||||
|
||||
#include <string>
|
||||
|
||||
struct DfuButton
|
||||
{
|
||||
std::string id;
|
||||
std::string name;
|
||||
int x = 0;
|
||||
int y = 0;
|
||||
};
|
||||
|
||||
struct DfuStep
|
||||
{
|
||||
std::string description;
|
||||
int duration = 0;
|
||||
std::vector<std::string> activeButtons;
|
||||
std::string action;
|
||||
};
|
||||
|
||||
struct DfuSequence
|
||||
{
|
||||
std::string imageName;
|
||||
int imageWidth = 0;
|
||||
int imageHeight = 0;
|
||||
int imageOffsetX = 0;
|
||||
|
||||
std::vector<DfuButton> buttons;
|
||||
std::vector<DfuStep> steps;
|
||||
};
|
||||
|
||||
DfuSequence ParseSequence(const std::string& deviceKey);
|
||||
bool SequenceIsSupported(const std::string& deviceKey);
|
||||
|
||||
#endif // SEQUENCE_H
|
||||
|
||||
#endif // WITH_GUI
|
||||
@@ -0,0 +1,26 @@
|
||||
#ifndef STATE_H
|
||||
#define STATE_H
|
||||
|
||||
#include <string>
|
||||
|
||||
enum class DeviceMode
|
||||
{
|
||||
None,
|
||||
Normal,
|
||||
Recovery,
|
||||
DFU
|
||||
};
|
||||
|
||||
struct DeviceState
|
||||
{
|
||||
bool connected = false;
|
||||
bool isSupported = false;
|
||||
DeviceMode mode = DeviceMode::None;
|
||||
std::string productVersion;
|
||||
std::string productType;
|
||||
std::string displayName;
|
||||
std::string udid;
|
||||
uint64_t ecid;
|
||||
};
|
||||
|
||||
#endif // STATE_H
|
||||
-209
@@ -1,209 +0,0 @@
|
||||
#ifdef WITH_GUI
|
||||
|
||||
#include "app.hpp"
|
||||
#include <wx/wx.h>
|
||||
#include <wx/mstream.h>
|
||||
#include <wx/image.h>
|
||||
#include <wx/bitmap.h>
|
||||
#include <wx/statline.h>
|
||||
#include <string>
|
||||
#include "../utils/constants.h"
|
||||
#include "../utils/paleinfo.h"
|
||||
#include "../utils/log.h"
|
||||
#include "../exploit/logo.h"
|
||||
|
||||
class MainFrame;
|
||||
|
||||
class MainPanel : public wxPanel
|
||||
{
|
||||
public:
|
||||
explicit MainPanel(MainFrame* frame, wxWindow* parent);
|
||||
};
|
||||
|
||||
class SettingsPanel : public wxPanel
|
||||
{
|
||||
public:
|
||||
explicit SettingsPanel(MainFrame* frame, wxWindow* parent);
|
||||
};
|
||||
|
||||
class MainFrame : public wxFrame
|
||||
{
|
||||
public:
|
||||
MainFrame()
|
||||
: wxFrame(
|
||||
nullptr,
|
||||
wxID_ANY,
|
||||
"palera1n – Version beta " + wxString(PALERAIN_VERSION),
|
||||
wxDefaultPosition,
|
||||
wxSize(480, 360),
|
||||
wxDEFAULT_FRAME_STYLE & ~(wxMAXIMIZE_BOX | wxRESIZE_BORDER))
|
||||
{
|
||||
auto* root = new wxBoxSizer(wxVERTICAL);
|
||||
|
||||
m_main = new MainPanel(this, this);
|
||||
m_settings = new SettingsPanel(this, this);
|
||||
|
||||
root->Add(m_main, 1, wxEXPAND);
|
||||
root->Add(m_settings, 1, wxEXPAND);
|
||||
|
||||
SetSizer(root);
|
||||
|
||||
ShowMain();
|
||||
}
|
||||
|
||||
void ShowMain()
|
||||
{
|
||||
m_main->Show();
|
||||
m_settings->Hide();
|
||||
Layout();
|
||||
}
|
||||
|
||||
void ShowSettings()
|
||||
{
|
||||
m_main->Hide();
|
||||
m_settings->Show();
|
||||
Layout();
|
||||
}
|
||||
|
||||
private:
|
||||
MainPanel* m_main;
|
||||
SettingsPanel* m_settings;
|
||||
};
|
||||
|
||||
MainPanel::MainPanel(MainFrame* frame, wxWindow* parent)
|
||||
: wxPanel(parent)
|
||||
{
|
||||
auto* root = new wxBoxSizer(wxVERTICAL);
|
||||
|
||||
wxInitAllImageHandlers();
|
||||
|
||||
wxMemoryInputStream stream(resources_logo_png, resources_logo_png_len);
|
||||
wxImage img(stream, wxBITMAP_TYPE_PNG);
|
||||
wxBitmap bmp(img);
|
||||
auto* logo = new wxStaticBitmap(this, wxID_ANY, bmp);
|
||||
|
||||
auto* titletext = new wxStaticText(this, wxID_ANY, "Welcome to palera1n!");
|
||||
wxFont titlefont = titletext->GetFont();
|
||||
titlefont.SetWeight(wxFONTWEIGHT_BOLD);
|
||||
titletext->SetFont(titlefont);
|
||||
|
||||
auto* left = new wxBoxSizer(wxVERTICAL);
|
||||
left->Add(titletext, 0, wxTOP | wxLEFT, 10);
|
||||
|
||||
auto* row = new wxBoxSizer(wxHORIZONTAL);
|
||||
row->Add(left, 0, wxALIGN_TOP);
|
||||
row->AddStretchSpacer();
|
||||
row->Add(logo, 0, wxTOP | wxRIGHT, 10);
|
||||
root->Add(row, 0, wxEXPAND);
|
||||
|
||||
root->Add(new wxStaticLine(this), 0, wxEXPAND | wxALL, 10);
|
||||
|
||||
auto* credtext = new wxStaticText(this, wxID_ANY, "Made by: asdfugil, kok3shidoll, claration, mineek, staturnz\n\nThanks to: itsnebulalol, llsc12, lrdsnow, nikias (libimobiledevice), Checkra1n\n(Siguza, axi0mx, littlelailo et al.), Procursus (Hayden Seay, Cameron Katri,\nKeto et al.)\n\nWith 💖 from C (claration)");
|
||||
root->Add(credtext, 0, wxLEFT | wxRIGHT, 10);
|
||||
|
||||
root->Add(new wxStaticLine(this), 0, wxEXPAND | wxALL, 10);
|
||||
|
||||
auto* notetext = new wxStaticText(this, wxID_ANY, "NOTE: Please ensure you have a backup of your device before applying the\njailbreak. While data loss is unlikely, we won't be responsible if something goes\nwrong. Use at your own risk.");
|
||||
wxFont notefont = wxSystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT);
|
||||
notefont.SetPointSize(12);
|
||||
notefont.SetStyle(wxFONTSTYLE_ITALIC);
|
||||
notetext->SetFont(notefont);
|
||||
root->Add(notetext, 0, wxLEFT | wxRIGHT, 10);
|
||||
|
||||
root->AddStretchSpacer();
|
||||
|
||||
auto* btn = new wxButton(this, wxID_ANY, "Options");
|
||||
auto* sbtn = new wxButton(this, wxID_ANY, "Start");
|
||||
sbtn->Disable();
|
||||
btn->Bind(wxEVT_BUTTON, [frame](wxCommandEvent&)
|
||||
{
|
||||
frame->ShowSettings();
|
||||
});
|
||||
auto* bottomRow = new wxBoxSizer(wxHORIZONTAL);
|
||||
bottomRow->AddStretchSpacer();
|
||||
bottomRow->Add(btn, 0, wxALL, 10);
|
||||
bottomRow->Add(sbtn, 0, wxALL, 10);
|
||||
root->Add(bottomRow, 0, wxEXPAND);
|
||||
|
||||
SetSizer(root);
|
||||
}
|
||||
|
||||
SettingsPanel::SettingsPanel(MainFrame* frame, wxWindow* parent)
|
||||
: wxPanel(parent)
|
||||
{
|
||||
auto* root = new wxBoxSizer(wxVERTICAL);
|
||||
|
||||
root->Add(new wxStaticText(this, wxID_ANY,
|
||||
"You may set the following options. If you don't know what they mean you'll\n"
|
||||
"probably have no reason to set them."), 0, wxALL, 10);
|
||||
|
||||
auto* option_safemode = new wxCheckBox(this, wxID_ANY, "Safe Mode");
|
||||
auto* option_verbose = new wxCheckBox(this, wxID_ANY, "Verbose Boot");
|
||||
auto* option_revert = new wxCheckBox(this, wxID_ANY, "Restore System");
|
||||
auto* option_dark_blockchain = new wxCheckBox(this, wxID_ANY, "Dark Blockchain");
|
||||
|
||||
option_safemode->SetValue(palerain_flags & palerain_option_safemode);
|
||||
option_verbose->SetValue(palerain_flags & palerain_option_verbose_boot);
|
||||
option_revert->SetValue(palerain_flags & palerain_option_force_revert);
|
||||
option_dark_blockchain->SetValue(true);
|
||||
|
||||
option_safemode->Bind(wxEVT_CHECKBOX, [&](wxCommandEvent& e)
|
||||
{
|
||||
if (e.IsChecked())
|
||||
palerain_flags |= palerain_option_safemode;
|
||||
else
|
||||
palerain_flags &= ~palerain_option_safemode;
|
||||
});
|
||||
option_verbose->Bind(wxEVT_CHECKBOX, [&](wxCommandEvent& e)
|
||||
{
|
||||
if (e.IsChecked())
|
||||
palerain_flags |= palerain_option_verbose_boot;
|
||||
else
|
||||
palerain_flags &= ~palerain_option_verbose_boot;
|
||||
});
|
||||
option_revert->Bind(wxEVT_CHECKBOX, [&](wxCommandEvent& e)
|
||||
{
|
||||
if (e.IsChecked())
|
||||
palerain_flags |= palerain_option_force_revert;
|
||||
else
|
||||
palerain_flags &= ~palerain_option_force_revert;
|
||||
});
|
||||
|
||||
root->Add(option_safemode, 0, wxLEFT | wxRIGHT, 10);
|
||||
root->Add(option_verbose, 0, wxLEFT | wxRIGHT | wxTOP, 10);
|
||||
|
||||
root->Add(new wxStaticText(this, wxID_ANY, "Boot Arguments:"), 0, wxALL, 10);
|
||||
auto* bootArgs = new wxTextCtrl(this, wxID_ANY, "", wxDefaultPosition, wxDefaultSize);
|
||||
root->Add(bootArgs, 0, wxLEFT | wxRIGHT | wxEXPAND, 30);
|
||||
|
||||
root->Add(option_revert, 0, wxLEFT | wxRIGHT | wxTOP, 10);
|
||||
root->Add(option_dark_blockchain, 0, wxLEFT | wxRIGHT | wxTOP, 10);
|
||||
|
||||
root->AddStretchSpacer();
|
||||
|
||||
auto* back = new wxButton(this, wxID_ANY, "Back");
|
||||
|
||||
back->Bind(wxEVT_BUTTON, [frame](wxCommandEvent&)
|
||||
{
|
||||
frame->ShowMain();
|
||||
});
|
||||
|
||||
auto* bottomRow = new wxBoxSizer(wxHORIZONTAL);
|
||||
bottomRow->AddStretchSpacer();
|
||||
bottomRow->Add(back, 0, wxALL, 10);
|
||||
|
||||
root->Add(bottomRow, 0, wxEXPAND);
|
||||
|
||||
SetSizer(root);
|
||||
}
|
||||
|
||||
bool PalerainApp::OnInit()
|
||||
{
|
||||
auto* frame = new MainFrame();
|
||||
frame->Show(true);
|
||||
return true;
|
||||
}
|
||||
|
||||
wxIMPLEMENT_APP_NO_MAIN(PalerainApp);
|
||||
|
||||
#endif // WITH_GUI
|
||||
@@ -1,10 +0,0 @@
|
||||
#ifdef WITH_GUI
|
||||
#pragma once
|
||||
#include <wx/wx.h>
|
||||
|
||||
class PalerainApp : public wxApp {
|
||||
public:
|
||||
bool OnInit() override;
|
||||
};
|
||||
|
||||
#endif // WITH_GUI
|
||||
@@ -1,7 +1,7 @@
|
||||
#ifndef CONSTANTS_H
|
||||
#define CONSTANTS_H
|
||||
|
||||
#define PALERAIN_VERSION "2.4.0"
|
||||
#define PALERAIN_VERSION "3.0.0"
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
|
||||
@@ -0,0 +1,165 @@
|
||||
#ifdef WITH_GUI
|
||||
|
||||
#include "AppFrame.hpp"
|
||||
|
||||
#include <wx/wx.h>
|
||||
#include "../utils/constants.h"
|
||||
#include "../event.hpp"
|
||||
|
||||
#include <thread>
|
||||
|
||||
#include <libimobiledevice/libimobiledevice.h>
|
||||
#include <libimobiledevice/lockdown.h>
|
||||
#include <libirecovery.h>
|
||||
|
||||
wxDEFINE_EVENT(EVT_DEVICE_STATE_UPDATE, wxCommandEvent);
|
||||
|
||||
void send_device_state(MainFrame* frame, const DeviceState& state)
|
||||
{
|
||||
wxTheApp->CallAfter([frame, state]() {
|
||||
auto* evt = new wxCommandEvent(EVT_DEVICE_STATE_UPDATE);
|
||||
evt->SetClientData(new DeviceState(state));
|
||||
wxPostEvent(frame, *evt);
|
||||
});
|
||||
}
|
||||
|
||||
MainFrame::MainFrame()
|
||||
: wxFrame(nullptr,
|
||||
wxID_ANY,
|
||||
"palera1n – Version beta " + wxString(PALERAIN_VERSION),
|
||||
wxDefaultPosition,
|
||||
wxSize(480, 360),
|
||||
wxDEFAULT_FRAME_STYLE & ~(wxMAXIMIZE_BOX | wxRESIZE_BORDER))
|
||||
{
|
||||
auto* root = new wxBoxSizer(wxVERTICAL);
|
||||
|
||||
m_main = new MainPanel(this, this);
|
||||
m_settings = new SettingsPanel(this, this);
|
||||
m_recovery = new RecoveryPanel(this, this);
|
||||
m_dfu = new DfuPanel(this, this);
|
||||
m_exploit = new ExploitPanel(this, this);
|
||||
|
||||
root->Add(m_main, 1, wxEXPAND);
|
||||
root->Add(m_settings, 1, wxEXPAND);
|
||||
root->Add(m_recovery, 1, wxEXPAND);
|
||||
root->Add(m_dfu, 1, wxEXPAND);
|
||||
root->Add(m_exploit, 1, wxEXPAND);
|
||||
|
||||
SetSizer(root);
|
||||
|
||||
Bind(EVT_DEVICE_STATE_UPDATE, [this](wxCommandEvent& e)
|
||||
{
|
||||
auto* state = static_cast<DeviceState*>(e.GetClientData());
|
||||
|
||||
if (!state)
|
||||
return;
|
||||
|
||||
UpdateDeviceState(*state);
|
||||
|
||||
delete state;
|
||||
});
|
||||
|
||||
ShowMain();
|
||||
|
||||
std::thread([this]()
|
||||
{
|
||||
idevice_event_subscribe(normal_device_event_cb, this);
|
||||
|
||||
while (true)
|
||||
sleep(1);
|
||||
}).detach();
|
||||
|
||||
std::thread([this]()
|
||||
{
|
||||
static irecv_device_event_context_t ctx = nullptr;
|
||||
|
||||
irecv_device_event_subscribe(
|
||||
&ctx,
|
||||
recovery_device_event_cb,
|
||||
this
|
||||
);
|
||||
|
||||
while (true)
|
||||
sleep(1);
|
||||
}).detach();
|
||||
}
|
||||
|
||||
void MainFrame::ShowMain()
|
||||
{
|
||||
m_main->Show();
|
||||
m_settings->Hide();
|
||||
m_recovery->Hide();
|
||||
m_dfu->Hide();
|
||||
m_exploit->Hide();
|
||||
Layout();
|
||||
}
|
||||
|
||||
void MainFrame::ShowSettings()
|
||||
{
|
||||
m_main->Hide();
|
||||
m_settings->Show();
|
||||
m_recovery->Hide();
|
||||
m_dfu->Hide();
|
||||
m_exploit->Hide();
|
||||
Layout();
|
||||
}
|
||||
|
||||
void MainFrame::ShowRecovery()
|
||||
{
|
||||
m_main->Hide();
|
||||
m_settings->Hide();
|
||||
m_recovery->Show();
|
||||
m_dfu->Hide();
|
||||
m_exploit->Hide();
|
||||
Layout();
|
||||
}
|
||||
|
||||
void MainFrame::ShowDfu()
|
||||
{
|
||||
m_main->Hide();
|
||||
m_settings->Hide();
|
||||
m_recovery->Hide();
|
||||
m_dfu->Show();
|
||||
m_dfu->LoadDevice(m_state.productType);
|
||||
m_exploit->Hide();
|
||||
Layout();
|
||||
}
|
||||
|
||||
void MainFrame::ShowExploit()
|
||||
{
|
||||
m_main->Hide();
|
||||
m_settings->Hide();
|
||||
m_recovery->Hide();
|
||||
m_dfu->Hide();
|
||||
m_exploit->Show();
|
||||
m_exploit->StartExploit();
|
||||
Layout();
|
||||
}
|
||||
|
||||
void MainFrame::UpdateDeviceState(const DeviceState& state)
|
||||
{
|
||||
m_state = state;
|
||||
|
||||
if (m_main)
|
||||
m_main->SetDeviceState(m_state);
|
||||
|
||||
if (m_recovery)
|
||||
m_recovery->SetDeviceState(m_state);
|
||||
|
||||
if (m_dfu)
|
||||
m_dfu->SetDeviceState(m_state);
|
||||
|
||||
if (m_exploit)
|
||||
m_exploit->SetDeviceState(m_state);
|
||||
}
|
||||
|
||||
bool PalerainApp::OnInit()
|
||||
{
|
||||
auto* frame = new MainFrame();
|
||||
frame->Show(true);
|
||||
return true;
|
||||
}
|
||||
|
||||
wxIMPLEMENT_APP_NO_MAIN(PalerainApp);
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,53 @@
|
||||
#ifndef APPFRAME_H
|
||||
#define APPFRAME_H
|
||||
|
||||
#ifdef WITH_GUI
|
||||
|
||||
#include <wx/wx.h>
|
||||
#include "../state.hpp"
|
||||
|
||||
#include "MainPanel.hpp"
|
||||
#include "SettingsPanel.hpp"
|
||||
#include "RecoveryPanel.hpp"
|
||||
#include "DfuPanel.hpp"
|
||||
#include "ExploitPanel.hpp"
|
||||
|
||||
void send_device_state(MainFrame* frame, const DeviceState& state);
|
||||
|
||||
class MainFrame : public wxFrame
|
||||
{
|
||||
public:
|
||||
MainFrame();
|
||||
|
||||
void ShowMain();
|
||||
void ShowSettings();
|
||||
void ShowRecovery();
|
||||
void ShowDfu();
|
||||
void ShowExploit();
|
||||
|
||||
const DeviceState& GetDeviceState() const { return m_state; }
|
||||
void UpdateDeviceState(const DeviceState& state);
|
||||
|
||||
MainPanel* GetMainPanel() const { return m_main; }
|
||||
SettingsPanel* GetSettingsPanel() const { return m_settings; }
|
||||
RecoveryPanel* GetRecoveryPanel() const { return m_recovery; }
|
||||
DfuPanel* GetDfuPanel() const { return m_dfu; }
|
||||
ExploitPanel* GetExploitPanel() const { return m_exploit; }
|
||||
private:
|
||||
MainPanel* m_main = nullptr;
|
||||
SettingsPanel* m_settings = nullptr;
|
||||
RecoveryPanel* m_recovery = nullptr;
|
||||
DfuPanel* m_dfu = nullptr;
|
||||
ExploitPanel* m_exploit = nullptr;
|
||||
|
||||
DeviceState m_state;
|
||||
};
|
||||
|
||||
class PalerainApp : public wxApp {
|
||||
public:
|
||||
bool OnInit() override;
|
||||
};
|
||||
|
||||
#endif // WITH_GUI
|
||||
|
||||
#endif // APPFRAME_H
|
||||
@@ -0,0 +1,38 @@
|
||||
#ifndef DEVICEPANEL_H
|
||||
#define DEVICEPANEL_H
|
||||
|
||||
#include <wx/wx.h>
|
||||
#include "../state.hpp"
|
||||
|
||||
class MainFrame;
|
||||
class DevicePanel : public wxPanel
|
||||
{
|
||||
public:
|
||||
DevicePanel(MainFrame* frame, wxWindow* parent)
|
||||
: wxPanel(parent),
|
||||
m_frame(frame)
|
||||
{
|
||||
}
|
||||
|
||||
virtual void SetDeviceState(const DeviceState& state)
|
||||
{
|
||||
m_deviceState = state;
|
||||
}
|
||||
|
||||
protected:
|
||||
MainFrame* GetMainFrame() const
|
||||
{
|
||||
return m_frame;
|
||||
}
|
||||
|
||||
const DeviceState& GetDeviceState() const
|
||||
{
|
||||
return m_deviceState;
|
||||
}
|
||||
|
||||
private:
|
||||
MainFrame* m_frame;
|
||||
DeviceState m_deviceState;
|
||||
};
|
||||
|
||||
#endif // DEVICEPANEL_H
|
||||
@@ -0,0 +1,427 @@
|
||||
#ifdef WITH_GUI
|
||||
|
||||
#include "DfuPanel.hpp"
|
||||
|
||||
#include "AppFrame.hpp"
|
||||
#include "DevicePanel.hpp"
|
||||
#include "../state.hpp"
|
||||
#include "../sequence.hpp"
|
||||
#include "../utils/log.h"
|
||||
|
||||
#include <thread>
|
||||
|
||||
#include <wx/wx.h>
|
||||
#include <wx/mstream.h>
|
||||
#include <wx/image.h>
|
||||
#include <wx/bitmap.h>
|
||||
|
||||
#include "../gen/images/atv_std_brd.h"
|
||||
#include "../gen/images/ipad.h"
|
||||
#include "../gen/images/ipadmini.h"
|
||||
#include "../gen/images/iphone6s.h"
|
||||
#include "../gen/images/iphone7.h"
|
||||
#include "../gen/images/iphone8.h"
|
||||
#include "../gen/images/iphonese.h"
|
||||
#include "../gen/images/iphonex.h"
|
||||
#include "../gen/images/ipodtouch.h"
|
||||
#include "../gen/images/siriremote.h"
|
||||
|
||||
#include <libirecovery.h>
|
||||
|
||||
static const wxColour kActive =
|
||||
wxSystemSettings::GetColour(wxSYS_COLOUR_HIGHLIGHTTEXT);
|
||||
|
||||
static const wxColour kInactive =
|
||||
wxSystemSettings::GetColour(wxSYS_COLOUR_GRAYTEXT);
|
||||
|
||||
DfuPanel::DfuPanel(MainFrame* frame, wxWindow* parent)
|
||||
: DevicePanel(frame, parent)
|
||||
{
|
||||
auto* root = new wxBoxSizer(wxVERTICAL);
|
||||
|
||||
m_headerText = new wxStaticText(this, wxID_ANY, "Time to put the device into DFU mode. Locate the buttons as marked below\non your device and check the instructions on the right.");
|
||||
root->Add(m_headerText, 0, wxALL, 10);
|
||||
|
||||
auto* contentRow = new wxBoxSizer(wxHORIZONTAL);
|
||||
|
||||
m_devicePanel = new wxPanel(this);
|
||||
m_devicePanel->SetMinSize(wxSize(310, 260));
|
||||
|
||||
m_deviceImage = new wxStaticBitmap(
|
||||
m_devicePanel,
|
||||
wxID_ANY,
|
||||
wxBitmap()
|
||||
);
|
||||
|
||||
contentRow->Add(m_devicePanel, 1, wxTOP, 10);
|
||||
m_stepsSizer = new wxBoxSizer(wxVERTICAL);
|
||||
contentRow->Add(m_stepsSizer, 2, wxEXPAND | wxALL, 0);
|
||||
|
||||
root->Add(contentRow, 1, wxEXPAND);
|
||||
|
||||
m_backButton = new wxButton(this, wxID_ANY, "Back");
|
||||
m_startButton = new wxButton(this, wxID_ANY, "Start");
|
||||
|
||||
m_backButton->Bind(wxEVT_BUTTON, [frame](wxCommandEvent&)
|
||||
{
|
||||
frame->ShowMain();
|
||||
});
|
||||
|
||||
m_startButton->Bind(wxEVT_BUTTON, [this](wxCommandEvent&)
|
||||
{
|
||||
m_isEnteringDfu = true;
|
||||
m_backButton->Disable();
|
||||
m_startButton->Disable();
|
||||
|
||||
StartSequence(m_sequence);
|
||||
});
|
||||
|
||||
m_timer.Bind(wxEVT_TIMER, [this](wxTimerEvent&)
|
||||
{
|
||||
RunStep();
|
||||
});
|
||||
|
||||
m_stagnentTimer.Bind(wxEVT_TIMER, [this](wxTimerEvent&)
|
||||
{
|
||||
m_backButton->Enable();
|
||||
m_startButton->Enable();
|
||||
GetMainFrame()->ShowExploit();
|
||||
});
|
||||
|
||||
auto* bottomRow = new wxBoxSizer(wxHORIZONTAL);
|
||||
|
||||
bottomRow->AddStretchSpacer();
|
||||
bottomRow->Add(m_backButton, 0, wxALL, 10);
|
||||
bottomRow->Add(m_startButton, 0, wxALL, 10);
|
||||
|
||||
root->Add(bottomRow, 0, wxEXPAND);
|
||||
|
||||
SetSizer(root);
|
||||
}
|
||||
|
||||
void DfuPanel::SetDeviceState(const DeviceState& state)
|
||||
{
|
||||
DevicePanel::SetDeviceState(state);
|
||||
|
||||
if (!(this->IsShown()))
|
||||
return;
|
||||
|
||||
if (m_isEnteringDfu && state.mode == DeviceMode::DFU)
|
||||
{
|
||||
m_isEnteringDfu = false;
|
||||
m_headerText->SetLabel("Device entered DFU mode successfully.\n");
|
||||
|
||||
if (!m_stagnentTimer.IsRunning())
|
||||
m_stagnentTimer.StartOnce(3000);
|
||||
}
|
||||
|
||||
if (m_isEnteringDfu && state.mode == DeviceMode::Normal)
|
||||
{
|
||||
m_isEnteringDfu = false;
|
||||
m_backButton->Enable();
|
||||
m_startButton->Enable();
|
||||
GetMainFrame()->ShowRecovery();
|
||||
GetMainFrame()->GetRecoveryPanel()->SetStatusText("Hmm... It seems like the device didn't enter DFU mode.");
|
||||
}
|
||||
|
||||
if (m_isEnteringDfu && state.mode == DeviceMode::Recovery)
|
||||
{
|
||||
m_isEnteringDfu = false;
|
||||
m_backButton->Enable();
|
||||
m_startButton->Enable();
|
||||
GetMainFrame()->ShowMain();
|
||||
}
|
||||
}
|
||||
|
||||
void DfuPanel::LoadDevice(const std::string& productType)
|
||||
{
|
||||
m_sequence = ParseSequence(productType);
|
||||
m_index = 0;
|
||||
|
||||
m_deviceImage->SetBitmap(wxBitmap());
|
||||
|
||||
for (auto* lbl : m_stepLabels)
|
||||
lbl->Destroy();
|
||||
|
||||
m_stepLabels.clear();
|
||||
m_stepsSizer->Clear(false);
|
||||
|
||||
for (auto* lbl : m_buttonLabels)
|
||||
lbl->Destroy();
|
||||
|
||||
m_buttonLabels.clear();
|
||||
|
||||
const unsigned char* imageData = nullptr;
|
||||
size_t imageSize = 0;
|
||||
|
||||
if (m_sequence.imageName == "ipadmini")
|
||||
{
|
||||
imageData = ipadmini;
|
||||
imageSize = ipadmini_len;
|
||||
}
|
||||
else if (m_sequence.imageName == "ipad")
|
||||
{
|
||||
imageData = ipad;
|
||||
imageSize = ipad_len;
|
||||
}
|
||||
else if (m_sequence.imageName == "iphonese")
|
||||
{
|
||||
imageData = iphonese;
|
||||
imageSize = iphonese_len;
|
||||
}
|
||||
else if (m_sequence.imageName == "iphone6s")
|
||||
{
|
||||
imageData = iphone6s;
|
||||
imageSize = iphone6s_len;
|
||||
}
|
||||
else if (m_sequence.imageName == "iphone7")
|
||||
{
|
||||
imageData = iphone7;
|
||||
imageSize = iphone7_len;
|
||||
}
|
||||
else if (m_sequence.imageName == "iphone8")
|
||||
{
|
||||
imageData = iphone8;
|
||||
imageSize = iphone8_len;
|
||||
}
|
||||
else if (m_sequence.imageName == "iphonex")
|
||||
{
|
||||
imageData = iphonex;
|
||||
imageSize = iphonex_len;
|
||||
}
|
||||
else if (m_sequence.imageName == "ipodtouch")
|
||||
{
|
||||
imageData = ipodtouch;
|
||||
imageSize = ipodtouch_len;
|
||||
}
|
||||
else if (m_sequence.imageName == "siriremote")
|
||||
{
|
||||
imageData = siriremote;
|
||||
imageSize = siriremote_len;
|
||||
}
|
||||
else if (m_sequence.imageName == "atv_std_brd")
|
||||
{
|
||||
imageData = atv_std_brd;
|
||||
imageSize = atv_std_brd_len;
|
||||
}
|
||||
|
||||
if (imageData)
|
||||
{
|
||||
wxMemoryInputStream stream(imageData, imageSize);
|
||||
|
||||
wxImage img(stream, wxBITMAP_TYPE_PNG);
|
||||
|
||||
if (img.IsOk())
|
||||
{
|
||||
img = img.Scale(
|
||||
m_sequence.imageWidth,
|
||||
m_sequence.imageHeight,
|
||||
wxIMAGE_QUALITY_HIGH
|
||||
);
|
||||
|
||||
m_deviceImage->SetBitmap(wxBitmap(img));
|
||||
m_deviceImage->SetPosition(wxPoint(m_sequence.imageOffsetX, 0));
|
||||
}
|
||||
}
|
||||
|
||||
for (const auto& button : m_sequence.buttons)
|
||||
{
|
||||
auto* lbl = new wxStaticText(
|
||||
m_devicePanel,
|
||||
wxID_ANY,
|
||||
button.name
|
||||
);
|
||||
|
||||
lbl->SetPosition(wxPoint(button.x, button.y));
|
||||
|
||||
m_buttonLabels.push_back(lbl);
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < m_sequence.steps.size(); i++)
|
||||
{
|
||||
const auto& step = m_sequence.steps[i];
|
||||
|
||||
wxString label = wxString::Format(
|
||||
"%zu. %s (%d)",
|
||||
i + 1,
|
||||
step.description,
|
||||
step.duration
|
||||
);
|
||||
|
||||
auto* lbl = new wxStaticText(
|
||||
this,
|
||||
wxID_ANY,
|
||||
label
|
||||
);
|
||||
|
||||
lbl->SetForegroundColour(i == 0 ? kActive : kInactive);
|
||||
|
||||
m_stepsSizer->Add(lbl, 0, wxBOTTOM, 5);
|
||||
m_stepLabels.push_back(lbl);
|
||||
}
|
||||
|
||||
m_devicePanel->Layout();
|
||||
m_stepsSizer->Layout();
|
||||
|
||||
Layout();
|
||||
Fit();
|
||||
Refresh();
|
||||
}
|
||||
|
||||
void DfuPanel::StartSequence(const DfuSequence& seq)
|
||||
{
|
||||
m_sequence = seq;
|
||||
m_index = 0;
|
||||
m_stepRemaining = -1;
|
||||
m_actionExecutedIndex = -1;
|
||||
|
||||
RunStep();
|
||||
m_timer.StartOnce(1000);
|
||||
}
|
||||
|
||||
void DfuPanel::RunStep()
|
||||
{
|
||||
if (m_sequence.steps.empty())
|
||||
return;
|
||||
|
||||
if (m_index >= m_sequence.steps.size())
|
||||
{
|
||||
for (auto* lbl : m_stepLabels)
|
||||
lbl->SetForegroundColour(kInactive);
|
||||
|
||||
Layout();
|
||||
Refresh();
|
||||
|
||||
m_timer.Stop();
|
||||
return;
|
||||
}
|
||||
|
||||
const auto& step = m_sequence.steps[m_index];
|
||||
|
||||
if (m_stepRemaining < 0)
|
||||
m_stepRemaining = step.duration;
|
||||
|
||||
if (m_stepRemaining > 0)
|
||||
--m_stepRemaining;
|
||||
|
||||
bool finished = (m_stepRemaining == 0);
|
||||
size_t completedIndex = m_index;
|
||||
|
||||
if (finished)
|
||||
{
|
||||
++m_index;
|
||||
m_stepRemaining = -1;
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < m_stepLabels.size(); i++)
|
||||
{
|
||||
auto* lbl = m_stepLabels[i];
|
||||
|
||||
if (i == m_index)
|
||||
{
|
||||
lbl->SetForegroundColour(kActive);
|
||||
|
||||
lbl->SetLabel(wxString::Format(
|
||||
"%zu. %s (%d)",
|
||||
i + 1,
|
||||
m_sequence.steps[i].description,
|
||||
m_stepRemaining < 0 ? m_sequence.steps[i].duration : m_stepRemaining
|
||||
));
|
||||
}
|
||||
else if (finished && i == completedIndex)
|
||||
{
|
||||
lbl->SetForegroundColour(kInactive);
|
||||
|
||||
lbl->SetLabel(wxString::Format(
|
||||
"%zu. %s (0)",
|
||||
i + 1,
|
||||
m_sequence.steps[i].description
|
||||
));
|
||||
}
|
||||
else
|
||||
{
|
||||
lbl->SetForegroundColour(kInactive);
|
||||
}
|
||||
}
|
||||
|
||||
for (auto* lbl : m_buttonLabels)
|
||||
lbl->SetForegroundColour(kInactive);
|
||||
|
||||
const auto& activeStep =
|
||||
(m_index < m_sequence.steps.size())
|
||||
? m_sequence.steps[m_index]
|
||||
: m_sequence.steps.back();
|
||||
|
||||
for (const auto& buttonId : activeStep.activeButtons)
|
||||
{
|
||||
for (const auto& btn : m_sequence.buttons)
|
||||
{
|
||||
if (btn.id == buttonId)
|
||||
{
|
||||
for (auto* lbl : m_buttonLabels)
|
||||
{
|
||||
if (lbl->GetLabel() == btn.name)
|
||||
lbl->SetForegroundColour(kActive);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (m_index < m_sequence.steps.size())
|
||||
{
|
||||
const auto& activeStep = m_sequence.steps[m_index];
|
||||
|
||||
if (!activeStep.action.empty() && m_actionExecutedIndex != m_index)
|
||||
{
|
||||
m_actionExecutedIndex = m_index;
|
||||
|
||||
if (activeStep.action == "reboot")
|
||||
{
|
||||
Reboot();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Layout();
|
||||
Refresh();
|
||||
Update();
|
||||
|
||||
m_timer.StartOnce(1000);
|
||||
}
|
||||
|
||||
void DfuPanel::Reboot()
|
||||
{
|
||||
std::thread([this]()
|
||||
{
|
||||
const auto state = GetDeviceState();
|
||||
|
||||
if (!state.connected || state.mode != DeviceMode::Recovery)
|
||||
return;
|
||||
|
||||
irecv_client_t client = nullptr;
|
||||
|
||||
int attempts = 0;
|
||||
const int max_attempts = 8;
|
||||
|
||||
while (attempts < max_attempts)
|
||||
{
|
||||
if (irecv_open_with_ecid(&client, state.ecid) == IRECV_E_SUCCESS)
|
||||
break;
|
||||
|
||||
attempts++;
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(250));
|
||||
}
|
||||
|
||||
if (!client)
|
||||
{
|
||||
return;
|
||||
}
|
||||
irecv_setenv(client, "auto-boot", "true");
|
||||
irecv_saveenv(client);
|
||||
irecv_reboot(client);
|
||||
|
||||
irecv_close(client);
|
||||
}).detach();
|
||||
}
|
||||
|
||||
#endif // WITH_GUI
|
||||
@@ -0,0 +1,47 @@
|
||||
#ifndef DFUPANEL_H
|
||||
#define DFUPANEL_H
|
||||
|
||||
#ifdef WITH_GUI
|
||||
|
||||
#include <wx/wx.h>
|
||||
#include "DevicePanel.hpp"
|
||||
#include "../state.hpp"
|
||||
#include "../sequence.hpp"
|
||||
|
||||
class DfuPanel;
|
||||
class DfuPanel : public DevicePanel
|
||||
{
|
||||
public:
|
||||
explicit DfuPanel(MainFrame* frame, wxWindow* parent);
|
||||
void SetDeviceState(const DeviceState& state) override;
|
||||
void LoadDevice(const std::string& productType);
|
||||
void StartSequence(const DfuSequence& seq);
|
||||
void RunStep();
|
||||
void Reboot();
|
||||
private:
|
||||
wxStaticText* m_headerText = nullptr;
|
||||
wxButton* m_backButton = nullptr;
|
||||
wxButton* m_startButton = nullptr;
|
||||
|
||||
wxBoxSizer* m_stepsSizer = nullptr;
|
||||
|
||||
std::vector<wxStaticText*> m_stepLabels;
|
||||
|
||||
DfuSequence m_sequence;
|
||||
size_t m_index = 0;
|
||||
int m_stepRemaining = 0;
|
||||
|
||||
wxTimer m_timer;
|
||||
wxTimer m_stagnentTimer;
|
||||
bool m_isEnteringDfu = false;
|
||||
|
||||
wxPanel* m_devicePanel = nullptr;
|
||||
wxStaticBitmap* m_deviceImage = nullptr;
|
||||
std::vector<wxStaticText*> m_buttonLabels;
|
||||
size_t m_actionExecutedIndex = std::numeric_limits<size_t>::max();
|
||||
};
|
||||
|
||||
|
||||
#endif // WITH_GUI
|
||||
|
||||
#endif // DFUPANEL_H
|
||||
@@ -0,0 +1,72 @@
|
||||
#ifdef WITH_GUI
|
||||
|
||||
#include "ExploitPanel.hpp"
|
||||
|
||||
#include <thread>
|
||||
|
||||
#include <wx/wx.h>
|
||||
|
||||
#include "AppFrame.hpp"
|
||||
#include "DevicePanel.hpp"
|
||||
#include "../state.hpp"
|
||||
#include "../utils/log.h"
|
||||
|
||||
ExploitPanel::ExploitPanel(MainFrame* frame, wxWindow* parent)
|
||||
: DevicePanel(frame, parent)
|
||||
{
|
||||
auto* root = new wxBoxSizer(wxVERTICAL);
|
||||
auto* text = new wxStaticText(this, wxID_ANY,
|
||||
"Installing jailbreak, this will take a moment. If the device asks for a passcode\nplease enter it. Do not disconnect the device until finished.");
|
||||
|
||||
m_statusText = new wxStaticText(this, wxID_ANY, "");
|
||||
|
||||
root->Add(text, 0, wxALL, 10);
|
||||
root->Add(m_statusText, 0, wxALL, 10);
|
||||
|
||||
m_backButton = new wxButton(this, wxID_ANY, "Back");
|
||||
m_backButton->Disable();
|
||||
m_backButton->Bind(wxEVT_BUTTON, [frame](wxCommandEvent&)
|
||||
{
|
||||
frame->ShowMain();
|
||||
});
|
||||
|
||||
auto* bottomRow = new wxBoxSizer(wxHORIZONTAL);
|
||||
bottomRow->AddStretchSpacer();
|
||||
bottomRow->Add(m_backButton, 0, wxALL, 10);
|
||||
|
||||
root->AddStretchSpacer();
|
||||
root->Add(bottomRow, 0, wxEXPAND);
|
||||
|
||||
SetSizer(root);
|
||||
}
|
||||
|
||||
void ExploitPanel::SetDeviceState(const DeviceState& state)
|
||||
{
|
||||
DevicePanel::SetDeviceState(state);
|
||||
|
||||
if (!(this->IsShown()))
|
||||
return;
|
||||
|
||||
if (state.mode == DeviceMode::Normal)
|
||||
{
|
||||
m_statusText->SetLabel("Done.");
|
||||
m_backButton->Enable();
|
||||
}
|
||||
}
|
||||
|
||||
void ExploitPanel::StartExploit()
|
||||
{
|
||||
m_statusText->SetLabel("Exploiting device...");
|
||||
m_backButton->Disable();
|
||||
|
||||
std::thread([this]()
|
||||
{
|
||||
const auto state = GetDeviceState();
|
||||
if (!state.connected || state.mode != DeviceMode::DFU)
|
||||
return;
|
||||
|
||||
LOG("Starting exploit on worker thread...");
|
||||
}).detach();
|
||||
}
|
||||
|
||||
#endif // WITH_GUI
|
||||
@@ -0,0 +1,24 @@
|
||||
#ifndef EXPLOITPANEL_HPP
|
||||
#define EXPLOITPANEL_HPP
|
||||
|
||||
#ifdef WITH_GUI
|
||||
|
||||
#include <wx/wx.h>
|
||||
#include "DevicePanel.hpp"
|
||||
#include "../state.hpp"
|
||||
|
||||
class ExploitPanel;
|
||||
class ExploitPanel : public DevicePanel
|
||||
{
|
||||
public:
|
||||
explicit ExploitPanel(MainFrame* frame, wxWindow* parent);
|
||||
void SetDeviceState(const DeviceState& state) override;
|
||||
void StartExploit();
|
||||
private:
|
||||
wxButton* m_backButton = nullptr;
|
||||
wxStaticText* m_statusText = nullptr;
|
||||
};
|
||||
|
||||
#endif // WITH_GUI
|
||||
|
||||
#endif // EXPLOITPANEL_HPP
|
||||
@@ -0,0 +1,164 @@
|
||||
#ifdef WITH_GUI
|
||||
|
||||
#include "MainPanel.hpp"
|
||||
|
||||
#include <wx/wx.h>
|
||||
#include <wx/mstream.h>
|
||||
#include <wx/image.h>
|
||||
#include <wx/bitmap.h>
|
||||
#include <wx/statline.h>
|
||||
|
||||
#include "AppFrame.hpp"
|
||||
#include "DevicePanel.hpp"
|
||||
#include "../state.hpp"
|
||||
|
||||
#include "../gen/images/logo.h"
|
||||
|
||||
const char* deviceTextString = "Connect your iPhone, iPod touch, iPad, or AppleTV to begin.";
|
||||
|
||||
MainPanel::MainPanel(MainFrame* frame, wxWindow* parent)
|
||||
: DevicePanel(frame, parent)
|
||||
{
|
||||
auto* root = new wxBoxSizer(wxVERTICAL);
|
||||
|
||||
wxInitAllImageHandlers();
|
||||
|
||||
wxMemoryInputStream stream(logo, logo_len);
|
||||
wxImage img(stream, wxBITMAP_TYPE_PNG);
|
||||
wxBitmap bmp(img);
|
||||
|
||||
auto* logo = new wxStaticBitmap(this, wxID_ANY, bmp);
|
||||
|
||||
auto* titletext = new wxStaticText(this, wxID_ANY, "Welcome to palera1n!");
|
||||
wxFont titlefont = titletext->GetFont();
|
||||
titlefont.SetWeight(wxFONTWEIGHT_BOLD);
|
||||
titletext->SetFont(titlefont);
|
||||
|
||||
m_deviceText = new wxStaticText(
|
||||
this,
|
||||
wxID_ANY,
|
||||
deviceTextString
|
||||
);
|
||||
|
||||
auto* left = new wxBoxSizer(wxVERTICAL);
|
||||
left->Add(titletext, 0, wxTOP | wxLEFT, 10);
|
||||
left->Add(m_deviceText, 0, wxTOP | wxLEFT, 10);
|
||||
|
||||
auto* row = new wxBoxSizer(wxHORIZONTAL);
|
||||
row->Add(left, 0, wxALIGN_TOP);
|
||||
row->AddStretchSpacer();
|
||||
row->Add(logo, 0, wxTOP | wxRIGHT, 10);
|
||||
|
||||
root->Add(row, 0, wxEXPAND);
|
||||
|
||||
root->Add(new wxStaticLine(this), 0, wxEXPAND | wxALL, 10);
|
||||
|
||||
auto* credtext = new wxStaticText(this, wxID_ANY,
|
||||
"Made by: asdfugil, kok3shidoll, claration, mineek, staturnz\n\n"
|
||||
"Thanks to: itsnebulalol, llsc12, lrdsnow, nikias (libimobiledevice), Checkra1n\n"
|
||||
"(Siguza, axi0mx, littlelailo et al.), Procursus (Hayden Seay, Cameron Katri,\n"
|
||||
"Keto et al.)\n\n"
|
||||
"With 💖 from C (claration)");
|
||||
|
||||
root->Add(credtext, 0, wxLEFT | wxRIGHT, 10);
|
||||
|
||||
root->Add(new wxStaticLine(this), 0, wxEXPAND | wxALL, 10);
|
||||
|
||||
auto* notetext = new wxStaticText(this, wxID_ANY,
|
||||
"NOTE: Please ensure you have a backup of your device before applying the\n"
|
||||
"jailbreak. While data loss is unlikely, we won't be responsible if something goes\n"
|
||||
"wrong. Use at your own risk.");
|
||||
|
||||
wxFont notefont = wxSystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT);
|
||||
notefont.SetPointSize(12);
|
||||
notefont.SetStyle(wxFONTSTYLE_ITALIC);
|
||||
notetext->SetFont(notefont);
|
||||
|
||||
root->Add(notetext, 0, wxLEFT | wxRIGHT, 10);
|
||||
|
||||
root->AddStretchSpacer();
|
||||
|
||||
auto* btn = new wxButton(this, wxID_ANY, "Options");
|
||||
m_startButton = new wxButton(this, wxID_ANY, "Start");
|
||||
m_startButton->Disable();
|
||||
|
||||
btn->Bind(wxEVT_BUTTON, [frame](wxCommandEvent&)
|
||||
{
|
||||
frame->ShowSettings();
|
||||
});
|
||||
|
||||
m_startButton->Bind(wxEVT_BUTTON, [frame](wxCommandEvent&)
|
||||
{
|
||||
if (frame->GetDeviceState().mode == DeviceMode::Normal)
|
||||
{
|
||||
frame->ShowRecovery();
|
||||
}
|
||||
else if (frame->GetDeviceState().mode == DeviceMode::Recovery)
|
||||
{
|
||||
frame->ShowDfu();
|
||||
}
|
||||
});
|
||||
|
||||
auto* bottomRow = new wxBoxSizer(wxHORIZONTAL);
|
||||
bottomRow->AddStretchSpacer();
|
||||
bottomRow->Add(btn, 0, wxALL, 10);
|
||||
bottomRow->Add(m_startButton, 0, wxALL, 10);
|
||||
|
||||
root->Add(bottomRow, 0, wxEXPAND);
|
||||
|
||||
SetSizer(root);
|
||||
}
|
||||
|
||||
void MainPanel::SetDeviceState(const DeviceState& state)
|
||||
{
|
||||
DevicePanel::SetDeviceState(state);
|
||||
|
||||
if (m_deviceText)
|
||||
{
|
||||
if (!state.connected)
|
||||
{
|
||||
m_deviceText->SetLabel(deviceTextString);
|
||||
m_startButton->Enable(false);
|
||||
}
|
||||
else
|
||||
{
|
||||
const std::string versionString =
|
||||
state.productVersion.empty() ? "Unknown" : state.productVersion;
|
||||
const std::string productString =
|
||||
state.productType.empty() ? "Unknown" : state.productType;
|
||||
const std::string ecidString = state.ecid != 0 ? ("ECID: " + std::to_string(state.ecid)) : "ECID: Unknown";
|
||||
|
||||
switch (state.mode)
|
||||
{
|
||||
case DeviceMode::Normal:
|
||||
if (state.isSupported) {
|
||||
m_deviceText->SetLabel("Connected " + productString + " (iOS " + versionString + ") in normal mode.\n" + ecidString);
|
||||
m_startButton->Enable();
|
||||
} else {
|
||||
m_deviceText->SetLabel("Sorry, " + productString + " (iOS " + versionString + ") is not supported.\n" + ecidString);
|
||||
m_startButton->Enable(false);
|
||||
}
|
||||
break;
|
||||
case DeviceMode::Recovery:
|
||||
if (state.isSupported) {
|
||||
m_deviceText->SetLabel("Connected " + productString + " in recovery mode.\n" + ecidString);
|
||||
m_startButton->Enable();
|
||||
} else {
|
||||
m_deviceText->SetLabel("Sorry, " + productString + " is not supported.\n" + ecidString);
|
||||
m_startButton->Enable(false);
|
||||
}
|
||||
break;
|
||||
case DeviceMode::DFU:
|
||||
m_deviceText->SetLabel("Sorry, DFU mode is not supported.");
|
||||
m_startButton->Enable(false);
|
||||
break;
|
||||
case DeviceMode::None:
|
||||
m_deviceText->SetLabel(deviceTextString);
|
||||
m_startButton->Enable(false);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,23 @@
|
||||
#ifndef MAINPANEL_H
|
||||
#define MAINPANEL_H
|
||||
|
||||
#ifdef WITH_GUI
|
||||
|
||||
#include <wx/wx.h>
|
||||
#include "DevicePanel.hpp"
|
||||
#include "../state.hpp"
|
||||
|
||||
class MainPanel;
|
||||
class MainPanel : public DevicePanel
|
||||
{
|
||||
public:
|
||||
explicit MainPanel(MainFrame* frame, wxWindow* parent);
|
||||
void SetDeviceState(const DeviceState& state) override;
|
||||
private:
|
||||
wxStaticText* m_deviceText = nullptr;
|
||||
wxButton* m_startButton = nullptr;
|
||||
};
|
||||
|
||||
#endif // WITH_GUI
|
||||
|
||||
#endif // MAINPANEL_H
|
||||
@@ -0,0 +1,130 @@
|
||||
#ifdef WITH_GUI
|
||||
|
||||
#include "RecoveryPanel.hpp"
|
||||
|
||||
#include <wx/wx.h>
|
||||
|
||||
#include "AppFrame.hpp"
|
||||
#include "DevicePanel.hpp"
|
||||
#include "../state.hpp"
|
||||
|
||||
#include <libimobiledevice/libimobiledevice.h>
|
||||
#include <libimobiledevice/lockdown.h>
|
||||
#include <libirecovery.h>
|
||||
|
||||
RecoveryPanel::RecoveryPanel(MainFrame* frame, wxWindow* parent)
|
||||
: DevicePanel(frame, parent)
|
||||
{
|
||||
auto* root = new wxBoxSizer(wxVERTICAL);
|
||||
|
||||
root->Add(new wxStaticText(this, wxID_ANY,
|
||||
"The device needs to be put into DFU mode to apply the jailbreak. This is a\n"
|
||||
"manual process and we will guide you through it.\n\n"
|
||||
"In order to prevent filesystem corruption through hard reset, the device will\n"
|
||||
"be put into recovery mode first. Click next when you are ready."), 0, wxALL, 10);
|
||||
|
||||
m_statusText = new wxStaticText(this, wxID_ANY, "");
|
||||
root->Add(m_statusText, 0, wxALL, 10);
|
||||
|
||||
m_backButton = new wxButton(this, wxID_ANY, "Back");
|
||||
m_nextButton = new wxButton(this, wxID_ANY, "Next");
|
||||
|
||||
m_backButton->Bind(wxEVT_BUTTON, [frame](wxCommandEvent&)
|
||||
{
|
||||
frame->ShowMain();
|
||||
});
|
||||
|
||||
m_nextButton->Bind(wxEVT_BUTTON, [this](wxCommandEvent&)
|
||||
{
|
||||
m_statusText->SetLabel("Entering recovery mode...");
|
||||
m_backButton->Disable();
|
||||
m_nextButton->Disable();
|
||||
EnterRecoveryMode();
|
||||
});
|
||||
|
||||
m_timer.Bind(wxEVT_TIMER, [this](wxTimerEvent&)
|
||||
{
|
||||
m_backButton->Enable();
|
||||
m_nextButton->Enable();
|
||||
m_isEnteringRecovery = false;
|
||||
|
||||
GetMainFrame()->ShowDfu();
|
||||
m_statusText->SetLabel("");
|
||||
});
|
||||
|
||||
auto* bottomRow = new wxBoxSizer(wxHORIZONTAL);
|
||||
bottomRow->AddStretchSpacer();
|
||||
bottomRow->Add(m_backButton, 0, wxALL, 10);
|
||||
bottomRow->Add(m_nextButton, 0, wxALL, 10);
|
||||
|
||||
root->AddStretchSpacer();
|
||||
root->Add(bottomRow, 0, wxEXPAND);
|
||||
|
||||
SetSizer(root);
|
||||
}
|
||||
|
||||
void RecoveryPanel::SetDeviceState(const DeviceState& state)
|
||||
{
|
||||
DevicePanel::SetDeviceState(state);
|
||||
|
||||
if (!(this->IsShown()))
|
||||
return;
|
||||
|
||||
if (m_isEnteringRecovery && state.mode == DeviceMode::Recovery)
|
||||
{
|
||||
m_statusText->SetLabel("Device is now in recovery mode.");
|
||||
m_timer.StartOnce(3000);
|
||||
}
|
||||
|
||||
if (m_isEnteringRecovery && state.mode == DeviceMode::Normal)
|
||||
{
|
||||
m_isEnteringRecovery = false;
|
||||
m_statusText->SetLabel("Hmm... It seems like the device didn't enter recovery mode. Please try again.");
|
||||
m_backButton->Enable();
|
||||
m_nextButton->Enable();
|
||||
}
|
||||
|
||||
if (!m_isEnteringRecovery && state.mode == DeviceMode::None)
|
||||
{
|
||||
m_backButton->Enable();
|
||||
m_nextButton->Enable();
|
||||
GetMainFrame()->ShowMain();
|
||||
}
|
||||
}
|
||||
|
||||
void RecoveryPanel::EnterRecoveryMode()
|
||||
{
|
||||
const auto& state = GetDeviceState();
|
||||
|
||||
if (!state.connected)
|
||||
return;
|
||||
|
||||
idevice_t device = nullptr;
|
||||
lockdownd_client_t client = nullptr;
|
||||
|
||||
if (idevice_new(&device, state.udid.c_str()) != IDEVICE_E_SUCCESS)
|
||||
return;
|
||||
|
||||
if (lockdownd_client_new_with_handshake(
|
||||
device,
|
||||
&client,
|
||||
"palera1n") != LOCKDOWN_E_SUCCESS)
|
||||
{
|
||||
idevice_free(device);
|
||||
return;
|
||||
}
|
||||
|
||||
m_isEnteringRecovery = true;
|
||||
|
||||
lockdownd_enter_recovery(client);
|
||||
|
||||
lockdownd_client_free(client);
|
||||
idevice_free(device);
|
||||
}
|
||||
|
||||
void RecoveryPanel::SetStatusText(const wxString& text)
|
||||
{
|
||||
m_statusText->SetLabel(text);
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,28 @@
|
||||
#ifndef RECOVERYPANEL_H
|
||||
#define RECOVERYPANEL_H
|
||||
|
||||
#ifdef WITH_GUI
|
||||
|
||||
#include <wx/wx.h>
|
||||
#include "DevicePanel.hpp"
|
||||
|
||||
class RecoveryPanel;
|
||||
class RecoveryPanel : public DevicePanel
|
||||
{
|
||||
public:
|
||||
explicit RecoveryPanel(MainFrame* frame, wxWindow* parent);
|
||||
void SetDeviceState(const DeviceState& state) override;
|
||||
void SetStatusText(const wxString& text);
|
||||
void EnterRecoveryMode();
|
||||
private:
|
||||
wxButton* m_backButton = nullptr;
|
||||
wxButton* m_nextButton = nullptr;
|
||||
wxStaticText* m_statusText = nullptr;
|
||||
wxTimer m_timer;
|
||||
bool m_isEnteringRecovery = false;
|
||||
};
|
||||
|
||||
|
||||
#endif // WITH_GUI
|
||||
|
||||
#endif // RECOVERYPANEL_H
|
||||
@@ -0,0 +1,84 @@
|
||||
#ifdef WITH_GUI
|
||||
|
||||
#include "SettingsPanel.hpp"
|
||||
|
||||
#include <wx/wx.h>
|
||||
|
||||
#include "AppFrame.hpp"
|
||||
#include "DevicePanel.hpp"
|
||||
#include "../utils/constants.h"
|
||||
#include "../utils/paleinfo.h"
|
||||
|
||||
SettingsPanel::SettingsPanel(MainFrame* frame, wxWindow* parent)
|
||||
: DevicePanel(frame, parent)
|
||||
{
|
||||
auto* root = new wxBoxSizer(wxVERTICAL);
|
||||
|
||||
root->Add(new wxStaticText(this, wxID_ANY,
|
||||
"You may set the following options. If you don't know what they mean you'll\n"
|
||||
"probably have no reason to set them."), 0, wxALL, 10);
|
||||
|
||||
auto* option_safemode = new wxCheckBox(this, wxID_ANY, "Safe Mode");
|
||||
auto* option_verbose = new wxCheckBox(this, wxID_ANY, "Verbose Boot");
|
||||
auto* option_revert = new wxCheckBox(this, wxID_ANY, "Restore System");
|
||||
auto* option_dark_blockchain = new wxCheckBox(this, wxID_ANY, "Dark Blockchain");
|
||||
|
||||
option_safemode->SetValue(palerain_flags & palerain_option_safemode);
|
||||
option_verbose->SetValue(palerain_flags & palerain_option_verbose_boot);
|
||||
option_revert->SetValue(palerain_flags & palerain_option_force_revert);
|
||||
option_dark_blockchain->SetValue(true);
|
||||
|
||||
option_safemode->Bind(wxEVT_CHECKBOX, [&](wxCommandEvent& e)
|
||||
{
|
||||
if (e.IsChecked())
|
||||
palerain_flags |= palerain_option_safemode;
|
||||
else
|
||||
palerain_flags &= ~palerain_option_safemode;
|
||||
});
|
||||
|
||||
option_verbose->Bind(wxEVT_CHECKBOX, [&](wxCommandEvent& e)
|
||||
{
|
||||
if (e.IsChecked())
|
||||
palerain_flags |= palerain_option_verbose_boot;
|
||||
else
|
||||
palerain_flags &= ~palerain_option_verbose_boot;
|
||||
});
|
||||
|
||||
option_revert->Bind(wxEVT_CHECKBOX, [&](wxCommandEvent& e)
|
||||
{
|
||||
if (e.IsChecked())
|
||||
palerain_flags |= palerain_option_force_revert;
|
||||
else
|
||||
palerain_flags &= ~palerain_option_force_revert;
|
||||
});
|
||||
|
||||
root->Add(option_safemode, 0, wxLEFT | wxRIGHT, 10);
|
||||
root->Add(option_verbose, 0, wxLEFT | wxRIGHT | wxTOP, 10);
|
||||
|
||||
root->Add(new wxStaticText(this, wxID_ANY, "Boot Arguments:"), 0, wxALL, 10);
|
||||
|
||||
auto* bootArgs = new wxTextCtrl(this, wxID_ANY, "", wxDefaultPosition, wxDefaultSize);
|
||||
root->Add(bootArgs, 0, wxLEFT | wxRIGHT | wxEXPAND, 30);
|
||||
|
||||
root->Add(option_revert, 0, wxLEFT | wxRIGHT | wxTOP, 10);
|
||||
root->Add(option_dark_blockchain, 0, wxLEFT | wxRIGHT | wxTOP, 10);
|
||||
|
||||
root->AddStretchSpacer();
|
||||
|
||||
auto* back = new wxButton(this, wxID_ANY, "Back");
|
||||
|
||||
back->Bind(wxEVT_BUTTON, [frame](wxCommandEvent&)
|
||||
{
|
||||
frame->ShowMain();
|
||||
});
|
||||
|
||||
auto* bottomRow = new wxBoxSizer(wxHORIZONTAL);
|
||||
bottomRow->AddStretchSpacer();
|
||||
bottomRow->Add(back, 0, wxALL, 10);
|
||||
|
||||
root->Add(bottomRow, 0, wxEXPAND);
|
||||
|
||||
SetSizer(root);
|
||||
}
|
||||
|
||||
#endif // WITH_GUI
|
||||
@@ -0,0 +1,18 @@
|
||||
#ifndef SETTINGSPANEL_H
|
||||
#define SETTINGSPANEL_H
|
||||
|
||||
#ifdef WITH_GUI
|
||||
|
||||
#include <wx/wx.h>
|
||||
#include "DevicePanel.hpp"
|
||||
|
||||
class SettingsPanel;
|
||||
class SettingsPanel : public DevicePanel
|
||||
{
|
||||
public:
|
||||
explicit SettingsPanel(MainFrame* frame, wxWindow* parent);
|
||||
};
|
||||
|
||||
#endif // WITH_GUI
|
||||
|
||||
#endif // SETTINGSPANEL_H
|
||||
Reference in new issue
Block a user