2025年5月9日 星期五

bss section (2) - 在 linux 下測試 bss

the 1st edition: 20120304
the 2nd edition: 20250505
env: in 32bit linux, 64 bit 似乎有點不同??

在沒有 os 的環境下搞懂 bss 有點麻煩嗎?這次提供在 os 環境下, 看看 bss。首先找出 bss 開始和結束的地方。先來看看 default linker script。

list 1. ld --verbose
  1 GNU ld (GNU Binutils for Debian) 2.22
  2   Supported emulations:
  3    elf_i386
  4    i386linux
  5    elf32_x86_64
  6    elf_x86_64
  7    elf_l1om
  8    elf_k1om
  9 using internal linker script:
 10 ==================================================
 11 /* Script for -z combreloc: combine and sort reloc sections */
 12 OUTPUT_FORMAT("elf32-i386", "elf32-i386",
 13        "elf32-i386")
 14 OUTPUT_ARCH(i386)
 15 ENTRY(_start)
 16 SEARCH_DIR("/usr/i486-linux-gnu/lib32"); SEARCH_DIR("=/usr/local/lib32"); 
SEARCH_DIR("=/lib32"); SEARCH_DIR("=/usr/lib32"); 
SEARCH_DIR("=/usr/local/lib/i386-linux-gnu"); SEARCH_DIR("=/usr/local/lib"); 
SEARCH_DIR("=/lib/i386-linux-gnu"); SEARCH_DIR("=/lib"); 
SEARCH_DIR("=/usr/lib/i386-linux-gnu"); SEARCH_DIR("=/usr/lib");
 17 SECTIONS
 18 {
 19   /* Read-only sections, merged into text segment: */
 20   PROVIDE (__executable_start = SEGMENT_START("text-segment", 0x08048000)); 
. = SEGMENT_START("text-segment", 0x08048000) + SIZEOF_HEADERS;
 21   .interp         : { *(.interp) }
 22   .note.gnu.build-id : { *(.note.gnu.build-id) }
 23   .hash           : { *(.hash) }
 24   .gnu.hash       : { *(.gnu.hash) }
 25   .dynsym         : { *(.dynsym) }
 26   .dynstr         : { *(.dynstr) }
 27   .gnu.version    : { *(.gnu.version) }
 28   .gnu.version_d  : { *(.gnu.version_d) }
 29   .gnu.version_r  : { *(.gnu.version_r) }
 30   .rel.dyn        :
 31     {
 32       *(.rel.init)
 33       *(.rel.text .rel.text.* .rel.gnu.linkonce.t.*)
 34       *(.rel.fini)
 35       *(.rel.rodata .rel.rodata.* .rel.gnu.linkonce.r.*)
 36       *(.rel.data.rel.ro* .rel.gnu.linkonce.d.rel.ro.*)
 37       *(.rel.data .rel.data.* .rel.gnu.linkonce.d.*)
 38       *(.rel.tdata .rel.tdata.* .rel.gnu.linkonce.td.*)
 39       *(.rel.tbss .rel.tbss.* .rel.gnu.linkonce.tb.*)
 40       *(.rel.ctors)
 41       *(.rel.dtors)
 42       *(.rel.got)
 43       *(.rel.bss .rel.bss.* .rel.gnu.linkonce.b.*)
 44       *(.rel.ifunc)
 45     }
 46   .rel.plt        :
 47     {
 48       *(.rel.plt)
 49       PROVIDE_HIDDEN (__rel_iplt_start = .);
 50       *(.rel.iplt)
 51       PROVIDE_HIDDEN (__rel_iplt_end = .);
 52     }
 53   .init           :
 54   {
 55     KEEP (*(.init))
 56   } =0x90909090
 57   .plt            : { *(.plt) *(.iplt) }
 58   .text           :
 59   {
 60     *(.text.unlikely .text.*_unlikely)
 61     *(.text.exit .text.exit.*)
 62     *(.text.startup .text.startup.*)
 63     *(.text.hot .text.hot.*)
 64     *(.text .stub .text.* .gnu.linkonce.t.*)
 65     /* .gnu.warning sections are handled specially by elf32.em.  */
 66     *(.gnu.warning)
 67   } =0x90909090
 68   .fini           :
 69   {
 70     KEEP (*(.fini))
 71   } =0x90909090
 72   PROVIDE (__etext = .);
 73   PROVIDE (_etext = .);
 74   PROVIDE (etext = .);
 75   .rodata         : { *(.rodata .rodata.* .gnu.linkonce.r.*) }
 76   .rodata1        : { *(.rodata1) }
 77   .eh_frame_hdr : { *(.eh_frame_hdr) }
 78   .eh_frame       : ONLY_IF_RO { KEEP (*(.eh_frame)) }
 79   .gcc_except_table   : ONLY_IF_RO { *(.gcc_except_table
 80   .gcc_except_table.*) }
 81   /* These sections are generated by the Sun/Oracle C++ compiler.  */
 82   .exception_ranges   : ONLY_IF_RO { *(.exception_ranges
 83   .exception_ranges*) }
 84   /* Adjust the address for the data segment.  We want to adjust up to
 85      the same address within the page on the next page up.  */
 86   . = ALIGN (CONSTANT (MAXPAGESIZE)) - ((CONSTANT (MAXPAGESIZE) - .) 
& (CONSTANT (MAXPAGESIZE) - 1)); 
. = DATA_SEGMENT_ALIGN (CONSTANT (MAXPAGESIZE), CONSTANT (COMMONPAGESIZE));
 87   /* Exception handling  */
 88   .eh_frame       : ONLY_IF_RW { KEEP (*(.eh_frame)) }
 89   .gcc_except_table   : ONLY_IF_RW { *(.gcc_except_table .gcc_except_table.*) }
 90   .exception_ranges   : ONLY_IF_RW { *(.exception_ranges .exception_ranges*) }
 91   /* Thread Local Storage sections  */
 92   .tdata   : { *(.tdata .tdata.* .gnu.linkonce.td.*) }
 93   .tbss    : { *(.tbss .tbss.* .gnu.linkonce.tb.*) *(.tcommon) }
 94   .preinit_array     :
 95   {
 96     PROVIDE_HIDDEN (__preinit_array_start = .);
 97     KEEP (*(.preinit_array))
 98     PROVIDE_HIDDEN (__preinit_array_end = .);
 99   }
100   .init_array     :
101   {
102     PROVIDE_HIDDEN (__init_array_start = .);
103     KEEP (*(SORT_BY_INIT_PRIORITY(.init_array.*) SORT_BY_INIT_PRIORITY(.ctors.*)))
104     KEEP (*(.init_array))
105     KEEP (*(EXCLUDE_FILE (*crtbegin.o *crtbegin?.o *crtend.o *crtend?.o ) .ctors))
106     PROVIDE_HIDDEN (__init_array_end = .);
107   }
108   .fini_array     :
109   {
110     PROVIDE_HIDDEN (__fini_array_start = .);
111     KEEP (*(SORT_BY_INIT_PRIORITY(.fini_array.*) SORT_BY_INIT_PRIORITY(.dtors.*)))
112     KEEP (*(.fini_array))
113     KEEP (*(EXCLUDE_FILE (*crtbegin.o *crtbegin?.o *crtend.o *crtend?.o ) .dtors))
114     PROVIDE_HIDDEN (__fini_array_end = .);
115   }
116   .ctors          :
117   {
118     /* gcc uses crtbegin.o to find the start of
119        the constructors, so we make sure it is
120        first.  Because this is a wildcard, it
121        doesn't matter if the user does not
122        actually link against crtbegin.o; the
123        linker won't look for a file to match a
124        wildcard.  The wildcard also means that it
125        doesn't matter which directory crtbegin.o
126        is in.  */
127     KEEP (*crtbegin.o(.ctors))
128     KEEP (*crtbegin?.o(.ctors))
129     /* We don't want to include the .ctor section from
130        the crtend.o file until after the sorted ctors.
131        The .ctor section from the crtend file contains the
132        end of ctors marker and it must be last */
133     KEEP (*(EXCLUDE_FILE (*crtend.o *crtend?.o ) .ctors))
134     KEEP (*(SORT(.ctors.*)))
135     KEEP (*(.ctors))
136   }
137   .dtors          :
138   {
139     KEEP (*crtbegin.o(.dtors))
140     KEEP (*crtbegin?.o(.dtors))
141     KEEP (*(EXCLUDE_FILE (*crtend.o *crtend?.o ) .dtors))
142     KEEP (*(SORT(.dtors.*)))
143     KEEP (*(.dtors))
144   }
145   .jcr            : { KEEP (*(.jcr)) }
146   .data.rel.ro : { *(.data.rel.ro.local* .gnu.linkonce.d.rel.ro.local.*) 
*(.data.rel.ro* .gnu.linkonce.d.rel.ro.*) }
147   .dynamic        : { *(.dynamic) }
148   .got            : { *(.got) *(.igot) }
149   . = DATA_SEGMENT_RELRO_END (12, .);
150   .got.plt        : { *(.got.plt)  *(.igot.plt) }
151   .data           :
152   {
153     *(.data .data.* .gnu.linkonce.d.*)
154     SORT(CONSTRUCTORS)
155   }
156   .data1          : { *(.data1) }
157   _edata = .; PROVIDE (edata = .);
158   __bss_start = .;
159   .bss            :
160   {
161    *(.dynbss)
162    *(.bss .bss.* .gnu.linkonce.b.*)
163    *(COMMON)
164    /* Align here to ensure that the .bss section occupies space up to
165       _end.  Align after .bss to ensure correct alignment even if the
166       .bss section disappears because there are no input sections.
167       FIXME: Why do we need it? When there is no .bss section, we don't
168       pad the .data section.  */
169    . = ALIGN(. != 0 ? 32 / 8 : 1);
170   }
171   . = ALIGN(32 / 8);
172   . = ALIGN(32 / 8);
173   _end = .; PROVIDE (end = .);
174   . = DATA_SEGMENT_END (.);
175   /* Stabs debugging sections.  */
176   .stab          0 : { *(.stab) }
177   .stabstr       0 : { *(.stabstr) }
178   .stab.excl     0 : { *(.stab.excl) }
179   .stab.exclstr  0 : { *(.stab.exclstr) }
180   .stab.index    0 : { *(.stab.index) }
181   .stab.indexstr 0 : { *(.stab.indexstr) }
182   .comment       0 : { *(.comment) }
183   /* DWARF debug sections.
184      Symbols in the DWARF debugging sections are relative to the beginning
185      of the section so we begin them at 0.  */
186   /* DWARF 1 */
187   .debug          0 : { *(.debug) }
188   .line           0 : { *(.line) }
189   /* GNU DWARF 1 extensions */
190   .debug_srcinfo  0 : { *(.debug_srcinfo) }
191   .debug_sfnames  0 : { *(.debug_sfnames) }
192   /* DWARF 1.1 and DWARF 2 */
193   .debug_aranges  0 : { *(.debug_aranges) }
194   .debug_pubnames 0 : { *(.debug_pubnames) }
195   /* DWARF 2 */
196   .debug_info     0 : { *(.debug_info .gnu.linkonce.wi.*) }
197   .debug_abbrev   0 : { *(.debug_abbrev) }
198   .debug_line     0 : { *(.debug_line) }
199   .debug_frame    0 : { *(.debug_frame) }
200   .debug_str      0 : { *(.debug_str) }
201   .debug_loc      0 : { *(.debug_loc) }
202   .debug_macinfo  0 : { *(.debug_macinfo) }
203   /* SGI/MIPS DWARF 2 extensions */
204   .debug_weaknames 0 : { *(.debug_weaknames) }
205   .debug_funcnames 0 : { *(.debug_funcnames) }
206   .debug_typenames 0 : { *(.debug_typenames) }
207   .debug_varnames  0 : { *(.debug_varnames) }
208   /* DWARF 3 */
209   .debug_pubtypes 0 : { *(.debug_pubtypes) }
210   .debug_ranges   0 : { *(.debug_ranges) }
211   .gnu.attributes 0 : { KEEP (*(.gnu.attributes)) }
212   /DISCARD/ : { *(.note.GNU-stack) *(.gnu_debuglink) *(.gnu.lto_*) }
213 }
214 
215 
216 ==================================================

