use std::sync::atomic::Ordering;
use crate::windows::constants::*;
use crate::emu::Emu;
use crate::windows::structures::ProcessBasicInformation64;
fn is_current_process_handle(h: u64) -> bool {
h == !0 || h == 0xffff_ffff_ffff_fffe
}
pub fn nt_access_check(emu: &mut Emu) {
let _security_descriptor = emu.regs().rcx;
let _client_token = emu.regs().rdx;
let desired_access = emu.regs().r8 as u32;
let _generic_mapping = emu.regs().r9;
let rsp = emu.regs().rsp;
let privilege_set = emu.maps.read_qword(rsp + 0x28).unwrap_or(0);
let privilege_set_length_ptr = emu.maps.read_qword(rsp + 0x30).unwrap_or(0);
let granted_access_ptr = emu.maps.read_qword(rsp + 0x38).unwrap_or(0);
let access_status_ptr = emu.maps.read_qword(rsp + 0x40).unwrap_or(0);
log_orange!(
emu,
"syscall 0x{:x}: NtAccessCheck desired_access: 0x{:x}, granted_out: 0x{:x}, status_out: 0x{:x}",
WIN64_NTACCESSCHECK,
desired_access,
granted_access_ptr,
access_status_ptr
);
if granted_access_ptr == 0 || access_status_ptr == 0 {
emu.regs_mut().rax = STATUS_INVALID_PARAMETER;
return;
}
if !emu.maps.is_mapped(granted_access_ptr) || !emu.maps.is_mapped(access_status_ptr) {
emu.regs_mut().rax = STATUS_ACCESS_VIOLATION;
return;
}
let _ = emu.maps.write_dword(granted_access_ptr, desired_access);
let _ = emu.maps.write_dword(access_status_ptr, STATUS_SUCCESS as u32);
if privilege_set_length_ptr != 0 && emu.maps.is_mapped(privilege_set_length_ptr) {
let len = emu.maps.read_dword(privilege_set_length_ptr).unwrap_or(0) as usize;
if len > 0 && privilege_set != 0 && emu.maps.is_mapped(privilege_set) {
let cap = len.min(0x1000);
emu.maps.memset(privilege_set, 0, cap);
}
let _ = emu.maps.write_dword(privilege_set_length_ptr, 0);
}
emu.regs_mut().rax = STATUS_SUCCESS;
}
pub fn nt_query_information_process(emu: &mut Emu) {
let process_handle = emu.regs().rcx;
let process_information_class = emu.regs().rdx;
let process_information = emu.regs().r8;
let process_information_length = emu.regs().r9;
let rsp = emu.regs().rsp;
let return_length_ptr = emu.maps.read_qword(rsp + 0x28).unwrap_or(0);
log_orange!(emu, "syscall 0x{:x}: NtQueryInformationProcess process_handle: 0x{:x}, process_information_class: 0x{:x}, process_information: 0x{:x}, process_information_length: 0x{:x}, return_length_ptr: 0x{:x}", WIN64_NTQUERYINFORMATIONPROCESS, process_handle, process_information_class, process_information, process_information_length, return_length_ptr);
if process_information_class == PROCESS_INFORMATION_CLASS_PROCESS_BASIC_INFORMATION {
if process_information == 0 {
emu.regs_mut().rax = STATUS_INVALID_PARAMETER;
return;
}
if !emu.maps.is_mapped(process_information) {
emu.regs_mut().rax = STATUS_INVALID_PARAMETER;
return;
}
if process_information_length < ProcessBasicInformation64::size() {
emu.regs_mut().rax = STATUS_INVALID_PARAMETER;
return;
}
let peb_base = emu.maps.get_mem("peb").get_base();
let process_info = ProcessBasicInformation64 {
Reserved1: 0,
PebBaseAddress: peb_base,
Reserved2: [0, 0],
UniqueProcessId: 4,
Reserved3: 0,
};
process_info.save(process_information, &mut emu.maps);
if return_length_ptr != 0 {
if !emu
.maps
.write_qword(return_length_ptr, ProcessBasicInformation64::size())
{
emu.regs_mut().rax = STATUS_INVALID_PARAMETER;
return;
}
}
emu.regs_mut().rax = STATUS_SUCCESS;
return;
}
if process_information_class == PROCESS_INFORMATION_CLASS_PROCESS_COOKIE {
if process_information == 0 {
emu.regs_mut().rax = STATUS_INVALID_PARAMETER;
return;
}
if process_information_length < 4 {
emu.regs_mut().rax = STATUS_INVALID_PARAMETER;
return;
}
if !emu.maps.is_mapped(process_information) {
emu.regs_mut().rax = STATUS_INVALID_PARAMETER;
return;
}
if !emu.maps.write_dword(process_information, 0x01234567) {
emu.regs_mut().rax = STATUS_INVALID_PARAMETER;
return;
}
if return_length_ptr != 0 {
if !emu.maps.write_dword(return_length_ptr, 4) {
emu.regs_mut().rax = STATUS_INVALID_PARAMETER;
return;
}
}
emu.regs_mut().rax = STATUS_SUCCESS;
return;
}
if process_information_class == PROCESS_INFORMATION_CLASS_PROCESS_DEBUG_PORT {
if process_information_length < 8 {
emu.regs_mut().rax = STATUS_INVALID_PARAMETER;
return;
}
if !emu.maps.is_mapped(process_information) {
emu.regs_mut().rax = STATUS_INVALID_PARAMETER;
return;
}
if !emu.maps.write_qword(process_information, 0) {
emu.regs_mut().rax = STATUS_INVALID_PARAMETER;
return;
}
if return_length_ptr != 0 {
if !emu.maps.write_qword(return_length_ptr, 8) {
emu.regs_mut().rax = STATUS_INVALID_PARAMETER;
return;
}
}
emu.regs_mut().rax = STATUS_SUCCESS;
return;
}
if process_information_class == 0x25 {
if process_information_length < 8 || process_information == 0 || !emu.maps.is_mapped(process_information) {
emu.regs_mut().rax = STATUS_INVALID_PARAMETER;
return;
}
let _ = emu.maps.write_qword(process_information, 0);
if return_length_ptr != 0 {
let _ = emu.maps.write_dword(return_length_ptr, 8);
}
emu.regs_mut().rax = STATUS_SUCCESS;
return;
}
if process_information_class == 0x1b {
const HDR: u32 = 16; if process_information_length < HDR as u64 {
if return_length_ptr != 0 && emu.maps.is_mapped(return_length_ptr) {
let _ = emu.maps.write_dword(return_length_ptr, HDR);
}
emu.regs_mut().rax = STATUS_INFO_LENGTH_MISMATCH;
return;
}
if process_information == 0 || !emu.maps.is_mapped(process_information) {
emu.regs_mut().rax = STATUS_INVALID_PARAMETER;
return;
}
let _ = emu.maps.write_word(process_information, 0);
let _ = emu.maps.write_word(process_information + 2, 0);
let _ = emu.maps.write_qword(process_information + 8, 0);
if return_length_ptr != 0 && emu.maps.is_mapped(return_length_ptr) {
let _ = emu.maps.write_dword(return_length_ptr, HDR);
}
emu.regs_mut().rax = STATUS_SUCCESS;
return;
}
emu.regs_mut().rax = STATUS_INVALID_PARAMETER;
}
pub fn nt_query_performance_counter(emu: &mut Emu) {
let counter_ptr = emu.regs().rcx;
let freq_ptr = emu.regs().rdx;
log_orange!(emu, "syscall 0x{:x}: NtQueryPerformanceCounter counter: 0x{:x} freq: 0x{:x}", WIN64_NTQUERYPERFORMANCECOUNTER, counter_ptr, freq_ptr);
if counter_ptr == 0 {
emu.regs_mut().rax = STATUS_INVALID_PARAMETER;
return;
}
if !emu.maps.is_mapped(counter_ptr) {
emu.regs_mut().rax = STATUS_ACCESS_VIOLATION;
return;
}
let counter_value = (emu.tick as u64).saturating_mul(1000);
if !emu.maps.write_qword(counter_ptr, counter_value) {
emu.regs_mut().rax = STATUS_ACCESS_VIOLATION;
return;
}
if freq_ptr != 0 {
if !emu.maps.is_mapped(freq_ptr) {
emu.regs_mut().rax = STATUS_ACCESS_VIOLATION;
return;
}
if !emu.maps.write_qword(freq_ptr, 10_000_000) {
emu.regs_mut().rax = STATUS_ACCESS_VIOLATION;
return;
}
}
emu.regs_mut().rax = STATUS_SUCCESS;
}
pub fn nt_query_information_thread(emu: &mut Emu) {
let _thread_handle = emu.regs().rcx;
let thread_class = emu.regs().rdx;
let thread_info = emu.regs().r8;
let thread_info_len = emu.regs().r9;
let rsp = emu.regs().rsp;
let return_length_ptr = emu.maps.read_qword(rsp + 0x28).unwrap_or(0);
log_orange!(
emu,
"syscall 0x{:x}: NtQueryInformationThread class: 0x{:x}, out: 0x{:x}, len: 0x{:x}",
WIN64_NTQUERYINFORMATIONTHREAD,
thread_class,
thread_info,
thread_info_len
);
if thread_info == 0 && thread_info_len > 0 {
emu.regs_mut().rax = STATUS_INVALID_PARAMETER;
return;
}
if thread_class == THREAD_INFORMATION_CLASS_THREAD_BASIC_INFORMATION {
const TBI_SIZE: u64 = 48;
if thread_info_len < TBI_SIZE {
emu.regs_mut().rax = STATUS_INVALID_PARAMETER;
return;
}
if !emu.maps.is_mapped(thread_info) {
emu.regs_mut().rax = STATUS_ACCESS_VIOLATION;
return;
}
for i in 0..TBI_SIZE {
let _ = emu.maps.write_byte(thread_info + i, 0);
}
let teb_base = emu.maps.get_mem("teb").get_base();
let _ = emu.maps.write_qword(thread_info + 8, teb_base);
let _ = emu.maps.write_qword(thread_info + 0x10, 0x1000); let _ = emu.maps.write_qword(thread_info + 0x18, 0x1004); if return_length_ptr != 0 {
let _ = emu.maps.write_qword(return_length_ptr, TBI_SIZE);
}
emu.regs_mut().rax = STATUS_SUCCESS;
return;
}
if thread_class == THREAD_INFORMATION_CLASS_THREAD_QUERY_SET_WIN32_START_ADDRESS {
if thread_info_len < 8 {
emu.regs_mut().rax = STATUS_INVALID_PARAMETER;
return;
}
if !emu.maps.is_mapped(thread_info) {
emu.regs_mut().rax = STATUS_ACCESS_VIOLATION;
return;
}
let start = emu.regs().rip;
let _ = emu.maps.write_qword(thread_info, start);
if return_length_ptr != 0 {
let _ = emu.maps.write_qword(return_length_ptr, 8);
}
emu.regs_mut().rax = STATUS_SUCCESS;
return;
}
emu.regs_mut().rax = STATUS_INVALID_PARAMETER;
}
pub fn nt_set_information_process(emu: &mut Emu) {
let process_handle = emu.regs().rcx;
let class = emu.regs().rdx;
log_orange!(
emu,
"syscall 0x{:x}: NtSetInformationProcess h: 0x{:x} class: 0x{:x}",
WIN64_NTSETINFORMATIONPROCESS,
process_handle,
class
);
if !is_current_process_handle(process_handle) {
emu.regs_mut().rax = STATUS_ACCESS_DENIED;
return;
}
emu.regs_mut().rax = STATUS_SUCCESS;
}
pub fn nt_set_information_thread(emu: &mut Emu) {
let thread_handle = emu.regs().rcx;
let class = emu.regs().rdx;
log_orange!(
emu,
"syscall 0x{:x}: NtSetInformationThread h: 0x{:x} class: 0x{:x}",
WIN64_NTSETINFORMATIONTHREAD,
thread_handle,
class
);
emu.regs_mut().rax = STATUS_SUCCESS;
}
pub fn nt_open_process(emu: &mut Emu) {
let handle_out = emu.regs().rcx;
let _desired_access = emu.regs().rdx;
let _obj_attr = emu.regs().r8;
let _client_id = emu.regs().r9;
log_orange!(emu, "syscall 0x{:x}: NtOpenProcess out: 0x{:x}", WIN64_NTOPENPROCESS, handle_out);
if handle_out == 0 {
emu.regs_mut().rax = STATUS_INVALID_PARAMETER;
return;
}
let fake = 0x4u64;
if emu.maps.write_qword(handle_out, fake) {
emu.regs_mut().rax = STATUS_SUCCESS;
} else {
emu.regs_mut().rax = STATUS_ACCESS_VIOLATION;
}
}
pub fn nt_raise_hard_error(emu: &mut Emu) {
let error_status = emu.regs().rcx as u32;
let num_params = emu.regs().rdx as u32;
let unicode_mask = emu.regs().r8 as u32;
let parameters = emu.regs().r9;
let rsp = emu.regs().rsp;
let valid_options = emu.maps.read_dword(rsp + 0x28).unwrap_or(0);
let response_ptr = emu.maps.read_qword(rsp + 0x30).unwrap_or(0);
let status_tag = if error_status == STATUS_APP_INIT_FAILURE as u32 {
" STATUS_APP_INIT_FAILURE"
} else {
""
};
log_orange!(
emu,
"syscall 0x{:x}: NtRaiseHardError status: 0x{:x}{}, nparams: {}, umask: 0x{:x}, params: 0x{:x}, options: 0x{:x}, response: 0x{:x} (stub OK)",
WIN64_NTRAISEHARDERROR,
error_status,
status_tag,
num_params,
unicode_mask,
parameters,
valid_options,
response_ptr
);
const HARDERROR_RESPONSE_OK: u32 = 6;
if response_ptr != 0 && emu.maps.is_mapped(response_ptr) {
let _ = emu.maps.write_dword(response_ptr, HARDERROR_RESPONSE_OK);
}
emu.regs_mut().rax = STATUS_SUCCESS;
}
pub fn nt_terminate_process(emu: &mut Emu) {
let process_handle = emu.regs().rcx;
let exit_status = emu.regs().rdx;
log_orange!(
emu,
"syscall 0x{:x}: NtTerminateProcess h: 0x{:x} status: 0x{:x}",
WIN64_NTTERMINATEPROCESS,
process_handle,
exit_status
);
if process_handle != !0 && process_handle != 0 {
emu.regs_mut().rax = STATUS_ACCESS_DENIED;
return;
}
if emu.call_depth == 0 {
emu.process_terminated = true;
}
emu.is_running.store(0, Ordering::Relaxed);
emu.regs_mut().rax = STATUS_SUCCESS;
}
pub fn nt_raise_exception(emu: &mut Emu) {
let exception_record = emu.regs().r10; let context_ptr = emu.regs().rdx;
let first_chance = emu.regs().r8;
let exception_code = emu
.maps
.read_dword(exception_record)
.unwrap_or(0);
log_orange!(
emu,
"syscall 0x{:x}: NtRaiseException record: 0x{:x}, ctx: 0x{:x}, first: {}, code: 0x{:x}",
WIN64_NTRAISEEXCEPTION,
exception_record,
context_ptr,
first_chance,
exception_code
);
if context_ptr != 0 && emu.maps.is_mapped(context_ptr + 0xF8) {
let ctx_rip = emu.maps.read_qword(context_ptr + 0xF8).unwrap_or(0);
let ctx_rsp = emu.maps.read_qword(context_ptr + 0x98).unwrap_or(0);
let ctx_rbp = emu.maps.read_qword(context_ptr + 0xA0).unwrap_or(0);
let ctx_rax = emu.maps.read_qword(context_ptr + 0x78).unwrap_or(0);
let ctx_rbx = emu.maps.read_qword(context_ptr + 0x90).unwrap_or(0);
let ctx_rcx = emu.maps.read_qword(context_ptr + 0x80).unwrap_or(0);
let ctx_rdx = emu.maps.read_qword(context_ptr + 0x88).unwrap_or(0);
let ctx_rsi = emu.maps.read_qword(context_ptr + 0xA8).unwrap_or(0);
let ctx_rdi = emu.maps.read_qword(context_ptr + 0xB0).unwrap_or(0);
let ctx_r8 = emu.maps.read_qword(context_ptr + 0xB8).unwrap_or(0);
let ctx_r9 = emu.maps.read_qword(context_ptr + 0xC0).unwrap_or(0);
let ctx_r10 = emu.maps.read_qword(context_ptr + 0xC8).unwrap_or(0);
let ctx_r11 = emu.maps.read_qword(context_ptr + 0xD0).unwrap_or(0);
let ctx_r12 = emu.maps.read_qword(context_ptr + 0xD8).unwrap_or(0);
let ctx_r13 = emu.maps.read_qword(context_ptr + 0xE0).unwrap_or(0);
let ctx_r14 = emu.maps.read_qword(context_ptr + 0xE8).unwrap_or(0);
let ctx_r15 = emu.maps.read_qword(context_ptr + 0xF0).unwrap_or(0);
log::trace!(
"NtRaiseException: restoring context, RIP=0x{:x}, RSP=0x{:x}",
ctx_rip, ctx_rsp
);
let r = emu.regs_mut();
r.rip = ctx_rip;
r.rsp = ctx_rsp;
r.rbp = ctx_rbp;
r.rax = ctx_rax;
r.rbx = ctx_rbx;
r.rcx = ctx_rcx;
r.rdx = ctx_rdx;
r.rsi = ctx_rsi;
r.rdi = ctx_rdi;
r.r8 = ctx_r8;
r.r9 = ctx_r9;
r.r10 = ctx_r10;
r.r11 = ctx_r11;
r.r12 = ctx_r12;
r.r13 = ctx_r13;
r.r14 = ctx_r14;
r.r15 = ctx_r15;
emu.force_reload = true;
} else {
log::trace!("NtRaiseException: invalid context pointer, returning STATUS_ACCESS_VIOLATION");
emu.regs_mut().rax = STATUS_ACCESS_VIOLATION;
}
}
pub fn nt_query_security_attributes_token(emu: &mut Emu) {
let token_handle = emu.regs().rcx;
let _attributes = emu.regs().rdx;
let num_attrs = emu.regs().r8;
let buffer = emu.regs().r9;
let rsp = emu.regs().rsp;
let length = emu.maps.read_dword(rsp + 0x28).unwrap_or(0) as u64;
let return_length_ptr = emu.maps.read_qword(rsp + 0x30).unwrap_or(0);
log_orange!(
emu,
"syscall 0x{:x}: NtQuerySecurityAttributesToken h: 0x{:x} num_attrs: {} buf: 0x{:x} len: {}",
WIN64_NTQUERYSECURITYATTRIBUTESTOKEN,
token_handle,
num_attrs,
buffer,
length
);
const STRUCT_SIZE: u64 = 16;
if return_length_ptr != 0 && emu.maps.is_mapped(return_length_ptr) {
let _ = emu.maps.write_dword(return_length_ptr, STRUCT_SIZE as u32);
}
if buffer == 0 || length < STRUCT_SIZE {
emu.regs_mut().rax = STATUS_BUFFER_TOO_SMALL;
return;
}
if !emu.maps.is_mapped(buffer) {
emu.regs_mut().rax = STATUS_ACCESS_VIOLATION;
return;
}
let _ = emu.maps.write_word(buffer, 1); let _ = emu.maps.write_word(buffer + 2, 0); let _ = emu.maps.write_dword(buffer + 4, 0); let _ = emu.maps.write_qword(buffer + 8, 0);
emu.regs_mut().rax = STATUS_SUCCESS;
}
pub fn nt_create_thread_ex(emu: &mut Emu) {
let thread_handle_ptr = emu.regs().rcx;
let _desired_access = emu.regs().rdx;
let _process_handle = emu.regs().r9;
log_orange!(
emu,
"syscall 0x{:x}: NtCreateThreadEx handle_out: 0x{:x} process: 0x{:x}",
WIN64_NTCREATETHREADEX,
thread_handle_ptr,
_process_handle,
);
if thread_handle_ptr == 0 || !emu.maps.is_mapped(thread_handle_ptr) {
emu.regs_mut().rax = STATUS_INVALID_PARAMETER;
return;
}
let h = crate::syscall::windows::syscall64::sync::next_handle();
let _ = emu.maps.write_qword(thread_handle_ptr, h);
emu.regs_mut().rax = STATUS_SUCCESS;
}
pub fn nt_continue(emu: &mut Emu) {
let context_ptr = emu.regs().rcx;
let _test_alert = emu.regs().rdx;
log_orange!(
emu,
"syscall 0x{:x}: NtContinue context_ptr=0x{:x}",
WIN64_NTCONTINUE,
context_ptr
);
if context_ptr == 0 || !emu.maps.is_mapped(context_ptr) {
emu.regs_mut().rax = STATUS_INVALID_PARAMETER;
return;
}
let ctx_rip = emu.maps.read_qword(context_ptr + 0xF8).unwrap_or(0);
let ctx_rsp = emu.maps.read_qword(context_ptr + 0x98).unwrap_or(0);
let ctx_rax = emu.maps.read_qword(context_ptr + 0x78).unwrap_or(0);
let ctx_rcx = emu.maps.read_qword(context_ptr + 0x80).unwrap_or(0);
let ctx_rdx = emu.maps.read_qword(context_ptr + 0x88).unwrap_or(0);
let ctx_rbx = emu.maps.read_qword(context_ptr + 0x90).unwrap_or(0);
let ctx_rbp = emu.maps.read_qword(context_ptr + 0xA0).unwrap_or(0);
let ctx_rsi = emu.maps.read_qword(context_ptr + 0xA8).unwrap_or(0);
let ctx_rdi = emu.maps.read_qword(context_ptr + 0xB0).unwrap_or(0);
let ctx_r8 = emu.maps.read_qword(context_ptr + 0xB8).unwrap_or(0);
let ctx_r9 = emu.maps.read_qword(context_ptr + 0xC0).unwrap_or(0);
let ctx_r10 = emu.maps.read_qword(context_ptr + 0xC8).unwrap_or(0);
let ctx_r11 = emu.maps.read_qword(context_ptr + 0xD0).unwrap_or(0);
let ctx_r12 = emu.maps.read_qword(context_ptr + 0xD8).unwrap_or(0);
let ctx_r13 = emu.maps.read_qword(context_ptr + 0xE0).unwrap_or(0);
let ctx_r14 = emu.maps.read_qword(context_ptr + 0xE8).unwrap_or(0);
let ctx_r15 = emu.maps.read_qword(context_ptr + 0xF0).unwrap_or(0);
emu.regs_mut().rax = ctx_rax;
emu.regs_mut().rcx = ctx_rcx;
emu.regs_mut().rdx = ctx_rdx;
emu.regs_mut().rbx = ctx_rbx;
if ctx_rsp != 0 {
emu.regs_mut().rsp = ctx_rsp;
}
emu.regs_mut().rbp = ctx_rbp;
emu.regs_mut().rsi = ctx_rsi;
emu.regs_mut().rdi = ctx_rdi;
emu.regs_mut().r8 = ctx_r8;
emu.regs_mut().r9 = ctx_r9;
emu.regs_mut().r10 = ctx_r10;
emu.regs_mut().r11 = ctx_r11;
emu.regs_mut().r12 = ctx_r12;
emu.regs_mut().r13 = ctx_r13;
emu.regs_mut().r14 = ctx_r14;
emu.regs_mut().r15 = ctx_r15;
if ctx_rip != 0 {
emu.regs_mut().rip = ctx_rip;
emu.force_reload = true;
}
emu.regs_mut().rax = STATUS_SUCCESS;
}
pub fn nt_duplicate_object(emu: &mut Emu) {
let source_process = emu.regs().rcx;
let source_handle = emu.regs().rdx;
let _target_process = emu.regs().r8;
let target_handle_out = emu.regs().r9;
let rsp = emu.regs().rsp;
let desired_access = emu.maps.read_qword(rsp + 0x28).unwrap_or(0);
let options = emu.maps.read_qword(rsp + 0x38).unwrap_or(0);
log_orange!(
emu,
"syscall 0x{:x}: NtDuplicateObject src_proc: 0x{:x}, src_handle: 0x{:x}, target_out: 0x{:x}, access: 0x{:x}, opts: 0x{:x}",
WIN64_NTDUPLICATEOBJECT,
source_process,
source_handle,
target_handle_out,
desired_access,
options,
);
if target_handle_out != 0 && emu.maps.is_mapped(target_handle_out) {
let h = super::sync::next_handle();
let _ = emu.maps.write_qword(target_handle_out, h);
}
emu.regs_mut().rax = STATUS_SUCCESS;
}
pub fn nt_create_profile_ex(emu: &mut Emu) {
let profile_handle = emu.regs().rcx;
log_orange!(
emu,
"syscall 0x{:x}: NtCreateProfileEx out: 0x{:x}",
WIN64_NTCREATEPROFILEEX,
profile_handle,
);
emu.regs_mut().rax = STATUS_NOT_SUPPORTED;
}