2024年5月16日 星期四

c++20 moudles support by g++

這是一個我很想用的特性, 沒有 header files 還能編譯, 真的厲害, 不知道怎麼辦到的, c++ 的魔法越來越多了。

主要以 chatgpt 為主, 再搭配 google 找資料, 終於知道怎麼編譯 module。
descent@deb64:cpp_module$ g++ --version
g++ (Debian 13.2.0-12) 13.2.0
main.cpp
 1 import functions;
 2 
 3 #include <iostream>
 4 #include <cstdio>
 5 
 6 int main() {
 7 #if 0
 8     int x = 10;
 9     int y = 5;
10 
11     std::cout << "x + y = " << add(x, y) << std::endl;
12     std::cout << "x - y = " << subtract(x, y) << std::endl;
13     std::cout << "x + y = " << add(10.5, 1.2) << std::endl;
14     //std::cout << "x * y = " << multiply(x, y) << std::endl;
15 #endif
16     Point p1 = createPoint(3, 4);
17     Point p2 = {1, 2};
18 
19     std::cout << "p1.x = " << p1.x << ", p1.y = " << p1.y << std::endl;
20     std::cout << "p2.x = " << p2.x << ", p2.y = " << p2.y << std::endl;
21     Line line{1,2};
22     line.print();
23     printMessage();
24     print_msg();
25     return 0;
26 }
math_functions.cpp
 1 export module functions;
 2 
 3 import <iostream>;
 4 
 5 export int int_add(int a, int b) {
 6     return a + b;
 7 }
 8 
 9 #if 0
10 export int subtract(int a, int b) {
11     return a - b;
12 }
13 
14 export int multiply(int a, int b) {
15     return a * b;
16 }
17 #endif
18 
19 export template<typename T>
20 T add(T a, T b) {
21     return a + b;
22 }
23 
24 export template<typename T>
25 T subtract(T a, T b) {
26     return a - b;
27 }
28 
29 // export module math_functions;
30 
31 export struct Point 
32 {
33     int x;
34     int y;
35 };
36 
37 export Point createPoint(int x, int y) 
38 {
39     return {x, y};
40 }
41 
42 export 
43 class Line
44 {
45   public:
46     Line(int x, int y):x_(x), y_(y)
47     {
48     }
49     void print()
50     {
51       printf("x_: %d, y_: %d\n", x_, y_);
52       std::cout << "x_: " << x_ << std::endl;
53     }
54   private:
55     int x_;
56     int y_;
57 };
58 
59 
60 
61 export void printMessage() 
62 {
63   std::cout << "Hello again from math_functions.cpp by iostream" << std::endl;
64 }
65 
66 export void print_msg() 
67 {
68   std::printf("Hello again from math_functions.cpp by printf!\n");
69 }
list 1 編譯指令
g++ -std=c++23 -fmodules-ts main.cpp math_functions.cpp -o math_program -x c++-system-header iostream -x c++-system-header cstdio

編譯之後會產生 gcm.cache 目錄。另外也有可能第一次編譯有錯, 再一次編譯就正常的情形, 所以有時候我搞不清楚是我寫錯還是編譯的問題。

測試了:
  1. class
  2. function
  3. template function
  4. 使用標準程式庫
這樣大概就可以來用 module 了, 不過這樣就不能從 header files 知道有哪些 function, class, class member function 可用, 一定得寫文件了吧! 原本的 include 還是可以用。

c++20_module
 1 g++ -std=c++23 -fmodules-ts main.cpp math_functions.cpp -o math_program -x c++-system-header iostream -x c++-system-header cstdio
 2 In module imported at main.cpp:1:1:
 3 functions: error: failed to read compiled module: No such file or directory
 4 functions: note: compiled module file is ‘gcm.cache/functions.gcm’
 5 functions: note: imports must be built before being imported
 6 functions: fatal error: returning to the gate for a mechanical issue
 7 compilation terminated.
 8 In module imported at math_functions.cpp:3:1:
 9 /usr/include/c++/13/iostream: error: failed to read compiled module: No such file or directory