我知道很長, 我和你一樣大部份有看沒有懂, 只看 list 1. L158 ~ L173 就好。

158   __bss_start = .;
159   .bss            :
160   {
161    *(.dynbss)
162    *(.bss .bss.* .gnu.linkonce.b.*)
163    *(COMMON)
164    /* Align here to ensure that the .bss section occupies space up to
165       _end.  Align after .bss to ensure correct alignment even if the
166       .bss section disappears because there are no input sections.
167       FIXME: Why do we need it? When there is no .bss section, we don't
168       pad the .data section.  */
169    . = ALIGN(. != 0 ? 32 / 8 : 1);
170   }
171   . = ALIGN(32 / 8);
172   . = ALIGN(32 / 8);
173   _end = .; PROVIDE (end = .);

_end, __bss_start 這兩個 symbol 就是 bss section 的開始和結束。先來看看程式執行結果。

i: 0x8049734
i: 0x8049738
&__bss_start: 0x8049734
&_end: 0x804973c
ABCDEF: 11223344

bss 從  0x8049734 ~ 0x804973c 佔了 8 byte。

b.c L3 ABCDEF 不是預期的 0。表示成功改變 bss 這個區域。這應該簡單多了, 而且也可以馬上在 linux 上實驗。

另外有的人可能會因為 b.c L3 的寫法好像沒給變數初值, 改用 L4 的寫法, 指定了 0 就以為是以 0 為初值, 在這個例子上, 就算這樣寫, ABCDEF 一樣是 11223344。

