mirror of https://gitlab.com/nakst/essence
1276 lines
49 KiB
C++
1276 lines
49 KiB
C++
// This file is part of the Essence operating system.
|
|
// It is released under the terms of the MIT license -- see LICENSE.md.
|
|
// Written by: nakst.
|
|
|
|
#define INSTALLER
|
|
|
|
#define ES_CRT_WITHOUT_PREFIX
|
|
#include <essence.h>
|
|
|
|
#include <shared/hash.cpp>
|
|
#include <shared/strings.cpp>
|
|
#include <shared/partitions.cpp>
|
|
#include <shared/array.cpp>
|
|
#include <shared/fat.cpp>
|
|
#include <ports/lzma/LzmaDec.c>
|
|
|
|
#define Log(...)
|
|
#define EsFSError() EsAssert(false)
|
|
#include <shared/esfs2.h>
|
|
|
|
// Assume an additional 64MB of storage capacity is needed on top of totalUncompressedBytes.
|
|
#define PARTITION_OVERHEAD (64 * 1024 * 1024)
|
|
|
|
#define MSG_SET_PROGRESS ((EsMessageType) (ES_MSG_USER_START + 1))
|
|
|
|
struct InstallerMetadata {
|
|
uint64_t totalUncompressedBytes;
|
|
uint64_t crc64;
|
|
};
|
|
|
|
const EsStyle styleRoot = {
|
|
.metrics = {
|
|
.mask = ES_THEME_METRICS_GAP_MAJOR | ES_THEME_METRICS_INSETS,
|
|
.insets = ES_RECT_1(30),
|
|
.gapMajor = 20,
|
|
},
|
|
};
|
|
|
|
const EsStyle styleDrivesSelectHint = {
|
|
.inherit = ES_STYLE_TEXT_PARAGRAPH_SECONDARY,
|
|
|
|
.metrics = {
|
|
.mask = ES_THEME_METRICS_TEXT_ALIGN | ES_THEME_METRICS_INSETS,
|
|
.insets = ES_RECT_1(30),
|
|
.textAlign = ES_TEXT_H_CENTER | ES_TEXT_V_CENTER | ES_TEXT_WRAP,
|
|
},
|
|
};
|
|
|
|
const EsStyle styleButtonsRow = {
|
|
.metrics = {
|
|
.mask = ES_THEME_METRICS_GAP_MAJOR,
|
|
.gapMajor = 10,
|
|
},
|
|
};
|
|
|
|
const EsStyle styleTextboxMedium = {
|
|
.inherit = ES_STYLE_TEXTBOX_BORDERED_SINGLE,
|
|
|
|
.metrics = {
|
|
.mask = ES_THEME_METRICS_PREFERRED_WIDTH,
|
|
.preferredWidth = 80,
|
|
},
|
|
};
|
|
|
|
InstallerMetadata *metadata;
|
|
Array<EsMessageDevice> connectedDrives;
|
|
EsListView *drivesList;
|
|
EsPanel *driveInformation;
|
|
EsObjectID selectedDriveID;
|
|
EsButton *installButton;
|
|
EsButton *useMBRCheckbox;
|
|
EsButton *finishButton;
|
|
EsPanel *switcher;
|
|
EsPanel *panelInstallOptions;
|
|
EsPanel *panelCustomizeOptions;
|
|
EsPanel *panelLicenses;
|
|
EsPanel *panelWait;
|
|
EsPanel *panelComplete;
|
|
EsPanel *panelError;
|
|
EsPanel *panelNotSupported;
|
|
EsTextbox *userNameTextbox;
|
|
EsTextbox *timeTextbox;
|
|
EsTextDisplay *progressDisplay;
|
|
const char *cSelectedFont;
|
|
int64_t clockOffsetMs;
|
|
uint8_t progress;
|
|
bool onWaitScreen;
|
|
bool startedInstallation;
|
|
bool useMBR;
|
|
EsBlockDeviceInformation blockDeviceInformation;
|
|
EsHandle driveHandle;
|
|
EsFileOffset partitionOffset;
|
|
EsFileOffset partitionBytes;
|
|
EsUniqueIdentifier installationIdentifier;
|
|
EsMountPoint newFileSystemMountPoint;
|
|
EsHandle mountNewFileSystemEvent;
|
|
EsError installError;
|
|
bool archiveCRCError;
|
|
|
|
/////////////////////////////////////////////
|
|
|
|
#define BUFFER_SIZE (1048576)
|
|
#define NAME_MAX (4096)
|
|
|
|
struct Extractor {
|
|
EsFileInformation fileIn;
|
|
CLzmaDec state;
|
|
uint8_t inBuffer[BUFFER_SIZE], outBuffer[BUFFER_SIZE], copyBuffer[BUFFER_SIZE], pathBuffer[NAME_MAX];
|
|
size_t inFileOffset, inBytes, inPosition;
|
|
uintptr_t positionInBlock, blockSize;
|
|
};
|
|
|
|
void *DecompressAllocate(ISzAllocPtr, size_t size) { return EsHeapAllocate(size, false); }
|
|
void DecompressFree(ISzAllocPtr, void *address) { EsHeapFree(address); }
|
|
const ISzAlloc decompressAllocator = { DecompressAllocate, DecompressFree };
|
|
|
|
ptrdiff_t DecompressBlock(Extractor *e) {
|
|
if (e->inBytes == e->inPosition) {
|
|
e->inBytes = EsFileReadSync(e->fileIn.handle, e->inFileOffset, BUFFER_SIZE, e->inBuffer);
|
|
if (!e->inBytes) return -1;
|
|
e->inPosition = 0;
|
|
e->inFileOffset += e->inBytes;
|
|
}
|
|
|
|
size_t inProcessed = e->inBytes - e->inPosition;
|
|
size_t outProcessed = BUFFER_SIZE;
|
|
ELzmaStatus status;
|
|
LzmaDec_DecodeToBuf(&e->state, e->outBuffer, &outProcessed, e->inBuffer + e->inPosition, &inProcessed, LZMA_FINISH_ANY, &status);
|
|
e->inPosition += inProcessed;
|
|
return outProcessed;
|
|
}
|
|
|
|
bool Decompress(Extractor *e, void *_buffer, size_t bytes) {
|
|
uint8_t *buffer = (uint8_t *) _buffer;
|
|
|
|
while (bytes) {
|
|
if (e->positionInBlock == e->blockSize) {
|
|
ptrdiff_t processed = DecompressBlock(e);
|
|
if (processed == -1) return false;
|
|
e->blockSize = processed;
|
|
e->positionInBlock = 0;
|
|
}
|
|
|
|
size_t copyBytes = bytes > e->blockSize - e->positionInBlock ? e->blockSize - e->positionInBlock : bytes;
|
|
EsMemoryCopy(buffer, e->outBuffer + e->positionInBlock, copyBytes);
|
|
e->positionInBlock += copyBytes, buffer += copyBytes, bytes -= copyBytes;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
EsError Extract(const char *pathIn, size_t pathInBytes, const char *pathOut, size_t pathOutBytes) {
|
|
Extractor *e = (Extractor *) EsHeapAllocate(sizeof(Extractor), true);
|
|
if (!e) return ES_ERROR_INSUFFICIENT_RESOURCES;
|
|
EsDefer(EsHeapFree(e));
|
|
|
|
e->fileIn = EsFileOpen(pathIn, pathInBytes, ES_FILE_READ);
|
|
if (e->fileIn.error != ES_SUCCESS) return e->fileIn.error;
|
|
|
|
uint8_t header[LZMA_PROPS_SIZE + 8];
|
|
EsFileReadSync(e->fileIn.handle, 0, sizeof(header), header);
|
|
|
|
LzmaDec_Construct(&e->state);
|
|
LzmaDec_Allocate(&e->state, header, LZMA_PROPS_SIZE, &decompressAllocator);
|
|
LzmaDec_Init(&e->state);
|
|
|
|
e->inFileOffset = sizeof(header);
|
|
|
|
uint64_t crc64 = 0;
|
|
uint64_t totalBytesExtracted = 0;
|
|
uint8_t lastProgressByte = 0;
|
|
|
|
EsMemoryCopy(e->pathBuffer, pathOut, pathOutBytes);
|
|
|
|
while (true) {
|
|
uint64_t fileSize;
|
|
if (!Decompress(e, &fileSize, sizeof(fileSize))) break;
|
|
uint16_t nameBytes;
|
|
if (!Decompress(e, &nameBytes, sizeof(nameBytes))) break;
|
|
if (nameBytes > NAME_MAX - pathOutBytes) break;
|
|
if (!Decompress(e, e->pathBuffer + pathOutBytes, nameBytes)) break;
|
|
|
|
if (fileSize == (uint64_t) -1UL) {
|
|
EsPrint("Creating folder '%s'...\n", pathOutBytes + nameBytes, (const char *) e->pathBuffer);
|
|
EsPathCreate((const char *) e->pathBuffer, pathOutBytes + nameBytes, ES_NODE_DIRECTORY, true);
|
|
continue;
|
|
}
|
|
|
|
EsPrint("Copying file '%s' of size %D...\n", pathOutBytes + nameBytes, (const char *) e->pathBuffer, fileSize);
|
|
EsFileInformation fileOut = EsFileOpen((const char *) e->pathBuffer, pathOutBytes + nameBytes,
|
|
ES_FILE_WRITE | ES_NODE_CREATE_DIRECTORIES | ES_NODE_FAIL_IF_FOUND);
|
|
EsFileOffset fileOutPosition = 0;
|
|
|
|
if (fileOut.error != ES_SUCCESS) {
|
|
LzmaDec_Free(&e->state, &decompressAllocator);
|
|
EsHandleClose(e->fileIn.handle);
|
|
return fileOut.error;
|
|
}
|
|
|
|
while (fileOutPosition < fileSize) {
|
|
size_t copyBytes = (fileSize - fileOutPosition) > BUFFER_SIZE ? BUFFER_SIZE : (fileSize - fileOutPosition);
|
|
Decompress(e, e->copyBuffer, copyBytes);
|
|
EsFileWriteSync(fileOut.handle, fileOutPosition, copyBytes, e->copyBuffer);
|
|
fileOutPosition += copyBytes;
|
|
totalBytesExtracted += copyBytes;
|
|
crc64 = CalculateCRC64(e->copyBuffer, copyBytes, crc64);
|
|
|
|
EsMessage m = { MSG_SET_PROGRESS };
|
|
double progress = (double) totalBytesExtracted / metadata->totalUncompressedBytes;
|
|
m.user.context1.u = 10 + 80 * progress;
|
|
|
|
if (lastProgressByte != m.user.context1.u) {
|
|
lastProgressByte = m.user.context1.u;
|
|
EsMessagePost(nullptr, &m);
|
|
}
|
|
}
|
|
|
|
EsHandleClose(fileOut.handle);
|
|
}
|
|
|
|
LzmaDec_Free(&e->state, &decompressAllocator);
|
|
EsHandleClose(e->fileIn.handle);
|
|
|
|
return crc64 == metadata->crc64 ? ES_SUCCESS : ES_ERROR_CORRUPT_DATA;
|
|
}
|
|
|
|
/////////////////////////////////////////////
|
|
|
|
uint64_t writeOffset;
|
|
uint64_t writeBytes;
|
|
uint8_t writeBuffer[BUFFER_SIZE];
|
|
EsError formatError = ES_SUCCESS;
|
|
|
|
bool FlushWriteBuffer() {
|
|
if (!writeBytes) return true;
|
|
EsFileOffset parameters[2] = { partitionOffset * blockDeviceInformation.sectorSize + writeOffset, writeBytes };
|
|
EsError error = EsDeviceControl(driveHandle, ES_DEVICE_CONTROL_BLOCK_WRITE, writeBuffer, parameters);
|
|
writeBytes = 0;
|
|
if (error != ES_SUCCESS) formatError = error;
|
|
return error == ES_SUCCESS;
|
|
}
|
|
|
|
bool ReadBlock(uint64_t block, uint64_t count, void *buffer) {
|
|
if (!FlushWriteBuffer()) {
|
|
return false;
|
|
}
|
|
|
|
EsFileOffset parameters[2] = { partitionOffset * blockDeviceInformation.sectorSize + block * blockSize, count * blockSize };
|
|
EsError error = EsDeviceControl(driveHandle, ES_DEVICE_CONTROL_BLOCK_READ, buffer, parameters);
|
|
if (error != ES_SUCCESS) formatError = error;
|
|
return error == ES_SUCCESS;
|
|
}
|
|
|
|
bool WriteBlock(uint64_t block, uint64_t count, void *buffer) {
|
|
uint64_t offset = block * blockSize, bytes = count * blockSize;
|
|
|
|
if (writeBytes && writeOffset + writeBytes == offset && writeBytes + bytes < sizeof(writeBuffer)) {
|
|
EsMemoryCopy(writeBuffer + writeBytes, buffer, bytes);
|
|
writeBytes += bytes;
|
|
return true;
|
|
} else {
|
|
if (!FlushWriteBuffer()) {
|
|
return false;
|
|
}
|
|
|
|
writeOffset = offset;
|
|
writeBytes = bytes;
|
|
EsMemoryCopy(writeBuffer, buffer, bytes);
|
|
return true;
|
|
}
|
|
}
|
|
|
|
bool WriteBytes(uint64_t byteOffset, uint64_t byteCount, void *buffer) {
|
|
uint64_t firstSector = byteOffset / blockDeviceInformation.sectorSize;
|
|
uint64_t lastSector = (byteOffset + byteCount - 1) / blockDeviceInformation.sectorSize;
|
|
uint64_t sectorCount = lastSector - firstSector + 1;
|
|
|
|
void *buffer2 = EsHeapAllocate(sectorCount * blockDeviceInformation.sectorSize, false);
|
|
|
|
for (uintptr_t i = 0; i < sectorCount; i++) {
|
|
if (i > 0 && i < sectorCount - 1) continue;
|
|
EsFileOffset parameters[2] = { (partitionOffset + firstSector + i) * blockDeviceInformation.sectorSize, blockDeviceInformation.sectorSize } ;
|
|
EsError error = EsDeviceControl(driveHandle, ES_DEVICE_CONTROL_BLOCK_READ, (uint8_t *) buffer2 + i * blockDeviceInformation.sectorSize, parameters);
|
|
|
|
if (error != ES_SUCCESS) {
|
|
formatError = error;
|
|
return false;
|
|
}
|
|
}
|
|
|
|
EsMemoryCopy((uint8_t *) buffer2 + byteOffset % blockDeviceInformation.sectorSize, buffer, byteCount);
|
|
|
|
EsFileOffset parameters[2] = { (partitionOffset + firstSector) * blockDeviceInformation.sectorSize, sectorCount * blockDeviceInformation.sectorSize };
|
|
EsError error = EsDeviceControl(driveHandle, ES_DEVICE_CONTROL_BLOCK_WRITE, buffer, parameters);
|
|
|
|
if (error != ES_SUCCESS) {
|
|
formatError = error;
|
|
return false;
|
|
}
|
|
|
|
EsHeapFree(buffer2);
|
|
return true;
|
|
}
|
|
|
|
/////////////////////////////////////////////
|
|
|
|
EsBlockDeviceInformation ConnectedDriveGetInformation(EsHandle handle) {
|
|
EsBlockDeviceInformation information;
|
|
EsDeviceControl(handle, ES_DEVICE_CONTROL_BLOCK_GET_INFORMATION, &information, nullptr);
|
|
|
|
for (uintptr_t i = 0; i < information.modelBytes; i++) {
|
|
if (information.model[i] == 0) {
|
|
information.modelBytes = i;
|
|
break;
|
|
}
|
|
}
|
|
|
|
for (uintptr_t i = information.modelBytes; i > 0; i--) {
|
|
if (information.model[i - 1] == ' ') {
|
|
information.modelBytes--;
|
|
} else {
|
|
break;
|
|
}
|
|
}
|
|
|
|
return information;
|
|
}
|
|
|
|
void ConnectedDriveAdd(EsMessageDevice device) {
|
|
if (device.type != ES_DEVICE_BLOCK) {
|
|
return;
|
|
}
|
|
|
|
EsBlockDeviceInformation information = ConnectedDriveGetInformation(device.handle);
|
|
|
|
if (information.nestLevel || information.driveType == ES_DRIVE_TYPE_CDROM) {
|
|
return;
|
|
}
|
|
|
|
// TODO EsObjectID might not necessarily fit in EsGeneric...
|
|
EsListViewFixedItemInsert(drivesList, information.model, information.modelBytes, (uintptr_t) device.id);
|
|
connectedDrives.Add(device);
|
|
}
|
|
|
|
void ConnectedDriveRemove(EsMessageDevice device) {
|
|
if (device.id == selectedDriveID) {
|
|
EsElementDestroyContents(driveInformation);
|
|
EsTextDisplayCreate(driveInformation, ES_CELL_H_FILL | ES_CELL_V_FILL, &styleDrivesSelectHint, INTERFACE_STRING(InstallerDriveRemoved));
|
|
selectedDriveID = 0;
|
|
EsElementSetDisabled(installButton, true);
|
|
EsElementSetDisabled(useMBRCheckbox, true);
|
|
}
|
|
|
|
for (uintptr_t i = 0; i < connectedDrives.Length(); i++) {
|
|
if (connectedDrives[i].id == device.id) {
|
|
EsListViewFixedItemRemove(drivesList, (uintptr_t) device.id);
|
|
connectedDrives.Delete(i);
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
void ConnectedDriveSelect(uintptr_t index) {
|
|
EsMessageDevice device = connectedDrives[index];
|
|
if (selectedDriveID == device.id) return;
|
|
selectedDriveID = device.id;
|
|
EsElementSetDisabled(installButton, true);
|
|
|
|
EsBlockDeviceInformation information = ConnectedDriveGetInformation(device.handle);
|
|
EsElementStartTransition(driveInformation, ES_TRANSITION_FADE_VIA_TRANSPARENT, ES_FLAGS_DEFAULT, 4.0f);
|
|
EsElementDestroyContents(driveInformation);
|
|
|
|
EsPanel *nameRow = EsPanelCreate(driveInformation, ES_CELL_H_FILL | ES_PANEL_HORIZONTAL);
|
|
EsIconDisplayCreate(nameRow, ES_FLAGS_DEFAULT, 0, EsIconIDFromDriveType(information.driveType));
|
|
EsSpacerCreate(nameRow, ES_CELL_V_FILL, 0, 8, 0);
|
|
EsTextDisplayCreate(nameRow, ES_CELL_H_FILL, ES_STYLE_TEXT_HEADING2, information.model, information.modelBytes);
|
|
EsSpacerCreate(driveInformation, ES_CELL_H_FILL, 0, 0, 16);
|
|
|
|
EsPanel *messageRow = EsPanelCreate(driveInformation, ES_CELL_H_FILL | ES_PANEL_HORIZONTAL);
|
|
EsIconDisplay *statusIcon = EsIconDisplayCreate(messageRow, ES_CELL_V_TOP, ES_STYLE_ICON_DISPLAY_SMALL, ES_ICON_DIALOG_INFORMATION);
|
|
EsSpacerCreate(messageRow, ES_CELL_V_FILL, 0, 8, 0);
|
|
|
|
uint8_t *signatureBlock = (uint8_t *) EsHeapAllocate(K_SIGNATURE_BLOCK_SIZE, false);
|
|
EsFileOffset parameters[2] = { 0, K_SIGNATURE_BLOCK_SIZE };
|
|
EsError readError = EsDeviceControl(device.handle, ES_DEVICE_CONTROL_BLOCK_READ, signatureBlock, parameters);
|
|
bool alreadyHasPartitions = false;
|
|
|
|
if (readError == ES_SUCCESS && information.sectorSize >= 0x200) {
|
|
{
|
|
MBRPartition partitions[4];
|
|
|
|
if (MBRGetPartitions(signatureBlock, information.sectorCount, partitions)) {
|
|
for (uintptr_t i = 0; i < 4; i++) {
|
|
if (partitions[i].present) {
|
|
alreadyHasPartitions = true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
{
|
|
GPTPartition partitions[GPT_PARTITION_COUNT];
|
|
|
|
if (GPTGetPartitions(signatureBlock, information.sectorCount, information.sectorSize, partitions)) {
|
|
for (uintptr_t i = 0; i < GPT_PARTITION_COUNT; i++) {
|
|
if (partitions[i].present) {
|
|
alreadyHasPartitions = true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
EsHeapFree(signatureBlock);
|
|
|
|
bool showCapacity = false;
|
|
|
|
// Sector sizes up to 4KB should be possible on GPT, but this hasn't been tested yet.
|
|
#if 0
|
|
if (information.sectorSize < 0x200 || information.sectorSize > 0x1000) {
|
|
#else
|
|
if (information.sectorSize != 0x200) {
|
|
#endif
|
|
EsIconDisplaySetIcon(statusIcon, ES_ICON_DIALOG_ERROR);
|
|
EsTextDisplayCreate(messageRow, ES_CELL_H_FILL, ES_STYLE_TEXT_PARAGRAPH, INTERFACE_STRING(InstallerDriveUnsupported));
|
|
} else if (readError != ES_SUCCESS) {
|
|
EsIconDisplaySetIcon(statusIcon, ES_ICON_DIALOG_ERROR);
|
|
EsTextDisplayCreate(messageRow, ES_CELL_H_FILL, ES_STYLE_TEXT_PARAGRAPH, INTERFACE_STRING(InstallerDriveCouldNotRead));
|
|
} else if (information.readOnly) {
|
|
EsIconDisplaySetIcon(statusIcon, ES_ICON_DIALOG_ERROR);
|
|
EsTextDisplayCreate(messageRow, ES_CELL_H_FILL, ES_STYLE_TEXT_PARAGRAPH, INTERFACE_STRING(InstallerDriveReadOnly));
|
|
} else if (alreadyHasPartitions) {
|
|
EsIconDisplaySetIcon(statusIcon, ES_ICON_DIALOG_ERROR);
|
|
EsTextDisplayCreate(messageRow, ES_CELL_H_FILL, ES_STYLE_TEXT_PARAGRAPH, INTERFACE_STRING(InstallerDriveAlreadyHasPartitions));
|
|
} else if (information.sectorSize * information.sectorCount < metadata->totalUncompressedBytes + PARTITION_OVERHEAD) {
|
|
EsIconDisplaySetIcon(statusIcon, ES_ICON_DIALOG_ERROR);
|
|
EsTextDisplayCreate(messageRow, ES_CELL_H_FILL, ES_STYLE_TEXT_PARAGRAPH, INTERFACE_STRING(InstallerDriveNotEnoughSpace));
|
|
showCapacity = true;
|
|
} else {
|
|
EsTextDisplayCreate(messageRow, ES_CELL_H_FILL, ES_STYLE_TEXT_PARAGRAPH, INTERFACE_STRING(InstallerDriveOkay));
|
|
EsElementSetDisabled(installButton, false);
|
|
showCapacity = true;
|
|
}
|
|
|
|
EsElementSetEnabled(useMBRCheckbox, information.sectorSize == 0x200);
|
|
|
|
if (showCapacity) {
|
|
// TODO Localization.
|
|
char buffer[128];
|
|
size_t bytes = EsStringFormat(buffer, sizeof(buffer), "Minimum space required: %D", metadata->totalUncompressedBytes + PARTITION_OVERHEAD);
|
|
EsSpacerCreate(driveInformation, ES_CELL_H_FILL, 0, 0, 10);
|
|
EsTextDisplayCreate(driveInformation, ES_CELL_H_FILL, ES_STYLE_TEXT_PARAGRAPH, buffer, bytes);
|
|
bytes = EsStringFormat(buffer, sizeof(buffer), "Drive capacity: %D", information.sectorSize * information.sectorCount);
|
|
EsTextDisplayCreate(driveInformation, ES_CELL_H_FILL, ES_STYLE_TEXT_PARAGRAPH, buffer, bytes);
|
|
}
|
|
}
|
|
|
|
/////////////////////////////////////////////
|
|
|
|
EsError InstallMBR(EsBlockDeviceInformation driveInformation, uint8_t *sectorBuffer, EsMessageDevice drive,
|
|
void *mbr, EsMessage m, uint8_t *bootloader, size_t bootloaderSectors) {
|
|
// Create the partition table.
|
|
// TODO Adding new entries to existing tables.
|
|
|
|
EsAssert(driveInformation.sectorSize == 0x200);
|
|
EsError error;
|
|
|
|
partitionOffset = 0x800;
|
|
partitionBytes = driveInformation.sectorSize * (driveInformation.sectorCount - partitionOffset);
|
|
|
|
uint32_t partitions[16] = { 0x80 /* bootable */, 0x83 /* type */ };
|
|
uint16_t bootSignature = 0xAA55;
|
|
partitions[2] = partitionOffset; // Offset.
|
|
partitions[3] = driveInformation.sectorCount - 0x800; // Sector count.
|
|
MBRFixPartition(partitions);
|
|
|
|
EsMemoryCopy(sectorBuffer + 0, mbr, 446);
|
|
EsMemoryCopy(sectorBuffer + 446, partitions, 64);
|
|
EsMemoryCopy(sectorBuffer + 510, &bootSignature, 2);
|
|
|
|
{
|
|
EsFileOffset parameters[2] = { 0, driveInformation.sectorSize };
|
|
error = EsDeviceControl(drive.handle, ES_DEVICE_CONTROL_BLOCK_WRITE, sectorBuffer, parameters);
|
|
if (error != ES_SUCCESS) return error;
|
|
}
|
|
|
|
m.user.context1.u = 2;
|
|
EsMessagePost(nullptr, &m);
|
|
|
|
// Install the bootloader.
|
|
|
|
{
|
|
EsFileOffset parameters[2] = { partitionOffset * driveInformation.sectorSize, bootloaderSectors * driveInformation.sectorSize };
|
|
error = EsDeviceControl(drive.handle, ES_DEVICE_CONTROL_BLOCK_WRITE, bootloader, parameters);
|
|
if (error != ES_SUCCESS) return error;
|
|
}
|
|
|
|
m.user.context1.u = 4;
|
|
EsMessagePost(nullptr, &m);
|
|
|
|
return ES_SUCCESS;
|
|
}
|
|
|
|
void GenerateGUID(uint8_t *destination) {
|
|
for (uintptr_t i = 0; i < 16; i++) {
|
|
destination[i] = EsRandomU8();
|
|
}
|
|
|
|
destination[7] = 0x40 | (destination[6] & 0x0F);
|
|
destination[8] = 0x80 | (destination[6] & 0x3F);
|
|
}
|
|
|
|
EsError FATAddFile(FATDirectoryEntry *directory, size_t directoryEntries, uint16_t *fat,
|
|
EsFileOffset totalSectors, EsFileOffset sectorOffset, bool isDirectory,
|
|
const char *name, void *file, size_t fileBytes, EsHandle driveHandle, EsFileOffset *_sectorFirst) {
|
|
// Allocate the sector extent.
|
|
|
|
EsFileOffset sectorFirst = 0;
|
|
size_t sectorCount = (fileBytes + blockDeviceInformation.sectorSize - 1) / blockDeviceInformation.sectorSize;
|
|
|
|
for (uintptr_t i = 2; i < totalSectors - sectorOffset + 2; i++) {
|
|
if (!fat[i]) {
|
|
sectorFirst = i;
|
|
break;
|
|
}
|
|
}
|
|
|
|
EsAssert(sectorFirst);
|
|
|
|
for (uintptr_t i = 0; i < sectorCount; i++) {
|
|
fat[sectorFirst + i] = i == sectorCount - 1 ? 0xFFF8 : sectorFirst + i + 1;
|
|
}
|
|
|
|
// Write out the file contents.
|
|
|
|
uint8_t *buffer = (uint8_t *) EsHeapAllocate(sectorCount * blockDeviceInformation.sectorSize, true);
|
|
EsMemoryCopy(buffer, file, fileBytes);
|
|
EsFileOffset parameters[2] = {};
|
|
parameters[0] = (sectorFirst + sectorOffset) * blockDeviceInformation.sectorSize;
|
|
parameters[1] = sectorCount * blockDeviceInformation.sectorSize;
|
|
EsError error = EsDeviceControl(driveHandle, ES_DEVICE_CONTROL_BLOCK_WRITE, buffer, parameters);
|
|
EsHeapFree(buffer);
|
|
if (error != ES_SUCCESS) return error;
|
|
|
|
// Create the directory entry.
|
|
|
|
FATDirectoryEntry *entry = nullptr;
|
|
|
|
for (uintptr_t i = 0; i < directoryEntries; i++) {
|
|
if (!directory[i].name[0]) {
|
|
entry = directory + i;
|
|
break;
|
|
}
|
|
}
|
|
|
|
EsAssert(entry);
|
|
EsMemoryCopy(entry->name, name, 11);
|
|
entry->firstClusterHigh = sectorFirst >> 16;
|
|
entry->firstClusterLow = sectorFirst & 0xFFFF;
|
|
entry->fileSizeBytes = isDirectory ? 0 : fileBytes;
|
|
entry->attributes = isDirectory ? 0x10 : 0x00;
|
|
entry->creationDate = 33;
|
|
entry->accessedDate = 33;
|
|
entry->modificationDate = 33;
|
|
|
|
if (_sectorFirst) {
|
|
*_sectorFirst = sectorFirst;
|
|
}
|
|
|
|
return ES_SUCCESS;
|
|
}
|
|
|
|
void FATAddDotFiles(FATDirectoryEntry *directory, EsFileOffset thisSector, EsFileOffset parentSector) {
|
|
EsMemoryCopy(directory[0].name, ". ", 11);
|
|
directory[0].firstClusterHigh = thisSector >> 16;
|
|
directory[0].firstClusterLow = thisSector & 0xFFFF;
|
|
directory[0].attributes = 0x10;
|
|
directory[0].creationDate = 33;
|
|
directory[0].accessedDate = 33;
|
|
directory[0].modificationDate = 33;
|
|
EsMemoryCopy(directory[1].name, ".. ", 11);
|
|
directory[1].firstClusterHigh = parentSector >> 16;
|
|
directory[1].firstClusterLow = parentSector & 0xFFFF;
|
|
directory[1].attributes = 0x10;
|
|
directory[1].creationDate = 33;
|
|
directory[1].accessedDate = 33;
|
|
directory[1].modificationDate = 33;
|
|
}
|
|
|
|
EsError InstallGPT(EsBlockDeviceInformation driveInformation, EsMessageDevice drive, EsMessage m,
|
|
void *uefi1, size_t uefi1Bytes, void *uefi2, size_t uefi2Bytes) {
|
|
// Create the partition table.
|
|
// TODO Adding new entries to existing tables.
|
|
|
|
const uint8_t espGUID[] = { 0x28, 0x73, 0x2A, 0xC1, 0x1F, 0xF8, 0xD2, 0x11, 0xBA, 0x4B, 0x00, 0xA0, 0xC9, 0x3E, 0xC9, 0x3B };
|
|
const uint8_t dataGUID[] = { 0xAF, 0x3D, 0xC6, 0x0F, 0x83, 0x84, 0x72, 0x47, 0x8E, 0x79, 0x3D, 0x69, 0xD8, 0x47, 0x7D, 0xE4 };
|
|
|
|
EsError error;
|
|
EsFileOffset parameters[2] = {};
|
|
|
|
size_t partitionEntryCount = 0x80;
|
|
size_t tableSectors = (partitionEntryCount * sizeof(GPTEntry) + driveInformation.sectorSize - 1) / driveInformation.sectorSize;
|
|
|
|
ProtectiveMBR *mbr = (ProtectiveMBR *) EsHeapAllocate(driveInformation.sectorSize, true);
|
|
uint32_t mbrEntry[4];
|
|
mbrEntry[0] = 0x00020000;
|
|
mbrEntry[1] = 0xFFFFFFEE;
|
|
mbrEntry[2] = 0x00000001;
|
|
mbrEntry[3] = driveInformation.sectorCount > 0xFFFFFFFF ? 0xFFFFFFFF : driveInformation.sectorCount - 1;
|
|
mbr->signature = 0xAA55;
|
|
EsMemoryCopy(mbr->entry, mbrEntry, 16);
|
|
|
|
GPTHeader *header = (GPTHeader *) EsHeapAllocate(driveInformation.sectorSize, true);
|
|
EsMemoryCopy(header->signature, "EFI PART", 8);
|
|
header->revision = 0x00010000;
|
|
header->headerBytes = 0x5C;
|
|
header->headerSelfLBA = 1;
|
|
header->headerBackupLBA = driveInformation.sectorCount - 1;
|
|
header->firstUsableLBA = 2 + tableSectors;
|
|
header->lastUsableLBA = driveInformation.sectorCount - 2;
|
|
GenerateGUID(header->driveGUID);
|
|
header->tableLBA = 2;
|
|
header->partitionEntryCount = partitionEntryCount;
|
|
header->partitionEntryBytes = sizeof(GPTEntry);
|
|
|
|
GPTEntry *partitionTable = (GPTEntry *) EsHeapAllocate(partitionEntryCount * sizeof(GPTEntry), true);
|
|
EsMemoryCopy(partitionTable[0].typeGUID, espGUID, 16);
|
|
GenerateGUID(partitionTable[0].partitionGUID);
|
|
partitionTable[0].firstLBA = header->firstUsableLBA;
|
|
partitionTable[0].lastLBA = partitionTable[0].firstLBA + 16777216 / driveInformation.sectorSize - 1;
|
|
EsMemoryCopy(partitionTable[1].typeGUID, dataGUID, 16);
|
|
GenerateGUID(partitionTable[1].partitionGUID);
|
|
partitionTable[1].firstLBA = partitionTable[0].lastLBA + 1;
|
|
partitionTable[1].lastLBA = header->lastUsableLBA;
|
|
|
|
header->tableCRC32 = CalculateCRC32(partitionTable, partitionEntryCount * sizeof(GPTEntry));
|
|
header->headerCRC32 = CalculateCRC32(header, header->headerBytes);
|
|
|
|
GPTHeader *backupHeader = (GPTHeader *) EsHeapAllocate(driveInformation.sectorSize, true);
|
|
EsMemoryCopy(backupHeader, header, driveInformation.sectorSize);
|
|
backupHeader->headerSelfLBA = header->headerBackupLBA;
|
|
backupHeader->headerBackupLBA = header->headerSelfLBA;
|
|
backupHeader->headerCRC32 = 0;
|
|
backupHeader->headerCRC32 = CalculateCRC32(backupHeader, header->headerBytes);
|
|
|
|
parameters[0] = 0;
|
|
parameters[1] = driveInformation.sectorSize;
|
|
error = EsDeviceControl(drive.handle, ES_DEVICE_CONTROL_BLOCK_WRITE, mbr, parameters);
|
|
if (error != ES_SUCCESS) return error;
|
|
parameters[0] = header->headerSelfLBA * driveInformation.sectorSize;
|
|
parameters[1] = driveInformation.sectorSize;
|
|
error = EsDeviceControl(drive.handle, ES_DEVICE_CONTROL_BLOCK_WRITE, header, parameters);
|
|
if (error != ES_SUCCESS) return error;
|
|
parameters[0] = header->tableLBA * driveInformation.sectorSize;
|
|
parameters[1] = driveInformation.sectorSize * tableSectors;
|
|
error = EsDeviceControl(drive.handle, ES_DEVICE_CONTROL_BLOCK_WRITE, partitionTable, parameters);
|
|
if (error != ES_SUCCESS) return error;
|
|
parameters[0] = header->headerBackupLBA * driveInformation.sectorSize;
|
|
parameters[1] = driveInformation.sectorSize;
|
|
error = EsDeviceControl(drive.handle, ES_DEVICE_CONTROL_BLOCK_WRITE, backupHeader, parameters);
|
|
if (error != ES_SUCCESS) return error;
|
|
|
|
partitionOffset = partitionTable[1].firstLBA;
|
|
partitionBytes = (partitionTable[1].lastLBA - partitionTable[1].firstLBA + 1) * driveInformation.sectorSize;
|
|
|
|
m.user.context1.u = 2;
|
|
EsMessagePost(nullptr, &m);
|
|
|
|
// Load the kernel.
|
|
|
|
size_t kernelBytes;
|
|
void *kernel = EsFileReadAll(EsLiteral(K_SYSTEM_FOLDER "/Kernel.esx"), &kernelBytes);
|
|
if (!kernel) return ES_ERROR_FILE_DOES_NOT_EXIST;
|
|
|
|
m.user.context1.u = 3;
|
|
EsMessagePost(nullptr, &m);
|
|
|
|
// Format the ESP.
|
|
|
|
EsAssert(driveInformation.sectorSize >= 0x200 && driveInformation.sectorSize <= 0x1000); // We only support FAT16.
|
|
|
|
EsFileOffset espOffset = partitionTable[0].firstLBA;
|
|
EsFileOffset espSectors = partitionTable[0].lastLBA - partitionTable[0].firstLBA + 1;
|
|
EsAssert(espSectors * driveInformation.sectorSize == 16777216 && espSectors < 0x10000);
|
|
|
|
SuperBlock16 *superBlock = (SuperBlock16 *) EsHeapAllocate(driveInformation.sectorSize, true);
|
|
superBlock->jmp[0] = 0xEB;
|
|
superBlock->jmp[1] = 0x3C;
|
|
superBlock->jmp[2] = 0x90;
|
|
EsMemoryCopy(superBlock->oemName, "MSWIN4.1", 8);
|
|
superBlock->bytesPerSector = driveInformation.sectorSize;
|
|
superBlock->sectorsPerCluster = 1;
|
|
superBlock->reservedSectors = 1;
|
|
superBlock->fatCount = 2;
|
|
superBlock->rootDirectoryEntries = 512;
|
|
superBlock->totalSectors = espSectors;
|
|
superBlock->mediaDescriptor = 0xF8;
|
|
superBlock->sectorsPerFAT16 = (superBlock->totalSectors * 2 + driveInformation.sectorSize - 1) / driveInformation.sectorSize;
|
|
superBlock->sectorsPerTrack = 63;
|
|
superBlock->heads = 256;
|
|
superBlock->deviceID = 0x80;
|
|
superBlock->signature = 0x29;
|
|
superBlock->serial = (uint32_t) EsRandomU64();
|
|
EsMemoryCopy(superBlock->label, "EFI SYSTEM ", 11);
|
|
EsMemoryCopy(&superBlock->systemIdentifier, "FAT16 ", 8);
|
|
superBlock->_unused1[448] = 0x55;
|
|
superBlock->_unused1[449] = 0xAA;
|
|
|
|
uint32_t rootDirectoryOffset = superBlock->reservedSectors + superBlock->fatCount * superBlock->sectorsPerFAT16 + espOffset;
|
|
uint32_t rootDirectorySectors = (superBlock->rootDirectoryEntries * sizeof(FATDirectoryEntry) + (superBlock->bytesPerSector - 1)) / superBlock->bytesPerSector;
|
|
uint32_t sectorOffset = rootDirectoryOffset + rootDirectorySectors - 2 * superBlock->sectorsPerCluster;
|
|
size_t directoryEntriesPerSector = driveInformation.sectorSize / sizeof(FATDirectoryEntry);
|
|
|
|
uint16_t *fat = (uint16_t *) EsHeapAllocate(superBlock->sectorsPerFAT16 * driveInformation.sectorSize, true);
|
|
fat[0] = 0xFFF8;
|
|
fat[1] = 0xFFFF;
|
|
|
|
EsFileOffset efiDirectorySector, bootDirectorySector;
|
|
FATDirectoryEntry *rootDirectory = (FATDirectoryEntry *) EsHeapAllocate(rootDirectorySectors * driveInformation.sectorSize, true);
|
|
FATDirectoryEntry *efiDirectory = (FATDirectoryEntry *) EsHeapAllocate(driveInformation.sectorSize, true);
|
|
FATDirectoryEntry *bootDirectory = (FATDirectoryEntry *) EsHeapAllocate(driveInformation.sectorSize, true);
|
|
error = FATAddFile(rootDirectory, superBlock->rootDirectoryEntries, fat, superBlock->totalSectors, sectorOffset, false,
|
|
"ESLOADERBIN", uefi2, uefi2Bytes, drive.handle, nullptr);
|
|
if (error != ES_SUCCESS) return error;
|
|
error = FATAddFile(rootDirectory, superBlock->rootDirectoryEntries, fat, superBlock->totalSectors, sectorOffset, false,
|
|
"ESKERNELESX", kernel, kernelBytes, drive.handle, nullptr);
|
|
if (error != ES_SUCCESS) return error;
|
|
error = FATAddFile(rootDirectory, superBlock->rootDirectoryEntries, fat, superBlock->totalSectors, sectorOffset, false,
|
|
"ESIID DAT", &installationIdentifier, 16, drive.handle, nullptr);
|
|
if (error != ES_SUCCESS) return error;
|
|
error = FATAddFile(rootDirectory, superBlock->rootDirectoryEntries, fat, superBlock->totalSectors, sectorOffset, true,
|
|
"EFI ", efiDirectory, driveInformation.sectorSize, drive.handle, &efiDirectorySector);
|
|
if (error != ES_SUCCESS) return error;
|
|
FATAddDotFiles(efiDirectory, efiDirectorySector, 0);
|
|
error = FATAddFile(efiDirectory, directoryEntriesPerSector, fat, superBlock->totalSectors, sectorOffset, true,
|
|
"BOOT ", bootDirectory, driveInformation.sectorSize, drive.handle, &bootDirectorySector);
|
|
if (error != ES_SUCCESS) return error;
|
|
FATAddDotFiles(bootDirectory, bootDirectorySector, efiDirectorySector);
|
|
error = FATAddFile(bootDirectory, directoryEntriesPerSector, fat, superBlock->totalSectors, sectorOffset, false,
|
|
"BOOTX64 EFI", uefi1, uefi1Bytes, drive.handle, nullptr);
|
|
if (error != ES_SUCCESS) return error;
|
|
|
|
m.user.context1.u = 4;
|
|
EsMessagePost(nullptr, &m);
|
|
|
|
parameters[0] = espOffset * driveInformation.sectorSize;
|
|
parameters[1] = driveInformation.sectorSize;
|
|
error = EsDeviceControl(drive.handle, ES_DEVICE_CONTROL_BLOCK_WRITE, superBlock, parameters);
|
|
if (error != ES_SUCCESS) return error;
|
|
parameters[0] = (espOffset + 1) * driveInformation.sectorSize;
|
|
parameters[1] = superBlock->sectorsPerFAT16 * driveInformation.sectorSize;
|
|
error = EsDeviceControl(drive.handle, ES_DEVICE_CONTROL_BLOCK_WRITE, fat, parameters);
|
|
if (error != ES_SUCCESS) return error;
|
|
parameters[0] = (espOffset + 1 + superBlock->sectorsPerFAT16) * driveInformation.sectorSize;
|
|
parameters[1] = superBlock->sectorsPerFAT16 * driveInformation.sectorSize;
|
|
error = EsDeviceControl(drive.handle, ES_DEVICE_CONTROL_BLOCK_WRITE, fat, parameters);
|
|
if (error != ES_SUCCESS) return error;
|
|
parameters[0] = rootDirectoryOffset * driveInformation.sectorSize;
|
|
parameters[1] = rootDirectorySectors * driveInformation.sectorSize;
|
|
error = EsDeviceControl(drive.handle, ES_DEVICE_CONTROL_BLOCK_WRITE, rootDirectory, parameters);
|
|
if (error != ES_SUCCESS) return error;
|
|
parameters[0] = (efiDirectorySector + sectorOffset) * blockDeviceInformation.sectorSize;
|
|
parameters[1] = blockDeviceInformation.sectorSize;
|
|
error = EsDeviceControl(driveHandle, ES_DEVICE_CONTROL_BLOCK_WRITE, efiDirectory, parameters);
|
|
if (error != ES_SUCCESS) return error;
|
|
parameters[0] = (bootDirectorySector + sectorOffset) * blockDeviceInformation.sectorSize;
|
|
parameters[1] = blockDeviceInformation.sectorSize;
|
|
error = EsDeviceControl(driveHandle, ES_DEVICE_CONTROL_BLOCK_WRITE, bootDirectory, parameters);
|
|
if (error != ES_SUCCESS) return error;
|
|
|
|
// Cleanup.
|
|
|
|
EsHeapFree(mbr);
|
|
EsHeapFree(header);
|
|
EsHeapFree(partitionTable);
|
|
EsHeapFree(backupHeader);
|
|
EsHeapFree(superBlock);
|
|
EsHeapFree(fat);
|
|
EsHeapFree(rootDirectory);
|
|
EsHeapFree(efiDirectory);
|
|
EsHeapFree(bootDirectory);
|
|
|
|
m.user.context1.u = 6;
|
|
EsMessagePost(nullptr, &m);
|
|
|
|
return ES_SUCCESS;
|
|
}
|
|
|
|
EsError Install() {
|
|
EsMessage m = { MSG_SET_PROGRESS };
|
|
EsError error;
|
|
|
|
for (int i = 0; i < 16; i++) {
|
|
installationIdentifier.d[i] = EsRandomU8();
|
|
}
|
|
|
|
size_t mbrBytes, stage1Bytes, stage2Bytes, uefi1Bytes, uefi2Bytes, kernelBytes;
|
|
void *mbr = EsFileReadAll(EsLiteral("0:/Installer Data/mbr.dat"), &mbrBytes);
|
|
void *stage1 = EsFileReadAll(EsLiteral("0:/Installer Data/stage1.dat"), &stage1Bytes);
|
|
void *stage2 = EsFileReadAll(EsLiteral("0:/Installer Data/stage2.dat"), &stage2Bytes);
|
|
void *uefi1 = EsFileReadAll(EsLiteral("0:/Installer Data/uefi1.dat"), &uefi1Bytes);
|
|
void *uefi2 = EsFileReadAll(EsLiteral("0:/Installer Data/uefi2.dat"), &uefi2Bytes);
|
|
|
|
if (!mbr || !stage1 || !stage2 || !uefi1 || !uefi2) {
|
|
return ES_ERROR_FILE_DOES_NOT_EXIST;
|
|
}
|
|
|
|
EsMessageDevice drive = {};
|
|
|
|
for (uintptr_t i = 0; i < connectedDrives.Length(); i++) {
|
|
if (connectedDrives[i].id == selectedDriveID) {
|
|
drive = connectedDrives[i];
|
|
}
|
|
}
|
|
|
|
EsAssert(drive.handle);
|
|
EsBlockDeviceInformation driveInformation = ConnectedDriveGetInformation(drive.handle);
|
|
blockDeviceInformation = driveInformation;
|
|
driveHandle = drive.handle;
|
|
|
|
uint8_t *sectorBuffer = (uint8_t *) EsHeapAllocate(driveInformation.sectorSize, false);
|
|
if (!sectorBuffer) return ES_ERROR_INSUFFICIENT_RESOURCES;
|
|
|
|
size_t bootloaderSectors = (stage1Bytes + stage2Bytes + driveInformation.sectorSize - 1) / driveInformation.sectorSize;
|
|
uint8_t *bootloader = (uint8_t *) EsHeapAllocate(bootloaderSectors * driveInformation.sectorSize, true);
|
|
EsMemoryCopy(bootloader, stage1, stage1Bytes);
|
|
EsMemoryCopy(bootloader + stage1Bytes, stage2, stage2Bytes);
|
|
|
|
m.user.context1.u = 1;
|
|
EsMessagePost(nullptr, &m);
|
|
|
|
// Create the partitions and possibly install the bootloader.
|
|
|
|
if (useMBR) {
|
|
error = InstallMBR(driveInformation, sectorBuffer, drive, mbr, m, bootloader, bootloaderSectors);
|
|
} else {
|
|
error = InstallGPT(driveInformation, drive, m, uefi1, uefi1Bytes, uefi2, uefi2Bytes);
|
|
}
|
|
|
|
if (error != ES_SUCCESS) {
|
|
return error;
|
|
}
|
|
|
|
// Format the partition.
|
|
|
|
void *kernel;
|
|
|
|
if (useMBR) {
|
|
kernel = EsFileReadAll(EsLiteral(K_SYSTEM_FOLDER "/Kernel.esx"), &kernelBytes);
|
|
if (!kernel) return ES_ERROR_FILE_DOES_NOT_EXIST;
|
|
m.user.context1.u = 6;
|
|
EsMessagePost(nullptr, &m);
|
|
} else {
|
|
// The kernel is located on the ESP.
|
|
kernel = nullptr;
|
|
kernelBytes = 0;
|
|
}
|
|
|
|
Format(partitionBytes, interfaceString_InstallerVolumeLabel, installationIdentifier, kernel, kernelBytes);
|
|
FlushWriteBuffer();
|
|
|
|
if (formatError != ES_SUCCESS) {
|
|
return formatError;
|
|
}
|
|
|
|
m.user.context1.u = 8;
|
|
EsMessagePost(nullptr, &m);
|
|
|
|
// Mount the new partition and extract the archive to it.
|
|
|
|
EsDeviceControl(drive.handle, ES_DEVICE_CONTROL_BLOCK_DETECT_FS, nullptr, nullptr);
|
|
|
|
m.user.context1.u = 9;
|
|
EsMessagePost(nullptr, &m);
|
|
|
|
if (ES_ERROR_TIMEOUT_REACHED == (EsError) EsWait(&mountNewFileSystemEvent, 1, 10000)) {
|
|
return ES_ERROR_TIMEOUT_REACHED;
|
|
}
|
|
|
|
error = Extract(EsLiteral("0:/Installer Data/archive.dat"), newFileSystemMountPoint.prefix, newFileSystemMountPoint.prefixBytes);
|
|
|
|
if (error == ES_ERROR_CORRUPT_DATA) {
|
|
archiveCRCError = true;
|
|
}
|
|
|
|
if (error != ES_SUCCESS) {
|
|
return error;
|
|
}
|
|
|
|
return ES_SUCCESS;
|
|
}
|
|
|
|
void InstallThread(EsGeneric) {
|
|
EsPerformanceTimerPush();
|
|
installError = Install();
|
|
EsPrint("Installation finished in %Fs. Extracted %D from the archive. Error code = %d.\n", EsPerformanceTimerPop(), metadata->totalUncompressedBytes, installError);
|
|
|
|
EsMessage m = { MSG_SET_PROGRESS };
|
|
m.user.context1.u = 100;
|
|
EsMessagePost(nullptr, &m);
|
|
}
|
|
|
|
void WriteNewConfiguration() {
|
|
size_t newSystemConfigurationPathBytes, newSystemConfigurationBytes;
|
|
char *newSystemConfigurationPath = EsStringAllocateAndFormat(&newSystemConfigurationPathBytes, "%s/" K_SYSTEM_FOLDER_NAME "/Default.ini",
|
|
newFileSystemMountPoint.prefixBytes, newFileSystemMountPoint.prefix);
|
|
char *newSystemConfiguration = (char *) EsFileReadAll(newSystemConfigurationPath, newSystemConfigurationPathBytes, &newSystemConfigurationBytes);
|
|
|
|
size_t lineBufferBytes = 4096;
|
|
char *lineBuffer = (char *) EsHeapAllocate(lineBufferBytes, false);
|
|
EsINIState s = { .buffer = newSystemConfiguration, .bytes = newSystemConfigurationBytes };
|
|
EsBuffer buffer = { .canGrow = true };
|
|
|
|
while (EsINIParse(&s)) {
|
|
if (!s.sectionClassBytes && 0 == EsStringCompareRaw(s.section, s.sectionBytes, EsLiteral("ui_fonts"))
|
|
&& 0 == EsStringCompareRaw(s.key, s.keyBytes, EsLiteral("sans"))) {
|
|
EsBufferFormat(&buffer, "sans=%z\n", cSelectedFont);
|
|
} else if (!s.sectionClassBytes && 0 == EsStringCompareRaw(s.section, s.sectionBytes, EsLiteral("general"))
|
|
&& 0 == EsStringCompareRaw(s.key, s.keyBytes, EsLiteral("installation_state"))) {
|
|
EsBufferFormat(&buffer, "installation_state=0\n");
|
|
} else if (!s.sectionClassBytes && 0 == EsStringCompareRaw(s.section, s.sectionBytes, EsLiteral("general"))
|
|
&& 0 == EsStringCompareRaw(s.key, s.keyBytes, EsLiteral("clock_offset_ms"))) {
|
|
EsAssert(false);
|
|
} else if (!s.sectionClassBytes && 0 == EsStringCompareRaw(s.section, s.sectionBytes, EsLiteral("general"))
|
|
&& 0 == EsStringCompareRaw(s.key, s.keyBytes, EsLiteral("user_name"))) {
|
|
EsAssert(false);
|
|
} else if (!s.sectionClassBytes && 0 == EsStringCompareRaw(s.section, s.sectionBytes, EsLiteral("general")) && !s.keyBytes) {
|
|
size_t userNameBytes;
|
|
char *userName = EsTextboxGetContents(userNameTextbox, &userNameBytes);
|
|
EsBufferFormat(&buffer, "[general]\nclock_offset_ms=%d\nuser_name=%s\n", clockOffsetMs, userNameBytes, userName);
|
|
EsHeapFree(userName);
|
|
} else {
|
|
size_t lineBytes = EsINIFormat(&s, lineBuffer, lineBufferBytes);
|
|
EsBufferWrite(&buffer, lineBuffer, lineBytes);
|
|
EsAssert(lineBytes < lineBufferBytes);
|
|
}
|
|
}
|
|
|
|
EsFileWriteAll(newSystemConfigurationPath, newSystemConfigurationPathBytes, buffer.out, buffer.position);
|
|
|
|
EsHeapFree(newSystemConfigurationPath);
|
|
EsHeapFree(buffer.out);
|
|
EsHeapFree(lineBuffer);
|
|
}
|
|
|
|
/////////////////////////////////////////////
|
|
|
|
void ButtonViewLicenses(EsInstance *, EsElement *, EsCommand *) {
|
|
EsPanelSwitchTo(switcher, panelLicenses, ES_TRANSITION_FADE);
|
|
}
|
|
|
|
void ButtonInstallOptions(EsInstance *, EsElement *, EsCommand *) {
|
|
EsPanelSwitchTo(switcher, panelInstallOptions, ES_TRANSITION_FADE);
|
|
}
|
|
|
|
void ButtonShutdown(EsInstance *, EsElement *, EsCommand *) {
|
|
EsSyscall(ES_SYSCALL_SHUTDOWN, SHUTDOWN_ACTION_POWER_OFF, 0, 0, 0);
|
|
}
|
|
|
|
void ButtonRestart(EsInstance *, EsElement *, EsCommand *) {
|
|
EsSyscall(ES_SYSCALL_SHUTDOWN, SHUTDOWN_ACTION_RESTART, 0, 0, 0);
|
|
}
|
|
|
|
void ButtonInstall(EsInstance *, EsElement *, EsCommand *) {
|
|
useMBR = EsButtonGetCheck(useMBRCheckbox) == ES_CHECK_CHECKED;
|
|
EsPanelSwitchTo(switcher, panelCustomizeOptions, ES_TRANSITION_FADE);
|
|
EsElementFocus(userNameTextbox);
|
|
startedInstallation = true;
|
|
EsThreadCreate(InstallThread, nullptr, 0);
|
|
}
|
|
|
|
void ButtonFont(EsInstance *, EsElement *element, EsCommand *) {
|
|
EsFontInformation information;
|
|
cSelectedFont = (const char *) element->userData.p;
|
|
|
|
if (EsFontDatabaseLookupByName(cSelectedFont, -1, &information)) {
|
|
_EsUISetFont(information.id);
|
|
}
|
|
}
|
|
|
|
void Complete() {
|
|
if (installError == ES_SUCCESS) {
|
|
WriteNewConfiguration();
|
|
EsPanelSwitchTo(switcher, panelComplete, ES_TRANSITION_FADE);
|
|
} else {
|
|
EsPanel *row = EsPanelCreate(panelError, ES_CELL_H_FILL | ES_PANEL_HORIZONTAL);
|
|
EsIconDisplayCreate(row, ES_FLAGS_DEFAULT, 0, ES_ICON_DIALOG_ERROR);
|
|
EsSpacerCreate(row, ES_FLAGS_DEFAULT, 0, 15, 0);
|
|
|
|
if (installError == ES_ERROR_INSUFFICIENT_RESOURCES) {
|
|
EsTextDisplayCreate(row, ES_CELL_H_FILL, ES_STYLE_TEXT_PARAGRAPH, INTERFACE_STRING(InstallerFailedResources));
|
|
} else if (archiveCRCError) {
|
|
EsTextDisplayCreate(row, ES_CELL_H_FILL, ES_STYLE_TEXT_PARAGRAPH, INTERFACE_STRING(InstallerFailedArchiveCRCError));
|
|
} else {
|
|
EsTextDisplayCreate(row, ES_CELL_H_FILL, ES_STYLE_TEXT_PARAGRAPH, INTERFACE_STRING(InstallerFailedGeneric));
|
|
}
|
|
|
|
EsSpacerCreate(panelError, ES_CELL_FILL);
|
|
EsPanel *buttonsRow = EsPanelCreate(panelError, ES_CELL_H_FILL | ES_PANEL_HORIZONTAL, &styleButtonsRow);
|
|
EsSpacerCreate(buttonsRow, ES_CELL_H_FILL);
|
|
EsButtonOnCommand(EsButtonCreate(buttonsRow, ES_FLAGS_DEFAULT, 0, INTERFACE_STRING(DesktopRestartAction)), ButtonRestart);
|
|
|
|
EsPanelSwitchTo(switcher, panelError, ES_TRANSITION_FADE);
|
|
}
|
|
}
|
|
|
|
void ButtonFinish(EsInstance *, EsElement *, EsCommand *) {
|
|
EsDateComponents base, modified;
|
|
EsDateNowUTC(&base);
|
|
modified = base;
|
|
// TODO Proper date/time parsing.
|
|
size_t contentBytes;
|
|
char *contents = EsTextboxGetContents(timeTextbox, &contentBytes);
|
|
int64_t input = EsIntegerParse(contents, contentBytes);
|
|
EsHeapFree(contents);
|
|
modified.hour = input / 100;
|
|
modified.minute = (input % 100) % 60;
|
|
clockOffsetMs = DateToLinear(&modified) - DateToLinear(&base);
|
|
|
|
if (progress == 100) {
|
|
Complete();
|
|
} else {
|
|
onWaitScreen = true;
|
|
EsPanelSwitchTo(switcher, panelWait, ES_TRANSITION_FADE);
|
|
}
|
|
}
|
|
|
|
int DrivesListMessage(EsElement *element, EsMessage *message) {
|
|
if (message->type == ES_MSG_LIST_VIEW_SELECT) {
|
|
EsGeneric deviceID;
|
|
|
|
if (EsListViewFixedItemGetSelected(((EsListView *) element), &deviceID)) {
|
|
for (uintptr_t i = 0; i < connectedDrives.Length(); i++) {
|
|
if (connectedDrives[i].id == deviceID.u) {
|
|
ConnectedDriveSelect(i);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int SwitcherMessage(EsElement *, EsMessage *message) {
|
|
if (message->type == ES_MSG_GET_WIDTH || message->type == ES_MSG_GET_HEIGHT) {
|
|
return EsMessageSend(panelInstallOptions, message);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int UserNameTextboxMessage(EsElement *, EsMessage *message) {
|
|
if (message->type == ES_MSG_TEXTBOX_UPDATED) {
|
|
EsElementSetEnabled(finishButton, EsTextboxGetLineLength(userNameTextbox));
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
void _start() {
|
|
_init();
|
|
|
|
metadata = (InstallerMetadata *) EsFileReadAll(EsLiteral("0:/Installer Data/metadata.dat"), nullptr);
|
|
EsAssert(metadata);
|
|
|
|
mountNewFileSystemEvent = EsEventCreate(true);
|
|
|
|
EsWindow *window = EsWindowCreate(_EsInstanceCreate(sizeof(EsInstance), nullptr), ES_WINDOW_PLAIN);
|
|
EsHandle handle = _EsWindowGetHandle(window);
|
|
window->instance->window = window;
|
|
|
|
EsRectangle screen;
|
|
EsSyscall(ES_SYSCALL_SCREEN_BOUNDS_GET, 0, (uintptr_t) &screen, 0, 0);
|
|
EsSyscall(ES_SYSCALL_WINDOW_MOVE, handle, (uintptr_t) &screen, 0, 0);
|
|
EsSyscall(ES_SYSCALL_WINDOW_SET_PROPERTY, handle, ES_WINDOW_SOLID_TRUE, 0, ES_WINDOW_PROPERTY_SOLID);
|
|
EsSyscall(ES_SYSCALL_WINDOW_SET_PROPERTY, handle, 0, 0, ES_WINDOW_PROPERTY_FOCUSED);
|
|
|
|
EsPanel *clearBackground = EsPanelCreate(window, ES_CELL_FILL, ES_STYLE_CLEAR_BACKGROUND);
|
|
EsPanel *sheet = EsPanelCreate(clearBackground, ES_PANEL_VERTICAL | ES_CELL_PUSH | ES_CELL_CENTER, ES_STYLE_INSTALLER_ROOT);
|
|
switcher = EsPanelCreate(sheet, ES_CELL_H_FILL | ES_PANEL_SWITCHER);
|
|
switcher->messageUser = SwitcherMessage;
|
|
|
|
{
|
|
panelInstallOptions = EsPanelCreate(switcher, ES_CELL_H_FILL, &styleRoot);
|
|
EsTextDisplayCreate(panelInstallOptions, ES_CELL_H_FILL, ES_STYLE_TEXT_HEADING0, INTERFACE_STRING(InstallerTitle));
|
|
|
|
EsPanel *drivesPanel = EsPanelCreate(panelInstallOptions, ES_CELL_H_FILL, ES_STYLE_PANEL_INSET);
|
|
EsPanel *drivesSplit = EsPanelCreate(drivesPanel, ES_CELL_H_FILL | ES_PANEL_HORIZONTAL);
|
|
EsPanel *drivesLeft = EsPanelCreate(drivesSplit, ES_CELL_H_FILL);
|
|
EsTextDisplayCreate(drivesLeft, ES_CELL_H_FILL, ES_STYLE_TEXT_PARAGRAPH, INTERFACE_STRING(InstallerDrivesList));
|
|
EsSpacerCreate(drivesLeft, ES_CELL_H_FILL, 0, 0, 8);
|
|
drivesList = EsListViewCreate(drivesLeft, ES_CELL_H_FILL | ES_LIST_VIEW_CHOICE_SELECT | ES_LIST_VIEW_FIXED_ITEMS, ES_STYLE_LIST_CHOICE_BORDERED);
|
|
drivesList->messageUser = DrivesListMessage;
|
|
EsElementFocus(drivesList);
|
|
EsSpacerCreate(drivesSplit, ES_CELL_V_FILL, 0, 25, 0);
|
|
driveInformation = EsPanelCreate(drivesSplit, ES_CELL_H_FILL | ES_CELL_V_FILL);
|
|
EsTextDisplayCreate(driveInformation, ES_CELL_H_FILL | ES_CELL_V_FILL, &styleDrivesSelectHint, INTERFACE_STRING(InstallerDrivesSelectHint));
|
|
|
|
useMBRCheckbox = EsButtonCreate(panelInstallOptions, ES_CELL_H_FILL | ES_BUTTON_CHECKBOX | ES_ELEMENT_DISABLED, 0, INTERFACE_STRING(InstallerUseMBR));
|
|
|
|
EsPanel *buttonsRow = EsPanelCreate(panelInstallOptions, ES_CELL_H_FILL | ES_PANEL_HORIZONTAL, &styleButtonsRow);
|
|
EsButtonOnCommand(EsButtonCreate(buttonsRow, ES_FLAGS_DEFAULT, 0, INTERFACE_STRING(InstallerViewLicenses)), ButtonViewLicenses);
|
|
EsButtonOnCommand(EsButtonCreate(buttonsRow, ES_FLAGS_DEFAULT, ES_STYLE_PUSH_BUTTON_DANGEROUS, INTERFACE_STRING(DesktopShutdownAction)), ButtonShutdown);
|
|
EsSpacerCreate(buttonsRow, ES_CELL_H_FILL);
|
|
installButton = EsButtonCreate(buttonsRow, ES_ELEMENT_DISABLED, 0, INTERFACE_STRING(InstallerInstall));
|
|
EsButtonOnCommand(installButton, ButtonInstall);
|
|
}
|
|
|
|
{
|
|
panelLicenses = EsPanelCreate(switcher, ES_CELL_FILL, &styleRoot);
|
|
EsTextbox *textbox = EsTextboxCreate(panelLicenses, ES_CELL_FILL | ES_TEXTBOX_MULTILINE);
|
|
EsElementSetDisabled(textbox);
|
|
size_t bytes;
|
|
char *data = (char *) EsFileReadAll(EsLiteral("0:/Installer Data/licenses.txt"), &bytes);
|
|
EsTextboxInsert(textbox, data, bytes);
|
|
EsHeapFree(data);
|
|
EsButtonOnCommand(EsButtonCreate(panelLicenses, ES_CELL_H_LEFT, 0, INTERFACE_STRING(InstallerGoBack)), ButtonInstallOptions);
|
|
}
|
|
|
|
{
|
|
panelCustomizeOptions = EsPanelCreate(switcher, ES_CELL_FILL, &styleRoot);
|
|
EsTextDisplayCreate(panelCustomizeOptions, ES_CELL_H_FILL, ES_STYLE_TEXT_HEADING0, INTERFACE_STRING(InstallerTitle));
|
|
EsTextDisplayCreate(panelCustomizeOptions, ES_CELL_H_FILL, ES_STYLE_TEXT_HEADING1, INTERFACE_STRING(InstallerCustomizeOptions));
|
|
|
|
EsPanel *table = EsPanelCreate(panelCustomizeOptions, ES_CELL_H_CENTER | ES_PANEL_HORIZONTAL | ES_PANEL_TABLE, ES_STYLE_PANEL_FORM_TABLE);
|
|
EsPanelSetBands(table, 2 /* columns */);
|
|
|
|
EsTextDisplayCreate(table, ES_CELL_H_RIGHT, ES_STYLE_TEXT_LABEL, INTERFACE_STRING(InstallerUserName));
|
|
userNameTextbox = EsTextboxCreate(table, ES_CELL_H_LEFT);
|
|
userNameTextbox->messageUser = UserNameTextboxMessage;
|
|
|
|
// TODO Proper date formatting.
|
|
EsDateComponents date;
|
|
EsDateNowUTC(&date);
|
|
char timeBuffer[64];
|
|
ptrdiff_t timeBytes = EsStringFormat(timeBuffer, sizeof(timeBuffer), "%d%d:%d%d",
|
|
date.hour / 10, date.hour % 10, date.minute / 10, date.minute % 10);
|
|
|
|
// TODO Make a date/time entry element or textbox overlay.
|
|
EsTextDisplayCreate(table, ES_CELL_H_RIGHT, ES_STYLE_TEXT_LABEL, INTERFACE_STRING(InstallerTime));
|
|
timeTextbox = EsTextboxCreate(table, ES_CELL_H_LEFT, &styleTextboxMedium);
|
|
EsTextboxInsert(timeTextbox, timeBuffer, timeBytes);
|
|
// TODO A date field.
|
|
|
|
EsTextDisplayCreate(table, ES_CELL_H_RIGHT | ES_CELL_V_TOP, ES_STYLE_TEXT_RADIO_GROUP_LABEL, INTERFACE_STRING(InstallerSystemFont));
|
|
EsPanel *fonts = EsPanelCreate(table, ES_CELL_H_LEFT | ES_PANEL_RADIO_GROUP);
|
|
EsButton *button = EsButtonCreate(fonts, ES_BUTTON_RADIOBOX | ES_CELL_H_EXPAND, 0, INTERFACE_STRING(InstallerFontDefault));
|
|
button->userData = (void *) "Inter";
|
|
EsButtonOnCommand(button, ButtonFont);
|
|
EsButtonSetCheck(button, ES_CHECK_CHECKED);
|
|
button = EsButtonCreate(fonts, ES_BUTTON_RADIOBOX | ES_CELL_H_EXPAND, 0, EsLiteral("Atkinson Hyperlegible"));
|
|
button->userData = (void *) "Atkinson Hyperlegible";
|
|
EsButtonOnCommand(button, ButtonFont);
|
|
button = EsButtonCreate(fonts, ES_BUTTON_RADIOBOX | ES_CELL_H_EXPAND, 0, EsLiteral("OpenDyslexic"));
|
|
button->userData = (void *) "OpenDyslexic";
|
|
EsButtonOnCommand(button, ButtonFont);
|
|
|
|
EsTextDisplayCreate(panelCustomizeOptions, ES_CELL_H_FILL, ES_STYLE_TEXT_PARAGRAPH_SECONDARY, INTERFACE_STRING(InstallerCustomizeOptionsHint));
|
|
|
|
EsSpacerCreate(panelCustomizeOptions, ES_CELL_FILL);
|
|
|
|
EsPanel *buttonsRow = EsPanelCreate(panelCustomizeOptions, ES_CELL_H_FILL | ES_PANEL_HORIZONTAL, &styleButtonsRow);
|
|
EsSpacerCreate(buttonsRow, ES_CELL_H_FILL);
|
|
finishButton = EsButtonCreate(buttonsRow, ES_ELEMENT_DISABLED, 0, INTERFACE_STRING(InstallerFinish));
|
|
EsButtonOnCommand(finishButton, ButtonFinish);
|
|
}
|
|
|
|
{
|
|
panelWait = EsPanelCreate(switcher, ES_CELL_FILL, &styleRoot);
|
|
EsTextDisplayCreate(panelWait, ES_CELL_H_FILL, ES_STYLE_TEXT_HEADING0, INTERFACE_STRING(InstallerTitle));
|
|
progressDisplay = EsTextDisplayCreate(panelWait, ES_TEXT_DISPLAY_RICH_TEXT | ES_CELL_H_FILL, ES_STYLE_TEXT_PARAGRAPH);
|
|
}
|
|
|
|
{
|
|
panelComplete = EsPanelCreate(switcher, ES_CELL_FILL, &styleRoot);
|
|
EsTextDisplayCreate(panelComplete, ES_CELL_H_FILL, ES_STYLE_TEXT_HEADING0, INTERFACE_STRING(InstallerTitle));
|
|
|
|
EsVolumeInformation information;
|
|
|
|
if (EsMountPointGetVolumeInformation(EsLiteral("0:/"), &information) && information.driveType == ES_DRIVE_TYPE_USB_MASS_STORAGE) {
|
|
EsTextDisplayCreate(panelComplete, ES_CELL_H_FILL, ES_STYLE_TEXT_PARAGRAPH, INTERFACE_STRING(InstallerCompleteFromUSB));
|
|
} else {
|
|
EsTextDisplayCreate(panelComplete, ES_CELL_H_FILL, ES_STYLE_TEXT_PARAGRAPH, INTERFACE_STRING(InstallerCompleteFromOther));
|
|
}
|
|
|
|
EsSpacerCreate(panelComplete, ES_CELL_FILL);
|
|
EsPanel *buttonsRow = EsPanelCreate(panelComplete, ES_CELL_H_FILL | ES_PANEL_HORIZONTAL, &styleButtonsRow);
|
|
EsSpacerCreate(buttonsRow, ES_CELL_H_FILL);
|
|
EsButtonOnCommand(EsButtonCreate(buttonsRow, ES_FLAGS_DEFAULT, 0, INTERFACE_STRING(DesktopRestartAction)), ButtonRestart);
|
|
}
|
|
|
|
{
|
|
panelError = EsPanelCreate(switcher, ES_CELL_FILL, &styleRoot);
|
|
EsTextDisplayCreate(panelError, ES_CELL_H_FILL, ES_STYLE_TEXT_HEADING0, INTERFACE_STRING(InstallerTitle));
|
|
// Contents is created in Complete().
|
|
}
|
|
|
|
{
|
|
panelNotSupported = EsPanelCreate(switcher, ES_CELL_FILL, &styleRoot);
|
|
EsTextDisplayCreate(panelNotSupported, ES_CELL_H_FILL, ES_STYLE_TEXT_HEADING0, INTERFACE_STRING(InstallerTitle));
|
|
|
|
EsPanel *row = EsPanelCreate(panelNotSupported, ES_CELL_H_FILL | ES_PANEL_HORIZONTAL);
|
|
EsIconDisplayCreate(row, ES_FLAGS_DEFAULT, 0, ES_ICON_DIALOG_ERROR);
|
|
EsSpacerCreate(row, ES_FLAGS_DEFAULT, 0, 15, 0);
|
|
|
|
EsTextDisplayCreate(row, ES_CELL_H_FILL, ES_STYLE_TEXT_PARAGRAPH, INTERFACE_STRING(InstallerNotSupported));
|
|
|
|
EsSpacerCreate(panelNotSupported, ES_CELL_FILL);
|
|
EsPanel *buttonsRow = EsPanelCreate(panelNotSupported, ES_CELL_H_FILL | ES_PANEL_HORIZONTAL, &styleButtonsRow);
|
|
EsSpacerCreate(buttonsRow, ES_CELL_H_FILL);
|
|
EsButtonOnCommand(EsButtonCreate(buttonsRow, ES_FLAGS_DEFAULT, 0, INTERFACE_STRING(DesktopRestartAction)), ButtonRestart);
|
|
}
|
|
|
|
{
|
|
MemoryAvailable available;
|
|
EsSyscall(ES_SYSCALL_MEMORY_GET_AVAILABLE, (uintptr_t) &available, 0, 0, 0);
|
|
|
|
if (available.total < 64 * 1024 * 1024) {
|
|
EsPanelSwitchTo(switcher, panelNotSupported, ES_TRANSITION_NONE);
|
|
} else {
|
|
EsPanelSwitchTo(switcher, panelInstallOptions, ES_TRANSITION_NONE);
|
|
}
|
|
}
|
|
|
|
EsDeviceEnumerate([] (EsMessageDevice device, EsGeneric) {
|
|
ConnectedDriveAdd(device);
|
|
}, 0);
|
|
|
|
while (true) {
|
|
EsMessage *message = EsMessageReceive();
|
|
|
|
if (message->type == ES_MSG_DEVICE_CONNECTED) {
|
|
if (!startedInstallation) {
|
|
ConnectedDriveAdd(message->device);
|
|
}
|
|
} else if (message->type == ES_MSG_DEVICE_DISCONNECTED) {
|
|
if (!startedInstallation) {
|
|
ConnectedDriveRemove(message->device);
|
|
}
|
|
} else if (message->type == ES_MSG_REGISTER_FILE_SYSTEM) {
|
|
EsVolumeInformation information;
|
|
|
|
if (EsMountPointGetVolumeInformation(message->registerFileSystem.mountPoint->prefix, message->registerFileSystem.mountPoint->prefixBytes, &information)) {
|
|
bool isBootable = false;
|
|
|
|
for (uintptr_t i = 0; i < sizeof(EsUniqueIdentifier); i++) {
|
|
if (information.installationIdentifier.d[i]) {
|
|
isBootable = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (isBootable && 0 == EsMemoryCompare(&information.installationIdentifier, &installationIdentifier, sizeof(EsUniqueIdentifier))) {
|
|
newFileSystemMountPoint = *message->registerFileSystem.mountPoint;
|
|
EsEventSet(mountNewFileSystemEvent);
|
|
}
|
|
}
|
|
} else if (message->type == MSG_SET_PROGRESS) {
|
|
if (progress != message->user.context1.u) {
|
|
char buffer[128];
|
|
progress = message->user.context1.u;
|
|
EsAssert(progress <= 100);
|
|
size_t bytes = EsStringFormat(buffer, sizeof(buffer), "%z%d%z",
|
|
interfaceString_InstallerProgressMessage, progress, interfaceString_CommonUnitPercent);
|
|
EsTextDisplaySetContents(progressDisplay, buffer, bytes);
|
|
|
|
if (onWaitScreen && progress == 100) {
|
|
onWaitScreen = false;
|
|
Complete();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|