10 /usr/include/c++/13/iostream: note: compiled module file is ‘gcm.cache/./usr/include/c++/13/iostream.gcm’
11 /usr/include/c++/13/iostream: note: imports must be built before being imported
12 /usr/include/c++/13/iostream: fatal error: returning to the gate for a mechanical issue
13 compilation terminated.
14 make: *** [makefile:2: math_program] Error 1
15 descent@debian-vm:cpp20_module$ make clean
16 rm math_program
17 rm: cannot remove 'math_program': No such file or directory
18 make: *** [makefile:4: clean] Error 1
19 descent@debian-vm:cpp20_module$ ls
20 gcm.cache  main.cpp  makefile  math_functions.cpp
21 descent@debian-vm:cpp20_module$ rm -rf gcm.cache/
22 descent@debian-vm:cpp20_module$ make
23 g++ -std=c++23 -fmodules-ts main.cpp math_functions.cpp -o math_program -x c++-system-header iostream -x c++-system-header cstdio
24 In module imported at main.cpp:1:1:
25 functions: error: failed to read compiled module: No such file or directory
26 functions: note: compiled module file is ‘gcm.cache/functions.gcm’
27 functions: note: imports must be built before being imported
28 functions: fatal error: returning to the gate for a mechanical issue
29 compilation terminated.
30 In module imported at math_functions.cpp:3:1:
31 /usr/include/c++/13/iostream: error: failed to read compiled module: No such file or directory
32 /usr/include/c++/13/iostream: note: compiled module file is ‘gcm.cache/./usr/include/c++/13/iostream.gcm’
33 /usr/include/c++/13/iostream: note: imports must be built before being imported
34 /usr/include/c++/13/iostream: fatal error: returning to the gate for a mechanical issue
35 compilation terminated.
36 make: *** [makefile:2: math_program] Error 1
37 descent@debian-vm:cpp20_module$ make
38 g++ -std=c++23 -fmodules-ts main.cpp math_functions.cpp -o math_program -x c++-system-header iostream -x c++-system-header cstdio
39 In module imported at main.cpp:1:1:
40 functions: error: failed to read compiled module: No such file or directory
41 functions: note: compiled module file is ‘gcm.cache/functions.gcm’
42 functions: note: imports must be built before being imported
43 functions: fatal error: returning to the gate for a mechanical issue
44 compilation terminated.
45 make: *** [makefile:2: math_program] Error 1
46 descent@debian-vm:cpp20_module$ make
47 g++ -std=c++23 -fmodules-ts main.cpp math_functions.cpp -o math_program -x c++-system-header iostream -x c++-system-header cstdio
48 In module imported at main.cpp:1:1:
49 functions: error: import ‘/usr/include/c++/13/iostream’ has CRC mismatch
50 main.cpp: In substitution of ‘template<class _CharT, class _Traits> std::basic_ostream<_CharT, _Traits>& std::operator<<(basic_ostream<_CharT, _Traits>&, const _CharT*) [with _CharT = char; _Traits = std::char_traits<char>]’:
51 main.cpp:19:18:   required from here
52 functions: error: failed to read compiled module cluster 15: Bad file data
53 functions: note: compiled module file is ‘gcm.cache/functions.gcm’
54 main.cpp:19:18: fatal error: failed to load pendings for ‘std::operator<<’
55    19 |     std::cout << "p1.x = " << p1.x << ", p1.y = " << p1.y << std::endl;
56       |                  ^~~~~~~~~
57 compilation terminated.
58 make: *** [makefile:2: math_program] Error 1
59 descent@debian-vm:cpp20_module$ make
60 g++ -std=c++23 -fmodules-ts main.cpp math_functions.cpp -o math_program -x c++-system-header iostream -x c++-system-header cstdio
61 descent@debian-vm:cpp20_module$ ls
62 gcm.cache  main.cpp  makefile  math_functions.cpp  math_program
63 descent@debian-vm:cpp20_module$ ./math_program 
ref:
C++20 Modules - 讓編譯加速吧 | C++ · 傳統與革新的空間

2024年5月9日 星期四

[pc game] 性感戰士

性感戰士是一個卡牌戰鬥 + 冒險遊戲, 角色都是女學生。精訊出品的 18 禁遊戲, 不確定那時候是不是有這種遊戲分類, 店家看起來也是可以正常販售。不知道從什麼時候開始, 精訊的遊戲都會附上一張小海報, 在盜版風行的年代, 這些小週邊可以鼓勵玩家購買正版遊戲。精訊在 2023 還存在, 不過似乎不是遊戲業務, 好像改作其他業務了。

現在在 steam 上買遊戲, 都忘記有盜版保護的東西, 在這個年代, 一般都是用密碼保護, 遊戲手冊會有一節是密碼, 當遊戲出現密碼時, 就要去找手冊輸入正確的密碼, 真的很煩人。但有一種更讓人討厭, 那就是磁片保護, 磁片由於很脆弱, 基本上都會備份一份, 用備份的磁片玩, 但磁片保護用了特殊格式的磁片, 需要讓程式檢查那個特殊點, 才會繼續進行遊戲, 所以要需要一直插入那片原本磁片, 磨損的機會也變大。



畫面上的圖以現在 (2023) 眼光來說, 當然是太粗糙, 不過整個遊戲畫風覺得還不錯, 女孩子們都蠻可愛的, 戰鬥也都是和女學生戰鬥, 滿滿的女孩味。