b.s int ABCDED=0;
        .text
        .globl  ABCDEF
        .bss
        .align 4
        .type   ABCDEF, @object
        .size   ABCDEF, 4
ABCDEF:
        .zero   4

有趣的是, 如果是給 1, int ABCDEF = 1; 則 ABCDEF 的初值就還是1, 因為這時候 ABCDEF 就不放在 bss section 而是放在 data section。

b.s int ABCDEF=1;
        .text
        .globl  ABCDEF
        .data
        .align 4
        .type   ABCDEF, @object
        .size   ABCDEF, 4
ABCDEF:
        .long   1

b.c
 1 #include <stdio.h>
 2 
 3 int ABCDEF;
 4 //int ABCDEF = 0;
 5 extern int _end;
 6 extern int __bss_start;
 7 
 8 void fill_bss(void)
 9 {
10   int *i=0;
11   for (i = &__bss_start ; i != &_end ; ++i)
12   {
13     printf("i: %p\n", i);
14     *i = 0x11223344;
15   }
16 
17 }
18 
19 int main(void)
20 {
21   fill_bss();
22   printf("&__bss_start: %p\n", &__bss_start);
23   printf("&_end: %p\n", &_end);
24   printf("ABCDEF: %x\n", ABCDEF);
25   return 0;
26 }

