Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 26 additions & 6 deletions src-tauri/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -70,19 +70,39 @@ fn get_level_explanation(level_id: String) -> Result<String, String> {
}

#[tauri::command]
fn get_level_ini(level_id: String) -> Result<String, String> {
fn get_level_ini(level_id: String, syntax: String) -> Result<String, String> {
let dir = level_dir_for_id(level_id.as_str())
.ok_or_else(|| format!("Stage files not found for level: {}", level_id))?;
let p = format!("{}/ini.asm", dir);
read_stage_file(&p)
let primary = match syntax.as_str() {
"Att" => format!("{}/ini_Att.asm", dir),
_ => format!("{}/ini.asm", dir),
};
match read_stage_file(&primary) {
Ok(s) => Ok(s),
Err(_) => {
// Backward compatible fallback
let fallback = format!("{}/ini.asm", dir);
read_stage_file(&fallback)
}
}
}

#[tauri::command]
fn get_level_collect(level_id: String) -> Result<String, String> {
fn get_level_collect(level_id: String, syntax: String) -> Result<String, String> {
let dir = level_dir_for_id(level_id.as_str())
.ok_or_else(|| format!("Stage files not found for level: {}", level_id))?;
let p = format!("{}/collect.asm", dir);
read_stage_file(&p)
let primary = match syntax.as_str() {
"Att" => format!("{}/collect_Att.asm", dir),
_ => format!("{}/collect.asm", dir),
};
match read_stage_file(&primary) {
Ok(s) => Ok(s),
Err(_) => {
// Backward compatible fallback
let fallback = format!("{}/collect.asm", dir);
read_stage_file(&fallback)
}
}
}

#[tauri::command]
Expand Down
83 changes: 75 additions & 8 deletions src/lib/components/ExplanationView.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,37 @@

export let levelId: string | null = null;
export let isCompleted: boolean = false;
export let syntax: "Intel" | "Att" = "Intel";

let explanation: string | null = null;
let collectCode: string | null = null;
let collectIntel: string | null = null;
let collectAtt: string | null = null;
let loading = false;
let loadingCollect = false;
let error: string | null = null;
let errorCollect: string | null = null;
let showExplanation = false;
let showSolution = false;
let solutionSyntax: "Intel" | "Att" = "Intel";
let lastLevelId: string | null = null;

$: currentCollect =
solutionSyntax === "Att" ? collectAtt : collectIntel;

// レベルが切り替わったらキャッシュや表示状態をクリア(前のレベルの内容が残るのを防ぐ)
$: if (levelId !== lastLevelId) {
lastLevelId = levelId;
explanation = null;
collectIntel = null;
collectAtt = null;
error = null;
errorCollect = null;
loading = false;
loadingCollect = false;
showExplanation = false;
showSolution = false;
solutionSyntax = syntax;
}

async function loadExplanation() {
if (!levelId || !isCompleted) return;
Expand All @@ -36,12 +58,17 @@
showExplanation = !showExplanation;
}

async function loadCollect() {
async function loadCollect(targetSyntax: "Intel" | "Att") {
if (!levelId || !isCompleted) return;
loadingCollect = true;
errorCollect = null;
try {
collectCode = await invoke("get_level_collect", { levelId });
const code: string = await invoke("get_level_collect", {
levelId,
syntax: targetSyntax,
});
if (targetSyntax === "Att") collectAtt = code;
else collectIntel = code;
} catch (e) {
errorCollect = String(e);
console.error("Failed to load collect.asm:", e);
Expand All @@ -51,12 +78,22 @@
}

function toggleSolution() {
if (!showSolution && !collectCode) {
loadCollect();
if (!showSolution) {
solutionSyntax = syntax;
if (!currentCollect) {
loadCollect(solutionSyntax);
}
}
showSolution = !showSolution;
}

function onChangeSolutionSyntax(next: "Intel" | "Att") {
solutionSyntax = next;
if (!currentCollect) {
loadCollect(solutionSyntax);
}
}

// Markdownを簡単にレンダリング(基本的な処理)
function formatMarkdown(text: string): string {
let formatted = text;
Expand Down Expand Up @@ -119,10 +156,21 @@
<div class="loading">模範解答を読み込んでいます...</div>
{:else if errorCollect}
<div class="error">模範解答の読み込みに失敗しました: {errorCollect}</div>
{:else if collectCode}
{:else if currentCollect}
<div class="explanation-content">
<h2>模範解答(collect.asm)</h2>
<pre class="code-block"><code>{collectCode.trim()}</code></pre>
<div class="solution-head">
<h2>模範解答(collect.asm)</h2>
<select
class="syntax-select"
bind:value={solutionSyntax}
on:change={(e) =>
onChangeSolutionSyntax((e.currentTarget as HTMLSelectElement).value as any)}
>
<option value="Intel">Intel</option>
<option value="Att">AT&amp;T</option>
</select>
</div>
<pre class="code-block"><code>{currentCollect.trim()}</code></pre>
</div>
{/if}
</div>
Expand All @@ -140,6 +188,25 @@
gap: 0.75rem;
}

.solution-head {
display: flex;
align-items: center;
justify-content: space-between;
gap: 1rem;
}

.syntax-select {
background: rgba(0, 0, 0, 0.25);
color: #94a3b8;
border: 1px solid rgba(255, 255, 255, 0.12);
padding: 0.45rem 0.75rem;
border-radius: 10px;
font-family: "Fira Code", monospace;
font-size: 0.75rem;
cursor: pointer;
outline: none;
}

.explanation-toggle {
display: flex;
align-items: center;
Expand Down
5 changes: 3 additions & 2 deletions src/routes/grand/[grandId]/+page.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
markLevelComplete,
} from "$lib/progress";

let syntax = "Intel";
let syntax: "Intel" | "Att" = "Intel";
let code = `section .bss
buf resb 16

Expand Down Expand Up @@ -114,7 +114,7 @@ _start:
async function applyDefaultCode(levelId: string) {
// 可能ならステージごとの ini.asm を初期コードとして読み込む
try {
const ini: string = await invoke("get_level_ini", { levelId });
const ini: string = await invoke("get_level_ini", { levelId, syntax });
code = ini;
return;
} catch (e) {
Expand Down Expand Up @@ -289,6 +289,7 @@ _start:
<ExplanationView
levelId={currentLevel.id}
isCompleted={$completedLevelsStore.has(currentLevel.id)}
{syntax}
/>
{/if}
</div>
Expand Down
61 changes: 61 additions & 0 deletions stages/1.The-Accumulator/BOSS/12_TheAccumulator/collect_Att.asm
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
section .bss
buf resb 32
out resb 32

section .text
global _start

_start:
# read(0, buf, 32)
movq $0, %rax
movq $0, %rdi
movq $buf, %rsi
movq $32, %rdx
syscall

movq %rax, %rcx
movq $buf, %r15
movq $out, %r14
xorq %r8, %r8

.loop:
cmpq %rcx, %r8
jge .done

movb (%r15,%r8,1), %al

# uppercase?
cmpb $0x41, %al
jl .check_digit
cmpb $0x5a, %al
jg .check_digit
addb $0x20, %al
jmp .write

.check_digit:
cmpb $0x30, %al
jl .write
cmpb $0x39, %al
jg .write
incb %al
cmpb $0x3a, %al
jne .write
movb $0x30, %al

.write:
movb %al, (%r14,%r8,1)
incq %r8
jmp .loop

.done:
# write(1, out, rcx)
movq %rcx, %rdx
movq $1, %rax
movq $1, %rdi
movq %r14, %rsi
syscall

movq $60, %rax
xorq %rdi, %rdi
syscall

17 changes: 17 additions & 0 deletions stages/1.The-Accumulator/BOSS/12_TheAccumulator/ini_Att.asm
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
section .bss
buf resb 32
out resb 32

section .text
global _start

_start:
# MISSION: The Accumulator (BOSS STAGE)
# - A-Z -> a-z
# - 0-9 -> increment (wrap 9->0)
# - others unchanged

movq $60, %rax
xorq %rdi, %rdi
syscall

6 changes: 6 additions & 0 deletions stages/1.The-Accumulator/BOSS/12_TheAccumulator/test.sh
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,11 @@ STAGE_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"

cargo run --quiet --manifest-path "$ROOT_DIR/src-tauri/Cargo.toml" --bin stage_runner -- \
--level-id "12_TheAccumulator" \
--syntax Intel \
--asm "$STAGE_DIR/collect.asm"

cargo run --quiet --manifest-path "$ROOT_DIR/src-tauri/Cargo.toml" --bin stage_runner -- \
--level-id "12_TheAccumulator" \
--syntax Att \
--asm "$STAGE_DIR/collect_Att.asm"

Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
section .bss
buf resb 16

section .text
global _start

_start:
# read(0, buf, 16)
movq $0, %rax
movq $0, %rdi
movq $buf, %rsi
movq $16, %rdx
syscall

# write(1, buf, rax)
movq %rax, %rdx
movq $1, %rax
movq $1, %rdi
movq $buf, %rsi
syscall

# exit(0)
movq $60, %rax
xorq %rdi, %rdi
syscall

Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
section .bss
buf resb 16

section .text
global _start

_start:
# MISSION: Mov & Call
# read from stdin (syscall 0), write to stdout (syscall 1)

# exit(0)
movq $60, %rax
xorq %rdi, %rdi
syscall

Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,11 @@ STAGE_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"

cargo run --quiet --manifest-path "$ROOT_DIR/src-tauri/Cargo.toml" --bin stage_runner -- \
--level-id "01_Mov&Call" \
--syntax Intel \
--asm "$STAGE_DIR/collect.asm"

cargo run --quiet --manifest-path "$ROOT_DIR/src-tauri/Cargo.toml" --bin stage_runner -- \
--level-id "01_Mov&Call" \
--syntax Att \
--asm "$STAGE_DIR/collect_Att.asm"

Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
section .bss
buf resb 16

section .text
global _start

_start:
# read(0, buf, 16)
movq $0, %rax
movq $0, %rdi
movq $buf, %rsi
movq $16, %rdx
syscall

# rcx = bytes read, r8 = index
movq %rax, %rcx
xorq %r8, %r8

.loop:
cmpq %rcx, %r8
jge .done_add
movb buf(%r8), %al
addb $1, %al
movb %al, buf(%r8)
incq %r8
jmp .loop

.done_add:
# write(1, buf, rcx)
movq %rcx, %rdx
movq $1, %rax
movq $1, %rdi
movq $buf, %rsi
syscall

# exit(0)
movq $60, %rax
xorq %rdi, %rdi
syscall

Loading