卡片戰鬥特色是「脫」這個卡片, 衣服被脫光就算輸, 但脫了衣服, 戰鬥力會提昇, 這2個數值得好好平衡一下, 要不然很容易在還沒讓對方脫完衣服就被打死。

卡片很特別的是沒用到亂數, 也就是每一回合的卡片和敵人出的卡片都是一樣的, 本來這不可能知道的, 但科技在進步, dosbox-x 可以隨時存檔, 所以我可以回朔到前一個回合, 就被我發現這樣的祕密, 然後就可以用對應的卡片對付敵人, 好瞎!



另外也有解謎的內容, 需要自己解開一些小謎題讓遊戲繼續下去。

http://chiuinan.github.io/game/game/intro/ch/c21/sexy.txt
保護:顏色圖案密碼,破解碼:
      GAME.EXE (先用LZEXE解壓)
        83 3E AD 02 00 75 20
        -- -- -- -- -- EB --
        8B 46 E8 9E 74 04 B2 00
        -- -- -- -- EB -- -- --
        B2 01 8A C2 B4 00 0B C0 75 0B (共4處)
        -- -- -- -- -- -- -- -- EB --
      或
        83 3E AD 02 00 75 20
        -- -- -- -- -- EB --
        FF 06 AD 02 B0 01
        C7 -- -- -- 00 00
      或
        9E 74  (改前5組)
        -- EB
        AD 02 00 75 20
        -- -- -- EB --
性感戰士 ~ 攻略 & 密技
【密技】
  死而復生法 在一樓會議室有一位未被妖魔控制的學生會長『莎莎』,先故意攻擊
              她,讓她將你海K一頓,當生命值應該降到零時卻會回歸成999,如此
              一來應該就可以輕易過關了。

 電話密技  進入遊戲後按下Scrool Lock鍵,再輸入精訊公司的電話號碼395142X ,
              X 以任何0到9的數字取代,就會有不同的效果出現。例如X的值為5, 
              螢幕上的奇奇便會張口大笑,並且主角的攻擊力、防禦力、恥值都將
              高達999、經驗值也會高達999, 如此一來, 敵人將成「肉腳」。


遊戲其實有點難, 如果沒有練功, 連3樓以上都無法存活, 更不用說要在3樓以上調查事件, 我最後受不了還是開密技了。

性 感 戰 士 --- 密技

ref:
開發性感戰士這款遊戲的一些回憶

2024年5月3日 星期五

東立的 2024 電子版漲價

東立的電子版從 202403 開始漲價, 有非常高的漲幅, 之後應該不會購入了, 之前因為價格便宜, 還願意購入一些有點興趣的, 要是調漲之後的價格, 紙本、電子應該都不會購入。我自己不太能接受電子版和紙本書價差不大 (台灣漫畫家的作品也許還會購入電子版), 可能會導致我重新從二手市場找書, 或是不買一些娛樂型的書籍。

202405 的漲價有好消息也有壞消息, 好消息是之後不會再漲價了; 壞消息是: 剩下的全漲了。

202403 初時, 想看的野球太保沒有漲價, 還是 55, 但是七龍珠彩色版比克大魔王從定價是 165, 這個漲很多, 因為之前已經買了其他篇 (定價 105), 只好忍痛一起收, 目前還剩下少年篇 1-8 電子版還沒收。

結果野球太保 20240320 從 55 漲到 80, 這個漲幅實在太扯, 紙本打完折是 85, 這樣的對比我就不想買電子版了, 野球太保還差 58 ~ 71, 還要把電子書不能當二手書賣的成本也算進去, 80 實在太貴, 東立可以發個聲明, 到底為什麼會漲這麼多, 也許還能讓讀者接受。

fig 20. 電子版 80, 紙本版 70, 20240402 的價錢

fig 20 列出了野球太保紙本比電子版還便宜, 也許有人可以接受, 不過電子書沒有實體印刷、沒有擁有權、無法二手賣出, 這些成本不反應在書價說不過去。有句話說:「做生意的人不要好處全拿。」總要給消費者一些回饋, 更不要說沒有裡封、折頁、目錄、和落後紙本書的出版時間。當然我沒算進運費, 不過運費不能算進書價應該沒什麼問題。

202404 東立又繼續再漲一波, 真的搞不懂這個操作。真想知道東立漲價之後的銷售情形。美食獵人列入這次的漲價, 本來還在考慮 55 元是不是要購買, 漲價後的 80 就完全不會考慮了。之後只能挑真的想要才買, 而不是便宜也可以買買看的心態去購買了。覺得是讀者和出版社雙輸的商業策略。

ref:

2024年4月26日 星期五

兌換台灣大哥大電子發票