看看 elf 裡頭的資訊:
[26] .bss NOBITS 08049734 000734 000008 00  WA  0   0  4
63: 08049738     4 OBJECT  GLOBAL DEFAULT   26 ABCDEF
bss 佔 8 byte, ABCDEF 則位於 0x08049738

source code:
git clone git@github.com:descent/progs.git'
cd bss

ref:
linux kernel 完全剖析 (3.5.4 p 96)

2025年4月22日 星期二

hello pc9801

從來沒想過在 pc9801 上寫程式, 不過有了 dosbox-x 模擬器之後, 可以測試看看。

toolchain 用的一樣是 gcc, AI 建議使用 TASM + Turbo C 或是 DJGPP, 這些我不熟, 要打造環境也要另外花時間, 之前有使用 gcc 寫 dos 程式的經驗, 就拿來硬上, 需要注意大概是 bios call 要轉成 dos int call, 因為 pc9801 用的 bios 和 IBM/PC 不同。

這表示也可以用 c++, 以下範例以 c++ 和組合語言完成。

本來打算用 inline assembly 完成 dos int call, 透過 ai 很容易問到語法 (gcc inline assmelby 實在太可怕, 老是記不住), 不過 edx 這邊一直編譯不過, 只好改用組合語言完成。

pc9801.cpp
 1 __asm__(".code16gcc\n");
 2 #include "io.h"
 3 #include "obj.h"
 4 
 5 typedef signed char s8;
 6 typedef signed short s16;
 7 typedef signed int s32;
 8 
 9 typedef unsigned char u8;
