New project, some CPU operations emulated
This commit is contained in:
commit
d606446c12
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
||||
/target
|
8
.idea/.gitignore
generated
vendored
Normal file
8
.idea/.gitignore
generated
vendored
Normal file
@ -0,0 +1,8 @@
|
||||
# Default ignored files
|
||||
/shelf/
|
||||
/workspace.xml
|
||||
# Editor-based HTTP Client requests
|
||||
/httpRequests/
|
||||
# Datasource local storage ignored files
|
||||
/dataSources/
|
||||
/dataSources.local.xml
|
8
.idea/modules.xml
generated
Normal file
8
.idea/modules.xml
generated
Normal file
@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="ProjectModuleManager">
|
||||
<modules>
|
||||
<module fileurl="file://$PROJECT_DIR$/.idea/nesrust.iml" filepath="$PROJECT_DIR$/.idea/nesrust.iml" />
|
||||
</modules>
|
||||
</component>
|
||||
</project>
|
15
.idea/nesrust.iml
generated
Normal file
15
.idea/nesrust.iml
generated
Normal file
@ -0,0 +1,15 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<module type="EMPTY_MODULE" version="4">
|
||||
<component name="NewModuleRootManager">
|
||||
<content url="file://$MODULE_DIR$">
|
||||
<sourceFolder url="file://$MODULE_DIR$/core/core/src" isTestSource="false" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/core/src" isTestSource="false" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/core/core/target" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/core/target" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/target" />
|
||||
</content>
|
||||
<orderEntry type="inheritedJdk" />
|
||||
<orderEntry type="sourceFolder" forTests="false" />
|
||||
</component>
|
||||
</module>
|
6
.idea/vcs.xml
generated
Normal file
6
.idea/vcs.xml
generated
Normal file
@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="VcsDirectoryMappings">
|
||||
<mapping directory="" vcs="Git" />
|
||||
</component>
|
||||
</project>
|
16
Cargo.lock
generated
Normal file
16
Cargo.lock
generated
Normal file
@ -0,0 +1,16 @@
|
||||
# This file is automatically @generated by Cargo.
|
||||
# It is not intended for manual editing.
|
||||
version = 3
|
||||
|
||||
[[package]]
|
||||
name = "bitflags"
|
||||
version = "2.7.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1be3f42a67d6d345ecd59f675f3f012d6974981560836e938c22b424b85ce1be"
|
||||
|
||||
[[package]]
|
||||
name = "core"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
]
|
9
Cargo.toml
Normal file
9
Cargo.toml
Normal file
@ -0,0 +1,9 @@
|
||||
#[package]
|
||||
#name = "nesrust"
|
||||
#version = "0.1.0"
|
||||
#edition = "2021"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[workspace]
|
||||
members = ["core"]
|
7
core/Cargo.lock
generated
Normal file
7
core/Cargo.lock
generated
Normal file
@ -0,0 +1,7 @@
|
||||
# This file is automatically @generated by Cargo.
|
||||
# It is not intended for manual editing.
|
||||
version = 3
|
||||
|
||||
[[package]]
|
||||
name = "core"
|
||||
version = "0.1.0"
|
9
core/Cargo.toml
Normal file
9
core/Cargo.toml
Normal file
@ -0,0 +1,9 @@
|
||||
[package]
|
||||
name = "core"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
bitflags = "2.7.0"
|
211
core/src/cpu/mod.rs
Normal file
211
core/src/cpu/mod.rs
Normal file
@ -0,0 +1,211 @@
|
||||
mod op;
|
||||
mod operations;
|
||||
|
||||
use crate::memory::MemoryBus;
|
||||
use bitflags::bitflags;
|
||||
|
||||
const STACK_ADDR: u16 = 0x0100;
|
||||
|
||||
/// Represents a 6502 CPU
|
||||
struct Cpu<'a> {
|
||||
/// The registers of the CPU
|
||||
registers: CpuRegisters,
|
||||
|
||||
/// The memory bus accessible by the CPU
|
||||
memory_bus: &'a MemoryBus,
|
||||
|
||||
/// The amount of cycles the CPU will be busy for (won't execute any instruction)
|
||||
busy_cycle_count: u16,
|
||||
|
||||
/// Whether an OAM DMA was triggered (data transfer to the PPU OAM memory)
|
||||
oam_dma_triggered: bool,
|
||||
|
||||
/// Whether an interrupt was requested
|
||||
nmi_requested: bool,
|
||||
}
|
||||
|
||||
/// Represents the registers of the 6502 CPU
|
||||
struct CpuRegisters {
|
||||
/// The program counter
|
||||
pc: u16,
|
||||
|
||||
/// The stack pointer
|
||||
sp: u8,
|
||||
|
||||
/// The accumulator
|
||||
a: u8,
|
||||
|
||||
/// The X general purpose register
|
||||
x: u8,
|
||||
|
||||
/// The Y general purpose register
|
||||
y: u8,
|
||||
|
||||
/// The status flags
|
||||
status: u8,
|
||||
}
|
||||
|
||||
bitflags! {
|
||||
pub struct CpuStatus: u8 {
|
||||
const Carry = 0b00000001;
|
||||
const Zero = 0b00000010;
|
||||
const InterruptDisable = 0b00000100;
|
||||
const Decimal = 0b00001000;
|
||||
const B = 0b00010000;
|
||||
const Overflow = 0b01000000;
|
||||
const Negative = 0b10000000;
|
||||
}
|
||||
}
|
||||
|
||||
pub trait CpuInternals {
|
||||
/// Gets a status flag
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `flag` - The status flag to get
|
||||
fn get_status_flag(&self, flag: CpuStatus) -> bool;
|
||||
|
||||
/// Gets a status flag as a byte
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `flag` - The status flag to get
|
||||
///
|
||||
/// # Returns
|
||||
/// `1` if the flag is `true`, `0` if `false`
|
||||
fn get_status_flag_u8(&self, flag: CpuStatus) -> u8;
|
||||
|
||||
/// Gets the next byte in the program
|
||||
fn program_get_next_byte(&mut self) -> u8;
|
||||
|
||||
/// Gets the next word in the program
|
||||
fn program_get_next_word(&mut self) -> u16;
|
||||
|
||||
/// Sets a status flag
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `flag` - The status flag to set
|
||||
/// * `value` - Whether the flag is set
|
||||
fn set_status_flag(&mut self, flag: CpuStatus, set: bool);
|
||||
|
||||
/// Sets a status flag from a byte.
|
||||
/// The value `0` will become `false`, and any other value will be `true`.
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `flag` - The status flag to set
|
||||
/// * `value` - The value of the flag
|
||||
fn set_status_flag_u8(&mut self, flag: CpuStatus, value: u8);
|
||||
|
||||
/// Pushes a value to the top of the stack
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `value` - The value to push
|
||||
fn stack_push(&mut self, value: u8);
|
||||
|
||||
/// Pops the value on the top of the stack
|
||||
///
|
||||
/// # Returns
|
||||
/// The byte on the top of the stack
|
||||
fn stack_pop(&mut self) -> u8;
|
||||
|
||||
/// Pushes the context to the top of the stack
|
||||
/// The context consists of the program counter and the status register.
|
||||
fn stack_push_context(&mut self);
|
||||
|
||||
/// Pops the context from the top of the stack
|
||||
fn stack_pop_context(&mut self);
|
||||
}
|
||||
|
||||
impl Cpu {
|
||||
pub fn new<'a>(memory_bus: &mut MemoryBus) -> Self {
|
||||
Cpu {
|
||||
registers: CpuRegisters {
|
||||
pc: 0x8000,
|
||||
sp: 0xFD,
|
||||
a: 0,
|
||||
x: 0,
|
||||
y: 0,
|
||||
status: 0x04,
|
||||
},
|
||||
memory_bus,
|
||||
busy_cycle_count: 0,
|
||||
oam_dma_triggered: false,
|
||||
nmi_requested: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl CpuInternals for Cpu {
|
||||
fn get_status_flag(&self, flag: CpuStatus) -> bool {
|
||||
*self.registers.status & flag
|
||||
}
|
||||
|
||||
fn get_status_flag_u8(&self, flag: CpuStatus) -> u8 {
|
||||
let status = self.get_status_flag(flag);
|
||||
if status {
|
||||
1
|
||||
} else {
|
||||
0
|
||||
}
|
||||
}
|
||||
|
||||
fn program_get_next_byte(&mut self) -> u8 {
|
||||
let byte = self.memory_bus.get_byte(self.registers.pc);
|
||||
self.registers.pc += 1;
|
||||
|
||||
byte
|
||||
}
|
||||
|
||||
fn program_get_next_word(&mut self) -> u16 {
|
||||
let word = self.memory_bus.get_word(self.registers.pc);
|
||||
self.registers.pc += 2;
|
||||
|
||||
word
|
||||
}
|
||||
|
||||
fn set_status_flag(&mut self, flag: CpuStatus, set: bool) {
|
||||
if set {
|
||||
*self.registers.status |= flag;
|
||||
} else {
|
||||
*self.registers.status &= !flag;
|
||||
}
|
||||
}
|
||||
|
||||
fn set_status_flag_u8(&mut self, flag: CpuStatus, value: u8) {
|
||||
let set = if value == 0 { false } else { true };
|
||||
self.set_status_flag(flag, set);
|
||||
}
|
||||
|
||||
fn stack_push(&mut self, value: u8) {
|
||||
assert!(self.registers.sp > 0);
|
||||
|
||||
let addr = STACK_ADDR | self.registers.sp as u16;
|
||||
self.memory_bus.set_byte(addr, value);
|
||||
self.registers.sp -= 1;
|
||||
}
|
||||
|
||||
fn stack_pop(&mut self) -> u8 {
|
||||
assert!(self.registers.sp < 0xff);
|
||||
|
||||
self.registers.sp += 1;
|
||||
let addr = STACK_ADDR | self.registers.sp as u16;
|
||||
|
||||
self.memory_bus.get_byte(addr)
|
||||
}
|
||||
|
||||
fn stack_push_context(&mut self) {
|
||||
self.stack_push((self.registers.pc >> 8) as u8);
|
||||
self.stack_push((self.registers.pc & 0xff) as u8);
|
||||
self.stack_push(self.registers.status);
|
||||
}
|
||||
|
||||
fn stack_pop_context(&mut self) {
|
||||
let mut status = self.stack_pop();
|
||||
status &= 0xEF;
|
||||
status |= 0x20;
|
||||
|
||||
let mut pc = self.stack_pop() as u16;
|
||||
pc += (self.stack_pop() as u16) << 8;
|
||||
|
||||
self.registers.status = status;
|
||||
self.registers.pc = pc;
|
||||
}
|
||||
}
|
273
core/src/cpu/op.rs
Normal file
273
core/src/cpu/op.rs
Normal file
@ -0,0 +1,273 @@
|
||||
// From https://github.com/lukexor/tetanes/blob/main/tetanes-core/src/cpu/instr.rs
|
||||
#[derive(Default, Copy, Clone, Debug, PartialEq, Eq)]
|
||||
#[rustfmt::skip]
|
||||
pub enum OperationType {
|
||||
ADC, AND, ASL, BCC, BCS, BEQ, BIT, BMI, BNE, BPL, BRK, BVC, BVS, CLC, CLD, CLI, CLV, CMP, CPX,
|
||||
CPY, DEC, DEX, DEY, EOR, INC, INX, INY, JMP, JSR, LDA, LDX, LDY, LSR, NOP, ORA, PHA, PHP, PLA,
|
||||
PLP, ROL, ROR, RTI, RTS, SBC, SEC, SED, SEI, STA, STX, STY, TAX, TAY, TSX, TXA, TXS, TYA,
|
||||
// "Unofficial" opcodes
|
||||
SKB, IGN, ISB, DCP, AXS, LAS, LAX, AHX, SAX, XAA, SXA, RRA, TAS, SYA, ARR, SRE, ALR, RLA, ANC,
|
||||
SLO, #[default] XXX
|
||||
}
|
||||
|
||||
#[derive(Default, Copy, Clone, Debug, PartialEq, Eq)]
|
||||
#[rustfmt::skip]
|
||||
pub enum AddressingMode {
|
||||
#[default]
|
||||
IMM,
|
||||
ZP0, ZPX, ZPY,
|
||||
ABS, ABX, ABY,
|
||||
IND, IDX, IDY,
|
||||
REL, ACC, IMP,
|
||||
}
|
||||
|
||||
use crate::cpu::{Cpu, CpuInternals};
|
||||
use AddressingMode::{ABS, ABX, ABY, ACC, IDX, IDY, IMM, IMP, IND, REL, ZP0, ZPX, ZPY};
|
||||
use OperationType::{
|
||||
ADC, AHX, ALR, ANC, AND, ARR, ASL, AXS, BCC, BCS, BEQ, BIT, BMI, BNE, BPL, BRK, BVC, BVS, CLC,
|
||||
CLD, CLI, CLV, CMP, CPX, CPY, DCP, DEC, DEX, DEY, EOR, IGN, INC, INX, INY, ISB, JMP, JSR, LAS,
|
||||
LAX, LDA, LDX, LDY, LSR, NOP, ORA, PHA, PHP, PLA, PLP, RLA, ROL, ROR, RRA, RTI, RTS, SAX, SBC,
|
||||
SEC, SED, SEI, SKB, SLO, SRE, STA, STX, STY, SXA, SYA, TAS, TAX, TAY, TSX, TXA, TXS, TYA, XAA,
|
||||
XXX,
|
||||
};
|
||||
|
||||
/// CPU Instruction.
|
||||
pub struct Instruction(u8, AddressingMode, OperationType, usize);
|
||||
|
||||
impl Instruction {
|
||||
pub const fn opcode(&self) -> u8 {
|
||||
self.0
|
||||
}
|
||||
|
||||
pub const fn addr_mode(&self) -> AddressingMode {
|
||||
self.1
|
||||
}
|
||||
|
||||
pub const fn op(&self) -> OperationType {
|
||||
self.2
|
||||
}
|
||||
|
||||
pub const fn cycles(&self) -> usize {
|
||||
self.3
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
|
||||
enum OperandType {
|
||||
Accumulator,
|
||||
Address,
|
||||
Immediate,
|
||||
}
|
||||
|
||||
pub struct Operand {
|
||||
operand_type: OperandType,
|
||||
pub value: u16,
|
||||
is_page_crossing: bool,
|
||||
}
|
||||
|
||||
impl Operand {
|
||||
pub const fn value_u8(&self) -> u8 {
|
||||
self.value as u8
|
||||
}
|
||||
|
||||
pub const fn is_address(&self) -> bool {
|
||||
self.operand_type == OperandType::Address
|
||||
}
|
||||
|
||||
pub fn read(&self, cpu: &Cpu) -> u8 {
|
||||
match self.operand_type {
|
||||
OperandType::Accumulator => cpu.registers.a,
|
||||
OperandType::Immediate => self.value_u8(),
|
||||
OperandType::Address => {
|
||||
let addr = self.value;
|
||||
cpu.memory_bus.get_byte(addr)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn write(&self, value: u8, cpu: &mut Cpu) {
|
||||
match self.operand_type {
|
||||
OperandType::Accumulator => cpu.registers.a = value,
|
||||
OperandType::Address => {
|
||||
let addr = self.value;
|
||||
cpu.memory_bus.set_byte(addr, value);
|
||||
}
|
||||
OperandType::Immediate => unreachable!("Cannot write to immediate operand"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Operands
|
||||
impl Cpu {
|
||||
/// 16x16 grid of 6502 opcodes. Matches datasheet matrix for easy lookup
|
||||
#[rustfmt::skip]
|
||||
pub const INSTRUCTIONS: [Instruction; 256] = [
|
||||
Instruction(0x00, IMM, BRK, 7), Instruction(0x01, IDX, ORA, 6), Instruction(0x02, IMP, XXX, 2), Instruction(0x03, IDX, SLO, 8), Instruction(0x04, ZP0, NOP, 3), Instruction(0x05, ZP0, ORA, 3), Instruction(0x06, ZP0, ASL, 5), Instruction(0x07, ZP0, SLO, 5), Instruction(0x08, IMP, PHP, 3), Instruction(0x09, IMM, ORA, 2), Instruction(0x0A, ACC, ASL, 2), Instruction(0x0B, IMM, ANC, 2), Instruction(0x0C, ABS, NOP, 4), Instruction(0x0D, ABS, ORA, 4), Instruction(0x0E, ABS, ASL, 6), Instruction(0x0F, ABS, SLO, 6),
|
||||
Instruction(0x10, REL, BPL, 2), Instruction(0x11, IDY, ORA, 5), Instruction(0x12, IMP, XXX, 2), Instruction(0x13, IDY, SLO, 8), Instruction(0x14, ZPX, NOP, 4), Instruction(0x15, ZPX, ORA, 4), Instruction(0x16, ZPX, ASL, 6), Instruction(0x17, ZPX, SLO, 6), Instruction(0x18, IMP, CLC, 2), Instruction(0x19, ABY, ORA, 4), Instruction(0x1A, IMP, NOP, 2), Instruction(0x1B, ABY, SLO, 7), Instruction(0x1C, ABX, IGN, 4), Instruction(0x1D, ABX, ORA, 4), Instruction(0x1E, ABX, ASL, 7), Instruction(0x1F, ABX, SLO, 7),
|
||||
Instruction(0x20, ABS, JSR, 6), Instruction(0x21, IDX, AND, 6), Instruction(0x22, IMP, XXX, 2), Instruction(0x23, IDX, RLA, 8), Instruction(0x24, ZP0, BIT, 3), Instruction(0x25, ZP0, AND, 3), Instruction(0x26, ZP0, ROL, 5), Instruction(0x27, ZP0, RLA, 5), Instruction(0x28, IMP, PLP, 4), Instruction(0x29, IMM, AND, 2), Instruction(0x2A, ACC, ROL, 2), Instruction(0x2B, IMM, ANC, 2), Instruction(0x2C, ABS, BIT, 4), Instruction(0x2D, ABS, AND, 4), Instruction(0x2E, ABS, ROL, 6), Instruction(0x2F, ABS, RLA, 6),
|
||||
Instruction(0x30, REL, BMI, 2), Instruction(0x31, IDY, AND, 5), Instruction(0x32, IMP, XXX, 2), Instruction(0x33, IDY, RLA, 8), Instruction(0x34, ZPX, NOP, 4), Instruction(0x35, ZPX, AND, 4), Instruction(0x36, ZPX, ROL, 6), Instruction(0x37, ZPX, RLA, 6), Instruction(0x38, IMP, SEC, 2), Instruction(0x39, ABY, AND, 4), Instruction(0x3A, IMP, NOP, 2), Instruction(0x3B, ABY, RLA, 7), Instruction(0x3C, ABX, IGN, 4), Instruction(0x3D, ABX, AND, 4), Instruction(0x3E, ABX, ROL, 7), Instruction(0x3F, ABX, RLA, 7),
|
||||
Instruction(0x40, IMP, RTI, 6), Instruction(0x41, IDX, EOR, 6), Instruction(0x42, IMP, XXX, 2), Instruction(0x43, IDX, SRE, 8), Instruction(0x44, ZP0, NOP, 3), Instruction(0x45, ZP0, EOR, 3), Instruction(0x46, ZP0, LSR, 5), Instruction(0x47, ZP0, SRE, 5), Instruction(0x48, IMP, PHA, 3), Instruction(0x49, IMM, EOR, 2), Instruction(0x4A, ACC, LSR, 2), Instruction(0x4B, IMM, ALR, 2), Instruction(0x4C, ABS, JMP, 3), Instruction(0x4D, ABS, EOR, 4), Instruction(0x4E, ABS, LSR, 6), Instruction(0x4F, ABS, SRE, 6),
|
||||
Instruction(0x50, REL, BVC, 2), Instruction(0x51, IDY, EOR, 5), Instruction(0x52, IMP, XXX, 2), Instruction(0x53, IDY, SRE, 8), Instruction(0x54, ZPX, NOP, 4), Instruction(0x55, ZPX, EOR, 4), Instruction(0x56, ZPX, LSR, 6), Instruction(0x57, ZPX, SRE, 6), Instruction(0x58, IMP, CLI, 2), Instruction(0x59, ABY, EOR, 4), Instruction(0x5A, IMP, NOP, 2), Instruction(0x5B, ABY, SRE, 7), Instruction(0x5C, ABX, IGN, 4), Instruction(0x5D, ABX, EOR, 4), Instruction(0x5E, ABX, LSR, 7), Instruction(0x5F, ABX, SRE, 7),
|
||||
Instruction(0x60, IMP, RTS, 6), Instruction(0x61, IDX, ADC, 6), Instruction(0x62, IMP, XXX, 2), Instruction(0x63, IDX, RRA, 8), Instruction(0x64, ZP0, NOP, 3), Instruction(0x65, ZP0, ADC, 3), Instruction(0x66, ZP0, ROR, 5), Instruction(0x67, ZP0, RRA, 5), Instruction(0x68, IMP, PLA, 4), Instruction(0x69, IMM, ADC, 2), Instruction(0x6A, ACC, ROR, 2), Instruction(0x6B, IMM, ARR, 2), Instruction(0x6C, IND, JMP, 5), Instruction(0x6D, ABS, ADC, 4), Instruction(0x6E, ABS, ROR, 6), Instruction(0x6F, ABS, RRA, 6),
|
||||
Instruction(0x70, REL, BVS, 2), Instruction(0x71, IDY, ADC, 5), Instruction(0x72, IMP, XXX, 2), Instruction(0x73, IDY, RRA, 8), Instruction(0x74, ZPX, NOP, 4), Instruction(0x75, ZPX, ADC, 4), Instruction(0x76, ZPX, ROR, 6), Instruction(0x77, ZPX, RRA, 6), Instruction(0x78, IMP, SEI, 2), Instruction(0x79, ABY, ADC, 4), Instruction(0x7A, IMP, NOP, 2), Instruction(0x7B, ABY, RRA, 7), Instruction(0x7C, ABX, IGN, 4), Instruction(0x7D, ABX, ADC, 4), Instruction(0x7E, ABX, ROR, 7), Instruction(0x7F, ABX, RRA, 7),
|
||||
Instruction(0x80, IMM, SKB, 2), Instruction(0x81, IDX, STA, 6), Instruction(0x82, IMM, SKB, 2), Instruction(0x83, IDX, SAX, 6), Instruction(0x84, ZP0, STY, 3), Instruction(0x85, ZP0, STA, 3), Instruction(0x86, ZP0, STX, 3), Instruction(0x87, ZP0, SAX, 3), Instruction(0x88, IMP, DEY, 2), Instruction(0x89, IMM, SKB, 2), Instruction(0x8A, IMP, TXA, 2), Instruction(0x8B, IMM, XAA, 2), Instruction(0x8C, ABS, STY, 4), Instruction(0x8D, ABS, STA, 4), Instruction(0x8E, ABS, STX, 4), Instruction(0x8F, ABS, SAX, 4),
|
||||
Instruction(0x90, REL, BCC, 2), Instruction(0x91, IDY, STA, 6), Instruction(0x92, IMP, XXX, 2), Instruction(0x93, IDY, AHX, 6), Instruction(0x94, ZPX, STY, 4), Instruction(0x95, ZPX, STA, 4), Instruction(0x96, ZPY, STX, 4), Instruction(0x97, ZPY, SAX, 4), Instruction(0x98, IMP, TYA, 2), Instruction(0x99, ABY, STA, 5), Instruction(0x9A, IMP, TXS, 2), Instruction(0x9B, ABY, TAS, 5), Instruction(0x9C, ABX, SYA, 5), Instruction(0x9D, ABX, STA, 5), Instruction(0x9E, ABY, SXA, 5), Instruction(0x9F, ABY, AHX, 5),
|
||||
Instruction(0xA0, IMM, LDY, 2), Instruction(0xA1, IDX, LDA, 6), Instruction(0xA2, IMM, LDX, 2), Instruction(0xA3, IDX, LAX, 6), Instruction(0xA4, ZP0, LDY, 3), Instruction(0xA5, ZP0, LDA, 3), Instruction(0xA6, ZP0, LDX, 3), Instruction(0xA7, ZP0, LAX, 3), Instruction(0xA8, IMP, TAY, 2), Instruction(0xA9, IMM, LDA, 2), Instruction(0xAA, IMP, TAX, 2), Instruction(0xAB, IMM, LAX, 2), Instruction(0xAC, ABS, LDY, 4), Instruction(0xAD, ABS, LDA, 4), Instruction(0xAE, ABS, LDX, 4), Instruction(0xAF, ABS, LAX, 4),
|
||||
Instruction(0xB0, REL, BCS, 2), Instruction(0xB1, IDY, LDA, 5), Instruction(0xB2, IMP, XXX, 2), Instruction(0xB3, IDY, LAX, 5), Instruction(0xB4, ZPX, LDY, 4), Instruction(0xB5, ZPX, LDA, 4), Instruction(0xB6, ZPY, LDX, 4), Instruction(0xB7, ZPY, LAX, 4), Instruction(0xB8, IMP, CLV, 2), Instruction(0xB9, ABY, LDA, 4), Instruction(0xBA, IMP, TSX, 2), Instruction(0xBB, ABY, LAS, 4), Instruction(0xBC, ABX, LDY, 4), Instruction(0xBD, ABX, LDA, 4), Instruction(0xBE, ABY, LDX, 4), Instruction(0xBF, ABY, LAX, 4),
|
||||
Instruction(0xC0, IMM, CPY, 2), Instruction(0xC1, IDX, CMP, 6), Instruction(0xC2, IMM, SKB, 2), Instruction(0xC3, IDX, DCP, 8), Instruction(0xC4, ZP0, CPY, 3), Instruction(0xC5, ZP0, CMP, 3), Instruction(0xC6, ZP0, DEC, 5), Instruction(0xC7, ZP0, DCP, 5), Instruction(0xC8, IMP, INY, 2), Instruction(0xC9, IMM, CMP, 2), Instruction(0xCA, IMP, DEX, 2), Instruction(0xCB, IMM, AXS, 2), Instruction(0xCC, ABS, CPY, 4), Instruction(0xCD, ABS, CMP, 4), Instruction(0xCE, ABS, DEC, 6), Instruction(0xCF, ABS, DCP, 6),
|
||||
Instruction(0xD0, REL, BNE, 2), Instruction(0xD1, IDY, CMP, 5), Instruction(0xD2, IMP, XXX, 2), Instruction(0xD3, IDY, DCP, 8), Instruction(0xD4, ZPX, NOP, 4), Instruction(0xD5, ZPX, CMP, 4), Instruction(0xD6, ZPX, DEC, 6), Instruction(0xD7, ZPX, DCP, 6), Instruction(0xD8, IMP, CLD, 2), Instruction(0xD9, ABY, CMP, 4), Instruction(0xDA, IMP, NOP, 2), Instruction(0xDB, ABY, DCP, 7), Instruction(0xDC, ABX, IGN, 4), Instruction(0xDD, ABX, CMP, 4), Instruction(0xDE, ABX, DEC, 7), Instruction(0xDF, ABX, DCP, 7),
|
||||
Instruction(0xE0, IMM, CPX, 2), Instruction(0xE1, IDX, SBC, 6), Instruction(0xE2, IMM, SKB, 2), Instruction(0xE3, IDX, ISB, 8), Instruction(0xE4, ZP0, CPX, 3), Instruction(0xE5, ZP0, SBC, 3), Instruction(0xE6, ZP0, INC, 5), Instruction(0xE7, ZP0, ISB, 5), Instruction(0xE8, IMP, INX, 2), Instruction(0xE9, IMM, SBC, 2), Instruction(0xEA, IMP, NOP, 2), Instruction(0xEB, IMM, SBC, 2), Instruction(0xEC, ABS, CPX, 4), Instruction(0xED, ABS, SBC, 4), Instruction(0xEE, ABS, INC, 6), Instruction(0xEF, ABS, ISB, 6),
|
||||
Instruction(0xF0, REL, BEQ, 2), Instruction(0xF1, IDY, SBC, 5), Instruction(0xF2, IMP, XXX, 2), Instruction(0xF3, IDY, ISB, 8), Instruction(0xF4, ZPX, NOP, 4), Instruction(0xF5, ZPX, SBC, 4), Instruction(0xF6, ZPX, INC, 6), Instruction(0xF7, ZPX, ISB, 6), Instruction(0xF8, IMP, SED, 2), Instruction(0xF9, ABY, SBC, 4), Instruction(0xFA, IMP, NOP, 2), Instruction(0xFB, ABY, ISB, 7), Instruction(0xFC, ABX, IGN, 4), Instruction(0xFD, ABX, SBC, 4), Instruction(0xFE, ABX, INC, 7), Instruction(0xFF, ABX, ISB, 7),
|
||||
];
|
||||
|
||||
pub fn read_next_instruction(&mut self) -> Instruction {
|
||||
let op_code = self.program_get_next_byte();
|
||||
let instruction = Instruction[op_code];
|
||||
|
||||
instruction
|
||||
}
|
||||
|
||||
/// Decodes the next operand in memory
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `addr_mode` - The addressing mode of the instruction
|
||||
fn operand_decode(&mut self, addr_mode: AddressingMode) -> Operand {
|
||||
if addr_mode == ACC {
|
||||
Self::operand_decode_acc()
|
||||
} else if addr_mode == IMM {
|
||||
self.operand_decode_immediate()
|
||||
} else {
|
||||
// Other modes are addresses
|
||||
let mut page_crossed = false;
|
||||
let addr = match addr_mode {
|
||||
ZP0 => self.operand_addr_zp(),
|
||||
ZPX => self.operand_addr_zpx(),
|
||||
ZPY => self.operand_addr_zpy(),
|
||||
ABS => self.operand_addr_abs(),
|
||||
ABX => self.operand_addr_absx(&mut page_crossed),
|
||||
ABY => self.operand_addr_absy(&mut page_crossed),
|
||||
IND => self.operand_addr_indj(),
|
||||
IDX => self.operand_addr_indx(),
|
||||
IDY => self.operand_addr_indy(&mut page_crossed),
|
||||
REL => self.operand_addr_rel(),
|
||||
_ => unreachable!("Already handled"),
|
||||
};
|
||||
|
||||
Operand {
|
||||
operand_type: OperandType::Address,
|
||||
value: addr,
|
||||
is_page_crossing: page_crossed,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A - A
|
||||
fn operand_decode_acc() -> Operand {
|
||||
Operand {
|
||||
operand_type: OperandType::Accumulator,
|
||||
value: 0,
|
||||
is_page_crossing: false,
|
||||
}
|
||||
}
|
||||
|
||||
/// #v - byte
|
||||
fn operand_decode_immediate(&mut self) -> Operand {
|
||||
Operand {
|
||||
operand_type: OperandType::Immediate,
|
||||
value: self.program_get_next_byte() as u16,
|
||||
is_page_crossing: false,
|
||||
}
|
||||
}
|
||||
|
||||
/// d - byte % 256
|
||||
fn operand_addr_zp(&mut self) -> u16 {
|
||||
self.program_get_next_byte() as u16
|
||||
}
|
||||
|
||||
/// d,x - (byte + X) % 256
|
||||
fn operand_addr_zpx(&mut self) -> u16 {
|
||||
let addr = self.program_get_next_byte() as u16;
|
||||
(addr + self.registers.x as u16) & 0xFF
|
||||
}
|
||||
|
||||
/// d,y - (byte + Y) % 256
|
||||
fn operand_addr_zpy(&mut self) -> u16 {
|
||||
let addr = self.program_get_next_byte() as u16;
|
||||
(addr + self.registers.y as u16) & 0xFF
|
||||
}
|
||||
|
||||
/// a - word
|
||||
fn operand_addr_abs(&mut self) -> u16 {
|
||||
self.program_get_next_word()
|
||||
}
|
||||
|
||||
/// a,x - word + X
|
||||
fn operand_addr_absx(&mut self, page_crossed: &mut bool) -> u16 {
|
||||
let addr = self.program_get_next_word();
|
||||
let indexed_addr = addr + self.registers.x as u16;
|
||||
|
||||
*page_crossed = is_page_crossed(addr, indexed_addr);
|
||||
|
||||
indexed_addr
|
||||
}
|
||||
|
||||
/// a,y - word + Y
|
||||
fn operand_addr_absy(&mut self, page_crossed: &mut bool) -> u16 {
|
||||
let addr = self.program_get_next_word();
|
||||
let indexed_addr = addr + self.registers.y as u16;
|
||||
|
||||
*page_crossed = is_page_crossed(addr, indexed_addr);
|
||||
|
||||
indexed_addr
|
||||
}
|
||||
|
||||
/// (a) - PEEK(word)
|
||||
fn operand_addr_indj(&mut self) -> u16 {
|
||||
let ref_addr = self.program_get_next_word();
|
||||
|
||||
if ref_addr & 0xFF == 0xFF {
|
||||
// Error in NES CPU for JMP op
|
||||
let low = self.memory_bus.get_byte(ref_addr) as u16;
|
||||
let high = self.memory_bus.get_byte(ref_addr & 0xFF00) as u16;
|
||||
low + (high << 8)
|
||||
} else {
|
||||
self.memory_bus.get_word(ref_addr)
|
||||
}
|
||||
}
|
||||
|
||||
/// (d,x) - PEEK((byte + X) % 256)
|
||||
fn operand_addr_indx(&mut self) -> u16 {
|
||||
let mut ref_addr = self.program_get_next_byte() as u16;
|
||||
ref_addr += self.registers.x as u16;
|
||||
|
||||
let low = self.memory_bus.get_byte(ref_addr & 0xFF) as u16;
|
||||
let high = self.memory_bus.get_byte((ref_addr + 1) & 0xFF) as u16;
|
||||
low + (high << 8)
|
||||
}
|
||||
|
||||
/// (d),y - PEEK(byte) + Y
|
||||
fn operand_addr_indy(&mut self, page_crossed: &mut bool) -> u16 {
|
||||
let mut ref_addr = self.program_get_next_byte() as u16;
|
||||
|
||||
let low = self.memory_bus.get_byte(ref_addr) as u16;
|
||||
let high = self.memory_bus.get_byte((ref_addr + 1) & 0xFF) as u16;
|
||||
let addr = low + (high << 8);
|
||||
let incr_addr = addr + self.registers.y as u16;
|
||||
|
||||
*page_crossed = is_page_crossed(addr, incr_addr);
|
||||
|
||||
incr_addr
|
||||
}
|
||||
|
||||
/// label - PC + byte
|
||||
fn operand_addr_rel(&mut self) -> u16 {
|
||||
let base_addr = self.registers.pc;
|
||||
let offset = self.program_get_next_byte() as u16;
|
||||
|
||||
base_addr + offset
|
||||
}
|
||||
}
|
||||
|
||||
fn is_page_crossed(a: u16, b: u16) -> bool {
|
||||
(a & 0xFF00) != (b & 0xFF00)
|
||||
}
|
181
core/src/cpu/operations.rs
Normal file
181
core/src/cpu/operations.rs
Normal file
@ -0,0 +1,181 @@
|
||||
use crate::cpu::op::Operand;
|
||||
use crate::cpu::{Cpu, CpuInternals, CpuStatus};
|
||||
|
||||
fn is_sign_overflow(val1: u8, val2: u8, result: u8) -> bool {
|
||||
(val1 & 0x80 == val2 & 0x80) && (val1 & 0x80 != result & 0x80)
|
||||
}
|
||||
|
||||
impl Cpu {
|
||||
fn add_with_carry(&mut self, value: u8) {
|
||||
let a = self.registers.a;
|
||||
|
||||
let addition = a.wrapping_add(value);
|
||||
let mut overflow = value < a;
|
||||
|
||||
let result = addition.wrapping_add(self.get_status_flag_u8(CpuStatus::Carry));
|
||||
if result < addition {
|
||||
// The addition resulted in a smaller number, there was overflow
|
||||
overflow = true;
|
||||
}
|
||||
|
||||
self.registers.a = result;
|
||||
self.set_status_flag(CpuStatus::Carry, overflow);
|
||||
self.set_status_flag(CpuStatus::Overflow, is_sign_overflow(a, value, result));
|
||||
}
|
||||
|
||||
fn set_common_flags(&mut self, result: u8) {
|
||||
self.set_status_flag(CpuStatus::Zero, result == 0);
|
||||
self.set_status_flag(CpuStatus::Negative, result & 0x80 != 0);
|
||||
}
|
||||
|
||||
// ADC operations
|
||||
/// Add with carry to accumulator
|
||||
fn op_adc(&mut self, operand: Operand) {
|
||||
let value = operand.read(self);
|
||||
self.add_with_carry(value);
|
||||
}
|
||||
|
||||
/// Unofficial, stores A, X and (high byte of address + 1)
|
||||
/// Unstable in the hardware
|
||||
fn op_ahx(&mut self, operand: Operand) {
|
||||
assert!(operand.is_address());
|
||||
|
||||
let addr = operand.value;
|
||||
let a = self.registers.a;
|
||||
let x = self.registers.x;
|
||||
let h = (addr >> 8) as u8;
|
||||
|
||||
let result = a & x & h;
|
||||
operand.write(result, self);
|
||||
}
|
||||
|
||||
/// Unofficial, AND + LSR
|
||||
fn op_alr(&mut self, operand: Operand) {
|
||||
let value = operand.read(self);
|
||||
let a = self.registers.a;
|
||||
|
||||
let result = a & value;
|
||||
|
||||
// Sets the dropped bit in the carry register
|
||||
self.set_status_flag_u8(CpuStatus::Carry, result & 0x01);
|
||||
|
||||
let result_shift = result >> 1;
|
||||
operand.write(result_shift, self);
|
||||
|
||||
self.set_common_flags(result_shift);
|
||||
}
|
||||
|
||||
/// Unofficial, bitwise AND and set carry from shift left
|
||||
fn op_anc(&mut self, operand: Operand) {
|
||||
let value = operand.read(self);
|
||||
let a = self.registers.a;
|
||||
|
||||
let result = a & value;
|
||||
self.registers.a = result;
|
||||
|
||||
self.set_common_flags(result);
|
||||
self.set_status_flag_u8(CpuStatus::Carry, result & 0x80);
|
||||
}
|
||||
|
||||
/// Bitwise AND with accumulator
|
||||
fn op_and(&mut self, operand: Operand) {
|
||||
let value = operand.read(self);
|
||||
let a = self.registers.a;
|
||||
|
||||
let result = a & value;
|
||||
self.registers.a = result;
|
||||
|
||||
self.set_common_flags(result);
|
||||
}
|
||||
|
||||
/// Unofficial, depends on analog effects and cannot be emulated easily.
|
||||
fn op_ane(&self, _: Operand) {
|
||||
assert!(false);
|
||||
}
|
||||
|
||||
/// Unofficial, AND + ROR
|
||||
fn op_arr(&mut self, operand: Operand) {
|
||||
let value = operand.read(self);
|
||||
let a = self.registers.a;
|
||||
let carry = self.get_status_flag_u8(CpuStatus::Carry);
|
||||
|
||||
let result = a & value;
|
||||
let result_shift = result >> 1 | (carry << 7);
|
||||
self.registers.a = result_shift;
|
||||
|
||||
let result_added = result.wrapping_add(value);
|
||||
|
||||
self.set_common_flags(result_shift);
|
||||
self.set_status_flag_u8(CpuStatus::Carry, result & 0x01);
|
||||
self.set_status_flag(
|
||||
CpuStatus::Overflow,
|
||||
is_sign_overflow(result, value, result_added),
|
||||
);
|
||||
}
|
||||
|
||||
/// Arithmetic shift left
|
||||
fn op_asl(&mut self, operand: Operand) {
|
||||
let value = operand.read(self);
|
||||
|
||||
let result = value << 1;
|
||||
operand.write(result, self);
|
||||
|
||||
self.set_common_flags(result);
|
||||
self.set_status_flag_u8(CpuStatus::Carry, value & 0x80);
|
||||
}
|
||||
|
||||
/// Compare to accumulator
|
||||
fn op_cmp(&mut self, operand: Operand) {
|
||||
let value = operand.read(self);
|
||||
let a = self.registers.a;
|
||||
|
||||
let result = a.wrapping_sub(value);
|
||||
|
||||
self.set_status_flag(CpuStatus::Carry, a >= value);
|
||||
self.set_common_flags(result);
|
||||
}
|
||||
|
||||
/// Bitwise exclusive OR with accumulator
|
||||
fn op_eor(&mut self, operand: Operand) {
|
||||
let value = operand.read(self);
|
||||
let a = self.registers.a;
|
||||
|
||||
let result = a ^ value;
|
||||
self.registers.a = result;
|
||||
|
||||
self.set_common_flags(result);
|
||||
}
|
||||
|
||||
/// Load to accumulator
|
||||
fn op_lda(&mut self, operand: Operand) {
|
||||
let value = operand.read(self);
|
||||
|
||||
self.registers.a = value;
|
||||
|
||||
self.set_common_flags(value);
|
||||
}
|
||||
|
||||
/// Bitwise OR with accumulator
|
||||
fn op_ora(&mut self, operand: Operand) {
|
||||
let value = operand.read(self);
|
||||
let a = self.registers.a;
|
||||
|
||||
let result = a | value;
|
||||
self.registers.a = result;
|
||||
}
|
||||
|
||||
/// Subtract with carry from accumulator
|
||||
fn op_sbc(&mut self, operand: Operand) {
|
||||
let value = operand.read(self);
|
||||
self.add_with_carry(!value);
|
||||
}
|
||||
|
||||
/// Store accumulator
|
||||
fn op_sta(&mut self, operand: Operand) {
|
||||
assert!(operand.is_address());
|
||||
|
||||
let a = self.registers.a;
|
||||
operand.write(a, self);
|
||||
// TODO: C code enabled page crossing in the operand, but I didn't find it in the docs
|
||||
}
|
||||
}
|
13
core/src/lib.rs
Normal file
13
core/src/lib.rs
Normal file
@ -0,0 +1,13 @@
|
||||
mod cpu;
|
||||
mod memory;
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn it_works() {
|
||||
let result = add(2, 2);
|
||||
assert_eq!(result, 4);
|
||||
}
|
||||
}
|
32
core/src/memory.rs
Normal file
32
core/src/memory.rs
Normal file
@ -0,0 +1,32 @@
|
||||
const MEMORY_SIZE: usize = 0x0800;
|
||||
|
||||
pub struct MemoryBus {
|
||||
ram: [u8; MEMORY_SIZE],
|
||||
}
|
||||
|
||||
impl MemoryBus {
|
||||
/// Gets a byte from memory.
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `addr` - The address of the byte
|
||||
pub fn get_byte(&self, addr: u16) -> u8 {
|
||||
unimplemented!("MemoryBus::get_byte");
|
||||
}
|
||||
|
||||
/// Gets a word from memory.
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `addr` - The address of the word
|
||||
pub fn get_word(&self, addr: u16) -> u16 {
|
||||
unimplemented!("MemoryBus::get_word");
|
||||
}
|
||||
|
||||
/// Sets a byte in memory
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `addr` - The address of the byte
|
||||
/// * `value` - The value to set
|
||||
pub fn set_byte(&mut self, addr: u16, value: u8) {
|
||||
unimplemented!("MemoryBus::set_byte");
|
||||
}
|
||||
}
|
1
core/target/.rustc_info.json
Normal file
1
core/target/.rustc_info.json
Normal file
@ -0,0 +1 @@
|
||||
{"rustc_fingerprint":52135951790503613,"outputs":{"4614504638168534921":{"success":true,"status":"","code":0,"stdout":"rustc 1.74.0-nightly (62ebe3a2b 2023-09-08)\nbinary: rustc\ncommit-hash: 62ebe3a2b177d50ec664798d731b8a8d1a9120d1\ncommit-date: 2023-09-08\nhost: x86_64-unknown-linux-gnu\nrelease: 1.74.0-nightly\nLLVM version: 17.0.0\n","stderr":""},"14371922958718593042":{"success":true,"status":"","code":0,"stdout":"___\nlib___.rlib\nlib___.so\nlib___.so\nlib___.a\nlib___.so\n/home/william/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu\noff\npacked\nunpacked\n___\ndebug_assertions\noverflow_checks\npanic=\"unwind\"\nproc_macro\nrelocation_model=\"pic\"\ntarget_abi=\"\"\ntarget_arch=\"x86_64\"\ntarget_endian=\"little\"\ntarget_env=\"gnu\"\ntarget_family=\"unix\"\ntarget_feature=\"fxsr\"\ntarget_feature=\"sse\"\ntarget_feature=\"sse2\"\ntarget_has_atomic\ntarget_has_atomic=\"16\"\ntarget_has_atomic=\"32\"\ntarget_has_atomic=\"64\"\ntarget_has_atomic=\"8\"\ntarget_has_atomic=\"ptr\"\ntarget_has_atomic_equal_alignment=\"16\"\ntarget_has_atomic_equal_alignment=\"32\"\ntarget_has_atomic_equal_alignment=\"64\"\ntarget_has_atomic_equal_alignment=\"8\"\ntarget_has_atomic_equal_alignment=\"ptr\"\ntarget_has_atomic_load_store\ntarget_has_atomic_load_store=\"16\"\ntarget_has_atomic_load_store=\"32\"\ntarget_has_atomic_load_store=\"64\"\ntarget_has_atomic_load_store=\"8\"\ntarget_has_atomic_load_store=\"ptr\"\ntarget_os=\"linux\"\ntarget_pointer_width=\"64\"\ntarget_thread_local\ntarget_vendor=\"unknown\"\nunix\n","stderr":""}},"successes":{}}
|
3
core/target/CACHEDIR.TAG
Normal file
3
core/target/CACHEDIR.TAG
Normal file
@ -0,0 +1,3 @@
|
||||
Signature: 8a477f597d28d172789f06886806bc55
|
||||
# This file is a cache directory tag created by cargo.
|
||||
# For information about cache directory tags see https://bford.info/cachedir/
|
0
core/target/debug/.cargo-lock
Normal file
0
core/target/debug/.cargo-lock
Normal file
@ -0,0 +1 @@
|
||||
This file has an mtime of when this was started.
|
@ -0,0 +1 @@
|
||||
8876810ef228e07e
|
@ -0,0 +1 @@
|
||||
{"rustc":12738513403806206521,"features":"[]","target":16474754454797904296,"profile":13126374248311259211,"path":17523903030608720598,"deps":[],"local":[{"CheckDepInfo":{"dep_info":"debug/.fingerprint/core-0d1f6aefe433fb28/dep-lib-core"}}],"rustflags":[],"metadata":7797948686568424061,"config":2202906307356721367,"compile_kind":0}
|
@ -0,0 +1 @@
|
||||
This file has an mtime of when this was started.
|
@ -0,0 +1 @@
|
||||
d2244b0cb93f6bbb
|
@ -0,0 +1 @@
|
||||
{"rustc":12738513403806206521,"features":"[]","target":16474754454797904296,"profile":18326522262828315194,"path":17523903030608720598,"deps":[],"local":[{"CheckDepInfo":{"dep_info":"debug/.fingerprint/core-9be0639a870abcef/dep-test-lib-core"}}],"rustflags":[],"metadata":7797948686568424061,"config":2202906307356721367,"compile_kind":0}
|
Loading…
Reference in New Issue
Block a user