台灣大哥大寄了實體信件通知電子發票中獎, 202312 帳單中獎, 會需要 202312 帳單上的資料, 到 7-11 印出中獎發票。

麻煩的是我沒 202312 帳單, 都是從台灣大哥大 app 繳款, 所以要印出中獎發票有點麻煩。

從 mail 的 qr code 連到 https://ow.tstarcs.taiwanmobile.com/TWM/electronicInvoice.php, 裡頭有發票資訊, 找出中獎的那期, 以我的例子是 202401 那期。記住載具號碼, 那個可以在 7-11 ibon 印出電子發票; 另外還需要載具類別, 台灣大哥大固定是 EJ0118, 這些資訊當期帳單都有, 所以沒有帳單會比較麻煩,
拿到 7-11 兌換會有 2 元印花稅。

2024年4月21日 星期日

借閱電子版漫畫 - hyread/台灣雲端書庫

hyread app 可以借閱圖書館書籍, 來試試看。加入某個圖書館, 需要有帳號, 之前有申請借書證就應該會有。



最近開始借閱圖書館電子漫畫, 覺得好像不用自己買電子版, 閱讀體驗和自己買的電子版完全一樣, 不知道圖書館的電子版是不是永久存在, 還是也會下架。反正購買的電子書也不能擁有, 在圖書館借閱也不會擁有電子書, 實在沒動機購買電子版本。



目前會借閱漫畫和雜誌的部份, 漫畫和雜誌還勉強可以用 LCD 螢幕看; 書籍還是用 e-ink 螢幕看比較適合, 我沒辦法在 LCD 螢幕看大篇文字。

台灣雲端書庫 app 也可以借閱電子書, 一樣是用圖書館帳號登入。魔法使的 5, 6 hyread 不知道為什麼沒有, 台灣雲端書庫則是可以借閱。


ref:
電子書借閱新政上路 每人無上限且回饋補助作者

2024年4月12日 星期五

signal handle backtrace (0) - signal handle 如何 return

我不是無所不知, 只是剛好知道而已。
實做 c backtrace」介紹了怎麼實作 backtrace, 下一個議題是: 如果從 signal handle 執行 backtrace 的動作, 能正常追到源頭呼叫的人嗎?

應該可以吧, 你這麼認為, 覺得在 signal handle 執行 backtrace 和在一般 function 執行 backtrace 不應該有什麼不同才是。

在這之前可以先想想看, 你寫的程式要怎麼辦到在 signal 來時, 切斷正在執行的部份, 然後轉到 signal handle 執行, 最後還可以接回原本的地方執行。

先來看看當 signal handle 發動的時候, 整個處理流程是怎麼樣? 設計了一個小程式來做這樣的實驗, 按照 t1.c 的程式, 有 2 個 thread, 一個停在 L255 while, 一個停在 L384 while, 這時候 kill pid 會得到怎麼樣的 backtrace 呢?

list 1
main thread pid: 27873
main thread tid: 27873
123 thread pid: 27873
123 thread tid: 27874

list 1 列出 2 個 thread 的 pid, tid。一個是 main thread, 一個是用 pthread_create() 建立的 thread。gettid 用法在 t1.c L16 ~ L20。

list 2 by glibc backtrace()
如果 kill 27873 會得到 main thread 的 backtrace
print_backtrace -> sig_handler -> __restore_rt -> fun_223 -> fun_22 -> main -> __libc_start_main -> _start

如果 kill 27874 會得到 thread_1 thread 的 backtrace
print_backtrace -> sig_handler -> __restore_rt -> fun_123 -> fun_12 -> fun_1 -> start_thread -> __clone

list 2. 使用 glibc backtrace() 得到的結果。

如果是由我自己藉由 rpb 追朔 stack 就不是這樣, 少了一部分的資訊。list3 少了 list 2 紅色部份, fun_223, fun_123

list 3 by stack rbp
如果 kill 27873 會得到 main thread 的 backtrace
print_backtrace -> sig_handler -> __restore_rt -> fun_22 -> main -> __libc_start_main 

如果 kill 27874 會得到 thread_1 thread 的 backtrace
print_backtrace -> sig_handler -> __restore_rt -> fun_12 -> fun_1 -> start_thread 

對於這樣的結果覺得很不爽, 為什麼從 stack frame 無法追朔完整, 而 backtrace() 可以。原因很複雜, 晚點再說。

先來看看為什麼有一個沒出現的 function - __restore_rt。