10 typedef unsigned short u16;
11 typedef unsigned int u32;
12 
13 extern "C" void print_string(const char* str);
14 
15 extern "C" int cpp_main(void)
16 {
17   //BOCHS_MB
18   //u32 esp = get_sp();
19 
20 #if 0
21   while (val == 3)
22   {
23   }
24 #endif
25   print_string("hello pc9801/c++\r\n$");
26 
27   __asm__ volatile ("mov  $0x4c00, %ax\t\n");
28   __asm__ volatile ("int  $0x21\t\n");   // 回到 DOS
29 
30   return 0;
31 }

本來想用 inline assembly 建立 c function, 但是一直有問題, 只好出動組合語言, 有了 AI 的幫助, 很容易就搞定組合語言版本。

dio.S dos 中斷, 顯示字串, 字串須以 $ 結尾
 1 # print_string.s - AT&T syntax for real-mode DOS
 2 # void print_string(const char* str);
 3 
 4     .code16                         # 16-bit code
 5     .globl print_string            # C 函數名 (DJGPP 會加底線)
 6 
 7 print_string:
 8     push %ebp
 9     mov  %esp, %ebp
10 
11     mov  8(%ebp), %dx                # 將參數 str 傳給 DX
12     mov  $0x09, %ah                 # AH = 09h 顯示字串
13     int  $0x21                      # 呼叫 DOS 服務
14 
15     pop  %ebp
16     ret
list 1 編譯指令
1 g++ -DRELOC -static -O0 -m32  -g -Wall -Wextra -nostdlib -fno-builtin -nostartfiles -nodefaultlibs -fno-exceptions -fno-rtti -fno-stack-protector -std=c++17  -c cpp_init.S -o cpp_init.reloc.o
2 g++ -static -O0 -m32  -g -Wall -Wextra -nostdlib -fno-builtin -nostartfiles -nodefaultlibs -fno-exceptions -fno-rtti -fno-stack-protector -std=c++17  -c pc9801.cpp
3 as --32 dio.S -o dio.o
4 ld -pie -m elf_i386 -static -Treloc.ld -nostdlib -o pc9801.elf cpp_init.reloc.o pc9801.o dio.o
5 objcopy -R .pdr -R .comment -R.note -S -O binary pc9801.elf pc9801.bin
reloc.ld
 1 /* for cb.c */
 2 ENTRY(_start);
 3 SECTIONS
 4 {
 5 
 6     . = 0x100;
 7     __image_copy_start = .;
 8     .text :
 9     {
10         *(.text)
11         *(.gnu.linkonce.t*)
12     }
13     .rodata :
14     {
15         *(.rodata*)
16         *(.gnu.linkonce.r*)
17     }
18 
19     .data :
20     {
21         *(.data.*)
22         *(.gnu.linkonce.d*)
23     }
24     __image_copy_end = .;
25 
26  . = ALIGN(4);
27 
28 __rel_dyn_start = .;
29  .rel.dyn : {
30   *(.rel.dyn*)
31  }
32 __rel_dyn_end = .;
33 
34     /* for g++ 4.7 */
35     .init_array :
36     {
37       __start_global_ctor__ = .;
38     }
39     __end_global_ctor__ = .;
40     .ctors :
41     {
42       start_ctors = .; _start_ctors = .; __start_ctors = .;
43       *(.ctor*)
44       end_ctors = .; _end_ctors = .; __end_ctors = .;
45 /*      . = ALIGN(0x1000); */
46      }
47      /*
48     .dtors :
49     {
50       start_dtors = .; _start_dtors = .; __start_dtors = .;
51       *(.dtor*)
52       end_dtors = .; _end_dtors = .; __end_dtors = .;
53       . = ALIGN(0x1000);
54      }
55      */
56 
57 
58     .bss :
59     {
60         sbss = .; __bss_start__ = .;
61         *(.bss)
62         ebss = .; __bss_end__ = .;
63         *(COMMON)
64         *(.gnu.linkonce.b*)
65     }
66 
67     /DISCARD/ :
68     {
69         *(.comment)
70         *(.eh_frame) /* discard this, unless you are implementing runtime support for C++ exceptions. */
71     }
72 }


source code:
https://github.com/descent/simple_os
branch cpp_runtime
simple_os/cpp_runtime/global_object/dos_cpp
fig 1. hello pc9801

這個程式很大可能無法在真實 pc9801 機器執行, 因為 pc9801 使用 8086 相容 cpu, 無法執行 eax 這種暫存器, 只能用 ax 才是。

2025年4月20日 星期日

麻雀幻想曲II - 2001 中文版

這其實就是一個麻將遊戲, 但加上一些故事背景, 搞的看起來很像冒險遊戲。

fig 1. 麻雀幻想曲II - 2001 中文版
fig 2. 遊戲海報


以前的美少女遊戲都會附上一張大海報, 蠻有收藏性。



遊戲有2片 CD, 一片是遊戲安裝片, 一片是音樂CD, 可以直接聽, 也可以當遊戲的背景音樂, 如果不用音樂CD當背景音樂, 也可以用遊戲內建音樂。



以下為片頭動畫:


人物畫風很唯美, 承襲一貫的 pc9801 遊戲風格。遊戲手冊也算精緻, 講解一些日本13張麻將的規則, 還有一些牌型, 不習慣13張麻將的玩家可以參考一下。

2025年4月18日 星期五

20250322 行車紀錄器 acer t4-pro

acer t4-pro, 11999 nt

20250322 在車之輪購入行車紀錄器 acer t4-pro, 安裝費用 1500,辛苦師父們的施工, 讓老車有了前後鏡頭, 倒車終於可以看到後面了。之前如果車後有機車, 從後玻璃是看不到機車, 有可能倒車會撞到該台機車, 有點危險。

acer t4-pro 付了一張 64G sd card, 不用再另外購入 sd card。鏡頭是前後分開, 各 2K 解析度。我買的是後鏡頭外接的版本, 另外有安裝在車內的版本。

安裝施工的部份相當複雜, 大概花了2小時才安裝完成, 後鏡頭本來需要鑽洞, 後來師父改了走線, 不用鑽洞, 1500 花的很超值。

也才知道有些地方是用矽利康補強/黏接過, 師父安裝時有跟我說明。後車廂螺絲有些老化, 無法正常拆卸, 老車真難搞。

有 gps 可以定位以及校正時間, 蠻方便, 不需要自己設定。

2025年4月6日 星期日

pc9801 闘神都市2 hdm 軟碟影像檔安裝到硬碟

fig 1. 遊戲開始畫面

hdm 是 pc9801 軟碟影像檔, 需要安裝到硬碟才能玩, 在網路上搜尋了不少資訊, 沒有找到完整說明如何安裝 hdm 到硬碟, 靠著部份破碎資訊, 總算搞定怎麼安裝。

pc9801 闘神都市2 以這方式免費開放, 需要先安裝到硬碟。

步驟有點類似 dos 時代下的安裝, 通常第一片軟碟片會有一個安裝程式, 執行她就可以。pc 9801 友善些, 插入軟碟, 從軟碟開機就會執行安裝程式。

不過因為是日文, 所以有些畫面看不懂怎麼處理, 還好有 ai 翻譯, 勉強看懂。需要一個能從 dos 開機的硬碟影像檔, 我懶得建立一個新的硬碟影像檔, 從 NEC PC-98 Windows 95 hard drive image 下載。

再來需要讓硬碟影像檔有 dos 開機能力, 需要執行 format, sys 這些指令, 和 pc/ms dos 指令類似, format 指令則有文字 ui 界面, 反而讓我不知怎麼選擇, 參考「PC-98 專欄 (介紹如何 format 硬碟/建立硬碟影像檔)」, 完成之後把闘神都市2 DISK01.HDM 載入到 A disk, C 載入這個硬碟影像檔開機, 重開 np2 之後, 就可以進入到安裝畫面。



這是所有操作的影片。有點複雜。

以下畫面是在沒建立硬碟影像檔時發出的一些畫面訊息。









dosbox-x 我用了類似的方式, 但是無法正常進入安裝畫面, 如 fig 11。有了安裝之後的闘神都市2 硬碟影像檔案, 之後改用 dosbox-x 玩這款遊戲, dosbox-x 的 save/load 功能比較豐富。

fig 11. dosbox-x 的錯誤訊息

安裝之後再次執行, 就會看到如 fig 1. 的開始畫面。

使用 dosbox-x 執行 闘神都市2。



ref:
アリスソフト 的遊戲開放下載

2025年3月28日 星期五