t1.c
  1 #include <signal.h>
  2 #include <pthread.h>
  3 #include <errno.h>
  4 #include <stdlib.h>
  5 #include <stdio.h>
  6 #include <string.h>
  7 #include <sys/types.h>
  8 #include <stdint.h>
  9 
 10 //#define _GNU_SOURCE
 11 #include <unistd.h>
 12 
 13 #include <unistd.h>
 14 #include <sys/syscall.h>
 15 
 16 #ifndef SYS_gettid
 17 #error "SYS_gettid unavailable on this system"
 18 #endif
 19 
 20 #define gettid() ((pid_t)syscall(SYS_gettid))
 21 
 22 /* Simple error handling functions */
 23 
 24 #define handle_error_en(en, msg) \
 25                do { errno = en; perror(msg); exit(EXIT_FAILURE); } while (0)
 26 
 27 #include <execinfo.h>
 28 
 29 #if 1
 30 #define BT_BUF_SIZE 100
 31 
 32 int addr2func(uintptr_t addr)
 33 {
 34   char cmd[128] = {0};
 35   sprintf(cmd, "addr2line -f -e ./t1 %#lx\n", addr);
 36   printf("cmd: %s\n", cmd);
 37   system(cmd);
 38   return 0;
 39 }
 40 
 41 #if 1
 42 uintptr_t get_rip_value() 
 43 {
 44   //uintptr_t rip_value;
 45   //asm volatile("movq $0, %%rax; movq (%%rsp), %%rax" : "=a" (rip_value));
 46   //asm("mov %%rip, %0" : "=r" (rip_value));
 47   uintptr_t current_address;
 48   asm("lea (%%rip), %0" : "=r" (current_address));
 49   return current_address;
 50 }
 51 
 52 unsigned long get_rbp()
 53 {
 54   unsigned long rbp_value;
 55   asm("movq %%rbp, %0" : "=r" (rbp_value));
 56   printf("The value of RBP register is: %#lx\n", rbp_value);
 57   return rbp_value;
 58 }
 59 
 60 #endif
 61 
 62 void print_backtrace() 
 63 {
 64     void *buffer[BT_BUF_SIZE];
 65     char **strings;
 66     int nptrs;
 67 
 68     nptrs = backtrace(buffer, BT_BUF_SIZE);
 69     printf("backtrace() returned %d addresses\n", nptrs);
 70 
 71     strings = backtrace_symbols(buffer, nptrs);
 72     if (strings == NULL) {
 73         perror("backtrace_symbols");
 74         exit(EXIT_FAILURE);
 75     }
 76 
 77     for (int i = 0; i < nptrs; i++) 
 78     {
 79       char cmd[128]={0};
 80       printf("nn %s\n", strings[i]);
 81       addr2func( (uintptr_t)buffer[i]);
 82       #if 0
 83       //printf("aa %#x\n", buffer[i]);
 84       sprintf(cmd, "/usr/bin/addr2line -f -e ./t123 %p\n", buffer[i]);
 85       //printf("cmd: %s\n", cmd);
 86       FILE *fs = popen(cmd, "r");
 87       if (fs)
 88       {
 89         char data[128];
 90         fread(data, 1, 128, fs);
 91         printf("data: %s\n", data);
 92       }
 93       pclose(fs);
 94       #endif
 95       
 96 
 97     }
 98 
 99     free(strings);
100 }
101 #else
102 
103 
104 #if 0
105 #define UNW_LOCAL_ONLY
106 #include <libunwind.h>
107 #endif
108 
109 
110 
111 void print_backtrace() {
112     unw_cursor_t cursor;
113     unw_context_t context;
114 
115     // 获取当前线程的上下文
116     unw_getcontext(&context);
117     // 初始化游标以便从当前位置开始遍历堆栈帧
118     unw_init_local(&cursor, &context);
119 
120     // 遍历堆栈帧
121     while (unw_step(&cursor) > 0) {
122         unw_word_t offset, pc;
123         char sym[256];
124 
125         // 获取指令指针位置
126         unw_get_reg(&cursor, UNW_REG_IP, &pc);
127         // 获取调用指令的偏移量
128         unw_get_proc_name(&cursor, sym, sizeof(sym), &offset);
129 
130         printf("  0x%lx: (%s+0x%lx)\n", (long)pc, sym, offset);
131     }
132 }
133 
134 #endif
135 
136 void sig_handler(int signo) 
137 {
138   int level = 0;
139   printf("in sig_handler\n");
140   #if 1
141 
142   //printf ("Caller name: %p\n", __builtin_return_address(0));
143   print_backtrace();
144 
145   #if 1
146   printf("in sig_handler pid: %d\n", getpid());
147   printf("in sig_handler tid: %d\n", gettid());
148   if (signo == SIGINT)
149     printf("Received SIGINT\n");
150   #endif
151   #endif
152   #if 1
153     {
154       uintptr_t current_address;
155       asm("lea (%%rip), %0" : "=r" (current_address));
156       printf("current_address : %#lx\n", current_address);
157       addr2func(current_address);
158 
159       printf("======\n");
160 
161       unsigned long rbp_value, previous_rbp;
162       asm("movq %%rbp, %0" : "=r" (rbp_value));
163       printf("%d ## The value of RBP register is: %#lx\n", level, rbp_value);
164       ++level;
165 
166       uintptr_t ret_addr = *(uintptr_t*)(rbp_value + sizeof(uintptr_t));
167       printf("ret_addr : %#lx\n", ret_addr);
168 
169       addr2func(ret_addr);
170 
171       printf("======\n");
172 
173       rbp_value = *(uintptr_t*)(rbp_value);
174       printf("%d ## The value of RBP register is: %#lx\n", level, rbp_value);
175       ++level;
176 
177       ret_addr = *(uintptr_t*)(rbp_value + sizeof(uintptr_t));
178       addr2func(ret_addr);
179 
180 
181       printf("======\n");
182 
183       rbp_value = *(uintptr_t*)(rbp_value);
184       printf("%d ## The value of RBP register is: %#lx\n", level, rbp_value);
185       ++level;
186 
187       ret_addr = *(uintptr_t*)(rbp_value + sizeof(uintptr_t));
188       addr2func(ret_addr);
189 
190       printf("======\n");
191 
192       rbp_value = *(uintptr_t*)(rbp_value);
193       printf("%d ## The value of RBP register is: %#lx\n", level, rbp_value);
194       ++level;
195 
196       ret_addr = *(uintptr_t*)(rbp_value + sizeof(uintptr_t));
197       addr2func(ret_addr);
198 
199       printf("======\n");
200 
201       previous_rbp = rbp_value;
202       
203       rbp_value = *(uintptr_t*)(rbp_value);
204       printf("%d ## The value of RBP register is: %#lx, previous_rbp: %#lx\n", level, rbp_value, previous_rbp);
205       ++level;
206 
207       if (rbp_value > previous_rbp)
208       {
209         ret_addr = *(uintptr_t*)(rbp_value + sizeof(uintptr_t));
210         addr2func(ret_addr);
211       }
212       else
213       {
214         printf("top stack frame\n");
215       }
216 
217       printf("======\n");
218 
219 
220     void* frame_address = __builtin_frame_address(0);
221     if (frame_address)
222       printf("Frame 0 address of f3: %p\n", frame_address);
223 
224     void* return_address = __builtin_return_address(0);
225     if (return_address)
226       printf("Return 0 address of f3: %p\n", return_address);
227     }
228     #endif
229 }
230 
231 static void * sig_thread (void *arg)
232 {
233   sigset_t *set = arg;
234   int s, sig;
235 
236   printf("sig thread pid: %d\n", getpid());
237 
238   for (;;)
239     {
240       s = sigwait (set, &sig);
241       if (s != 0)
242  handle_error_en (s, "sigwait");
243       printf ("Signal handling thread got signal %d\n", sig);
244     }
245     return 0;
246 }
247 
248 static void fun_123(void *arg)
249 {
250   sigset_t *set = arg;
251   int s, sig;
252   printf("123 thread pid: %d\n", getpid());
253   printf("123 thread tid: %d\n", gettid());
254 
255   while(1)
256   {
257     //printf("123\n");
258     
259  
260 #if 0 
261    for (;;)
262      {
263        s = sigwait (set, &sig);
264        if (s != 0)
265          handle_error_en (s, "sigwait");
266        printf ("Signal handling thread got signal %d\n", sig);
267        print_backtrace();
268      }
269 #endif
270   }
271 }
272 
273 static void fun_12(void *arg)
274 {
275   fun_123(arg);
276 }
277 
278 static void * fun_1(void *arg)
279 {
280   //for (;;)
281   {
282     fun_12(arg);
283   #if 0
284     printf("11 sig thread pid: %d\n", getpid());
285     printf("11 sig thread tid: %d\n", gettid());
286   #endif
287   }
288   return 0;
289 }
290 
291 static void fun_223()
292 {
293   while(1)
294   {
295     //printf("223\n");
296   }
297 }
298 
299 static void fun_22()
300 {
301   fun_223();
302 }
303 
304 static void * fun_2(void *arg)
305 {
306   struct sched_param schedp;
307 
308   memset(&schedp, 0, sizeof(schedp));
309   schedp.sched_priority = 39;
310   sched_setscheduler(0, SCHED_RR, &schedp);
311 
312   for (;;)
313   {
314     fun_22();
315   #if 0
316     printf("22 sig thread pid: %d\n", getpid());
317     printf("22 sig thread tid: %d\n", gettid());
318   #endif
319   }
320   return 0;
321 }
322 
323 int main(int argc, char *argv[])
324 {
325   //pthread_t thread;
326   pthread_t thread_1;
327   //pthread_t thread_2;
328 
329   //printf ("Caller name: %p\n", __builtin_return_address(0));
330   printf("main: %p, fun_1: %p, fun_2: %p\n", main, fun_1, fun_2);
331 
332 
333   struct sigaction sa;
334   sa.sa_handler = sig_handler;
335   sigemptyset(&sa.sa_mask);
336   sa.sa_flags = 0;
337 
338   //signal(SIGUSR1, sig_handler);
339 
340 
341 #if 1
342     //if (sigaction(SIGINT, &sa, NULL) == -1) {
343     if (sigaction(SIGUSR1, &sa, NULL) == -1) {
344         perror("sigaction");
345         exit(EXIT_FAILURE);
346     }
347 #endif
348   sigset_t set;
349   int s;
350 
351   /* Block SIGQUIT and SIGUSR1; other threads created by main()
352      will inherit a copy of the signal mask. */
353 
354   printf("pid: %d\n", getpid());
355 
356 #if 0
357   sigemptyset (&set);
358   //sigaddset (&set, SIGQUIT);
359   sigaddset (&set, SIGUSR1);
360   s = pthread_sigmask (SIG_BLOCK, &set, NULL);
361   if (s != 0)
362     handle_error_en (s, "pthread_sigmask");
363 #endif
364 
365 #if 0
366   s = pthread_create (&thread, NULL, &sig_thread, (void *) &set);
367   if (s != 0)
368     handle_error_en (s, "pthread_create");
369 #endif
370 
371   s = pthread_create (&thread_1, NULL, &fun_1, (void *) &set);
372   if (s != 0)
373     handle_error_en (s, "pthread_create 1");
374 
375 #if 0
376   s = pthread_create (&thread_2, NULL, &fun_2, (void *) &set);
377   if (s != 0)
378     handle_error_en (s, "pthread_create 2");
379 
380   /* Main thread carries on to create other threads and/or do
381      other work */
382 #endif
383   //pause();
384   while(1)
385   {
386     printf("main thread pid: %d\n", getpid());
387     printf("main thread tid: %d\n", gettid());
388     fun_22();
389     #if 0
390     printf("main thread pid: %d\n", getpid());
391     printf("main thread tid: %d\n", gettid());
392     #endif
393   }
394   //pause ();   /* Dummy pause so we can test program */
395 }