dosbox-x debugger

fig 1. window 10 dosbox-x debugger

tifa 很漂亮吧! 從「夜行侦探 EVE burst error 中文化分析」, 得知 dosbox-x 有內建 debugger, 我也想試著用這個 debugger。

linux 版的 dosbox-x 一直沒看到 debug 選單, 找了好久都不知道怎麼用, 原來預設的安裝套件沒有 debug 選單, 從 source code 編譯需要加上 --enable-debug
./configure --enable-sdl2 --enable-debug
這樣就可以看到如 fig 2 的 debug 選單。
fig 2. linux dosbox-x debugger

有點高估自己了, 就算有這個 debugger, 我還是無法看出怎麼追蹤遊戲程式碼, 真的難。在 dos 時代, 想要破解遊戲密碼, 當然也都沒成功過。



最後寫了一個小程式, 用來測試 dosbox-x debugger, 上述的影片跑得程式原本是一個變數檢查, 如果 val == 3, 就執行無窮迴圈, 有2種改法可以跳過這個迴圈, 把檢查的程式碼消去, 影片是用這個方法, 填入 90 90 這個 nop code, 就不會做檢查的 code。另外是把檢查 val 和 3 是不是相等改成 val 和 4 是不是相等, 由於 val 是 3, 因為不等於 4, 就不會一直無窮迴圈。

list 1
1 0813:00000945 67668B8354FFFFFF    mov  eax,[ebx-000000AC]     ds:[0CE8]=00000003
2 0813:0000094D 6683F803            cmp  eax,0003
3 0813:00000951 74F2                je   00000945 ($-e)         (up)
4
5 while(val == 3)
6 {
7 }

list 1 的組合語言相等於 L5 ~ 7 的 c++ 語言。離開這個 while loop, 就會印出 cpp_main。基本原理是這樣, 但真的要破解一個程式, 可沒有這麼容易, 繼續努力。

用起來的手感和 gdb 差很多, 不知道怎麼用比較順手。

2025年3月16日 星期日

[pc games] dosv/pc9801 rusty, 2D 橫向捲軸動作遊戲

the 1st edition: 20220908
the 2nd edition: 20250316
the 3rd edition: 20250320 破關
不知怎麼想起了 rusty 這個遊戲, 是 dos/v 下的 2D 橫向捲軸動作遊戲, 看到 dos/v 你也許想到什麼, 不過這個遊戲是正常的一般遊戲, 主角是位漂亮的女性。

由於是 dos/v 讓我有點傷腦筋, 不知道要怎麼執行 dos/v 環境, 最後找到了dosbox-x, 相當威, 可以處理日文問題。

本來我以為要設定為日文環境才可以執行 dos/v game, 不過看來不用, 語言設定可以參考: [教学] DOS+PC98模拟器DOSBox-X的中文语言支持功能较详细图文说明

安裝時選擇中文或是英文就可以, 一樣可以正確顯示 dos/v game 日文, 要改語言設定比較麻煩, 需要自己修改設定檔。

以下是日文環境的設定:

country     = 81,932
language = ja_JP.lng

安裝 dosbox-x 之後, 類似 dosemu, 使用 mount e d:\rusty 就可以把 windows 10 d:\rusty 掛到 dosbox-x e 槽。另外也有抓圖、錄影的功能, 真的蠻威的。

e: 
play

就可以執行遊戲。





女主角的武器是一條鞭子, 另外還有一個跳躍的按鈕, 再來就是標準的上下左右按鍵, 操作簡單, 畫面以當時的水準很精美, 就是走走打怪, 之後打 boss 這樣。跳躍和鞭子按鍵一起按下會放出遊戲中取得的武器。全部共10個關卡。

在 rusty 這款遊戲中, 玩家可以收集多種特別道具來幫助主角 rusty 在冒險中戰勝敵人和解決難題。以下是一些主要的道具及其功能:
道具介紹
黃色球:增加 Rusty 的心智力量(MP)1 點。
綠色球:增加 Rusty 的心智力量 10 點。
紅色球:由 Boss 擊敗後掉落,收集後可完成該關卡。
鑰匙:用於打開帶有相同標記的門。
美元袋:提供 1000 點的分數獎勵。
心形物品:完全恢復 Rusty 的生命值。
時鐘:增加剩餘時間 100 秒。
1UP:增加一條生命。
蝸牛:增加心智力量至 50 MP。
金魚:將剩餘時間增加至 500 秒。
小雞:提供 50000 點的分數獎勵。
此外,遊戲中還有一些特殊武器和技能,例如:
炸彈(Mind Slasher):對全屏敵人造成傷害,消耗 5 MP。
護盾(Shadow Dance):防禦敵人的攻擊,消耗 10 MP。
時間減緩:使敵人的動作變慢,消耗 5 MP。

道具武器有無敵、時間變慢、老鷹、環狀散開的光環 ...

可惜一樣的是我看不懂日文, 無法體會遊戲劇情, ref 1 可以 patch 成英文版本, 不過是 pc9801 的版本。另外 ref 1 也提供了英文手冊, 有按鍵的基本操作, 建議瀏覽一下, 也有一些人物設定畫面。

上+右/左 (右/左斜上) 可以快速跑步, 第一關需要用到這招。

購入 steam deck 之後, 我改用 steam deck 玩這個遊戲, 在掌機中玩, 別有另一番風味。在 dosbox-x 無法正確設定手把 (上下左右無法正常觸發), 在 dosbox 則是正常, 另外使用 ps4 手把, dosbox-x/dosbox 都可正常使用。



這個遊戲相當難, 有些關卡非常刁鑽, 第3關有一個需要用鞭子連續勾住2個點才能跳過去, 非常難, 透過 dosbox-s 的 save/load 大法, 我嘗試了三十幾次才跳過去, 鞭子必須要用最長的長度鉤上才行, 而這個關卡是主線關卡一定要過去, 並非是隱藏關卡路線, 相當麻煩。

雖然有 save/load 大法, 但是因為有時間倒數, 歸0之後還是一樣要死, 所以還是蠻難, 解關卡如果花掉太多時間, 就會遇到時間歸0的問題。



這是另外一個使用鞭子的關卡, 如果要跳到上方的平台, 相當難, 不過這是隱藏關卡, 可以得到一些寶物, 沒跳上去沒有關係, 而且還需要取得紅色菱形鑰匙才能進入。



每過一關可以選擇存檔, 之後選繼續遊戲就可以從存的關卡開始, 畫面是日文, 可能需要自己測試一下。

第2個選項就是存檔 (data save), 第一個選項是繼續遊戲

ref 2 提供的無敵修正檔, 在 dosbox-x/dosbox 下似乎無法使用, 只能靠 save/load 大法了。







20250320 "刺客教條:暗影者" 終於推出, 我也在這天破了 rusty, 有夠難。第9關卡是一直往上通往城堡的關卡, 螢幕會一直向上移動, 沒跟著往上走, 就會死, 而且機關也很刁鑽, 有些地方很不好跳躍。能把以前玩的遊戲破關, 感覺真的太好了, 好像終於把學生時期的代辦事項完成, 沒有遺憾。

有一關一直卡住, 看了通關影片才知道怎麼過, 隱藏的雕像鑰匙一直沒出現, 害我以為走錯路, 原來需要跳一下或是攻擊一下, 那個雕像才會出現。

以下為破關畫面:


ref 5 提供了下載, 有日文、英文 9801 版本, 以及 PC-98 – Anex86 Emulation, PC-98 – Neko Project II Emulation 教學, PC-98 – Neko Project II Emulation 的使用比較麻煩, 需要設定 bios, GDC 從 5MGz 改為 2.5MHz, 我在這邊卡了好久, 一直無法成功執行 rusty, 原來是要改 bios 設定。

選擇 neko reset 選單之後, 按下 End 按鍵, 就會進入 pc9801 bios 設定, 如下影片設定 GDC。



Anex86 不需要設定 bios 就可以執行 rusty。

ref:
  1. rusty 說明手冊/翻譯為英文的 patch
  2. [孤舟古早味遊戲談] Rusty (羅斯蒂) ~ 類惡魔城遊戲的女宗師
  3. Rusty butt fix
  4. 关于Rusty游戏的汉化 (IBM PC版)
  5. download Rusty DOS - 1993
  6. yahoo pc9801 rusty 二手 408915日元, 原價 9800 日元
  7. 其他玩家的破關影片