這個 __restore_rt function 的內容很簡單, 如 list 5, 一個 system call, 查了一下, 參考 list 6 - rt_sigreturn, man rt_sigreturn 可以知道其內容, 英文不好懂是吧, 讓 chatgpt 翻譯一下, 就容易理解了。

當 kernel 要執行 signal handle 要怎麼做呢? 現在的程式可是執行在 while 的地方, 有什麼辦法可以改到 t1.c L136 void sig_handler(int signo) 執行呢? 不好想吧, 當 t1 process 睡覺時要再次執行之前, kernel 做了手腳, 改動 rip, 讓 t1 process 從 sig_handler 執行, 當然也要在 stack frame 做一些操作, 否則 sig_handler 執行時, 會用到原本的 rsp, 也許會有問題, 一般會建立一個新的 stack frame 讓 sig_handler 在這邊操作 stack, 就不會影響到其他 function 的 stack frame。

當要從 signal handle 回到原本執行的地方 (此例子是回到 while) 要怎麼做呢? 有 2 種作法, signal handle return 時, stack return address 塞 while 的位址, 這樣就回去了。不過 linux kernel 不是這麼做, 用了一個奇特的作法。

一樣是安插了一個 return address, 在 signal handle return 之後會跑去執行 __restore_rt, 然後執行 rt_sigreturn system call 回到 kernel, 這時候再把原本 t1 要執行的 while 回復, 等到下次執行 t1 時, 就從 while 開始執行。

list 5 __restore_rt
1 0000000000405940 <__restore_rt>:
2   405940:       48 c7 c0 0f 00 00 00    mov    $0xf,%rax
3   405947:       0f 05                   syscall
4   405949:       0f 1f 80 00 00 00 00    nopl   0x0(%rax)

list 6 https://filippo.io/linux-syscall-table/
%rax	Name	Manual	Entry point
15	rt_sigreturn	rt_sigreturn(2)	sys_rt_sigreturn

至於為什麼自己寫的 backtrace 查不到 fun_123, 簡單來說就是 __restore_rt 的 return address 不是 fun_123, 而是 fun_123 的上一層 fun_12, 自然從 stack frame 找不到 fun_123 這個資訊, 那為什麼 glibc backtrace 找得到呢? 因為它用了別的方法, 下一篇再來介紹這個魔法。

ref:

2024年4月7日 星期日

ff7 remake mod on steam deck

很久之前就一直很想用 mod, 但都找不到方法, 直到發現以下影片後終於知道要怎麼用 mod。



因為我玩 steam 遊戲幾乎都是用 steam deck, 怕這些 mod 在 steam deck/linux 上很難搞, 還好運氣不錯, 成功在 steam deck/linux 上執行這些 mod, 以下安裝路徑是以 steam deck 為主。影片介紹的是以 windows 路徑為主。

ff7-reamek mod 網址: https://www.nexusmods.com/finalfantasy7remake, 需要註冊才能下載 mod。

FFVIIHook 1.7-74-1-7-1708987498

2022/01/11  下午 06:13             1,480 Engine.ini
2024/02/27  上午 06:23            25,088 xinput1_3.dll
放到 /home/deck/.local/share/Steam/steamapps/common/FINAL FANTASY VII REMAKE/End/Binaries/Win64
(deck@steamdeck Win64)$ ls "/home/deck/.local/share/Steam/steamapps/common/FINAL FANTASY VII REMAKE/End/Binaries/Win64"
Engine.ini  ff7remake_.exe  vkd3d-proton.cache  xinput1_3.dll


mod 檔案放到 /home/deck/.local/share/Steam/steamapps/common/FINAL FANTASY VII REMAKE/End/Content/Paks/

我依照影片建議建立了 ~mods, /home/deck/.local/share/Steam/steamapps/common/FINAL FANTASY VII REMAKE/End/Content/Paks/~mods, 也和影片一樣先用潔西測試, 成功了。建議先照影片流程把
  1. 前置模組: FFVIIHook - INI and dev console unlocker
  2. 潔西模組: Hot Pink Jessie
安裝測試好, 再來玩其他模組。



/home/deck/.local/share/Steam/steamapps/common/FINAL FANTASY VII REMAKE/End/Content/Paks/~mods

我把 ~mods 改成 0-mods 有些 mod 會失效, 所以還是都用 ~mods, 看起來好像是個不成文規定的命名, 有些 mod 還會相依其他 mod, 比較麻煩。

另外也把存檔位置找出來, 30 個存檔不夠用, 只好自己備份。

save files: /home/deck/.local/share/Steam/steamapps/compatdata/1462040/pfx/drive_c/users/steamuser/Documents/My Games/FINAL FANTASY VII REMAKE/Steam/76561198098462161

設定成人 mod:
基本上從這邊進去 site preferences (右上角的按鈕)

Content blocking
Adult content
勾選 Show adult content

[模組MOD] 模組網站nexusmods,開啟成人內容,以及分享其他不重要的小事
[模組MOD] 艾莉絲裸裝 - FFVII-重製版
Tifa 4K Hi-Poly Nude Mod (tifa 裸體)

測試了好多 mod, 最後還是選國王的新衣。



綠意公園 tifa, aerith 那段, 短短2分鐘動畫效果超棒! 同時可以看到 tifa, aerith, 可惜不好放上來。

使用這些遊戲 mod 還蠻有門檻的, 不是無腦就可以搞定, 要花時間查資料。

在 steam deck 上操作不像 windows 方便, 除非你接鍵盤滑鼠外接螢幕又進入桌面模式, 我是用 ssh 登入, 直接打指令操作, 另外可以使用 symbolic link 就不用一直搬移檔案, 如 list 5。

list 5
(deck@steamdeck aerith)$ ls -l
lrwxrwxrwx 1 deck deck 82 Apr  7 09:56 zAerithBunny.pak -> '/home/deck/ff7-org-mods/mods/Aerith Bunny Girl-868-1-0-1649964294/zAerithBunny.pak'

另外由於路徑很長, 用了 symbolic link 縮短路徑, 如 list 6。

list 6
(deck@steamdeck ~)$ ls -l ff7-remake
lrwxrwxrwx 1 deck deck 55 Apr  5 11:17 ff7-remake -> '.steam/steam/steamapps/common/FINAL FANTASY VII REMAKE/'
(deck@steamdeck ~)$ ls -l ff7-paks
lrwxrwxrwx 1 deck deck 28 Apr  5 12:40 ff7-paks -> ff7-remake/End/Content/Paks/
fig 8. ff7-remake mod on steam deck

以下的表情很有趣, cloud 看到 aerith 打扮的很漂亮的表情 (正常男人看到女人裸體應該都是這表情); aerith 看到 cloud 打扮的很漂亮的表情。

fig 5 aerith 在按摩館的打扮 fig 6 看到aerith 在按摩館的打扮後的神情

fig 7 aerith 看到 cloud 女裝的神情 fig 8 cloud 女裝




Bee costume mod 蜜蜂裝也不錯, 玩起來不尷尬。

ref:
【情報】FF7R PC版MOD始動 (2022/06/18 更新STEAM版設定與控制台支援 )