Mccranky's Blog

Turn On, Boot Up, Jack In

Code snippet:

1
2
3
4
5
6
7
8
9
#!/usr/bin/env -S bash

hello="hello:world:hello:world:hello"

IFS="lo:"

read -ra list <<<"$hello"

printf "%s*" "${list[*]}"
  1. Double quotes
  2. Bundling into an array
  3. Array expansion re-splitting

Double quotes

If $hello is wrapped with "", its literal value will be preserved. Without them, IFS characters will be displayed as blanks spots.

With Without
hello:world:hello:world:hello he(4 spaces)w r d he(4 spaces)w r d he

Bundling into an array

read -ra list cares not for whether the string is wrapped in quotes. It will split the string into an array regardless.

However, mind that the read command reads one line at a time. If the string is multiline, read will only read the first line.

Array expansion re-splitting

Both ${list[*]} and ${list[@]} are prone to re-splitting the array.

IFS \ printf "%s*" <..> ${list[*]} ${list[@]} "${list[*]}" "${list[@]}"
IFS="lo:" he****w*r*d*he****w*r*d*he** (1) (1) hellllwlrldlhellllwlrldlhell* (2) he****w*r*d*he****w*r*d*he*** (3)
IFS= he*w*r*d*he*w*r*d*he* (4) (4) (2) (3)
Observation
  • IFS="lo:"
    1. * acts like a delimiter, and fills the gap between each array element. Eg: hel * l * o. Only behaves like this when the last element contains a single delimiter, else the result is the same as (3).
    2. The first character of IFS, which happens to be l, serves as the delimiter for the array elements. The whole string is treated as one element, thus * is appended to the very end.
    3. * acts normally and is appended to each array element. Eg: hel * l * o *
  • IFS=: The effect this has on word splitting seems to be that it squeezes multiple blank arguments into one.

Converting characters to uppercase(/lowercase)

Take variable sayhi for example:

1
2
sayhi="hello there"
echo ${sayhi^^[eo]}

This method of converting lowercase letters to uppercase only works in Bash and will throw and “Bad substitution” error if you try it in Zsh.
Instead, you should use general replacement for this in Zsh:

1
echo ${sayhi//[eo]/[EO]}

Or, for a more Zsh native method, we can try out substitution flags, which comes in two formats:

1
2
3
echo ${sayhi:u}
# Or use it as a prefix:
echo ${(U):sayhi}

To delete a superuser in Django, you can use the Django shell to query and delete the user model instance. Here are the steps to delete a superuser using the Django shell:

  1. Open a terminal and navigate to the root directory of your Django project.

  2. Activate the virtual environment, if you are using one.

  3. Run the following command to open the Django shell: python manage.py shell.

  4. Import the User model by running the following command: from django.contrib.auth.models import User.

  5. Query the User model for the superuser you want to delete using the get() method. For example, if the username of the superuser you want to delete is “admin”, you would run: user = User.objects.get(username='admin').

  6. Call the delete() method on the user instance to delete the superuser. For example, you would run: user.delete().

  7. Exit the Django shell by typing exit() or pressing Ctrl+D.

After following these steps, the superuser will be deleted from the Django project.

It’s important to note that deleting a superuser will also delete all related data, such as blog posts or comments, if those models are related to the User model with a foreign key. If you want to preserve this data, you should first transfer ownership of the data to another user before deleting the superuser.

Another way to delete a superuser is to use the Django admin. To do this, log in as a superuser in the Django admin, navigate to the “Users” section, and click the “Delete” button next to the superuser you want to delete. Confirm the deletion in the next screen.

In summary, you can delete a superuser in Django by using the Django shell to query and delete the user model instance. Alternatively, you can use the Django admin to delete the superuser. Keep in mind that deleting a superuser will also delete any related data.

因为本人使用的操作系统是 OS X ,所以一下书写规范均符合 POSIX(Portable Open System Interface eXtended),Windows 用户请自行查阅资料。

path.basename(path[, suffix])

1
2
path.basename('/path/to/project/index.html', '.html')
// 'index'

path.delimiter

1
2
3
console.log(process.env.PATH)
// /opt/homebrew/opt/llvm/bin:/opt/homebrew/opt/llvm/bin:/Applications/Sublime Text.app/Contents/SharedSupport/bin/:/Applications/Visual Studio Code.app/Contents/Resources/app/bin/:/Users/Mccranky/.gem/ruby/3.1.3/bin:/Users/Mccranky/.rubies/ruby-3.1.3/lib/ruby/gems/3.1.0/bin:/Users/Mccranky/.rubies/ruby-3.1.3/bin::/opt/local/bin:/opt/homebrew/bin:/opt/homebrew/sbin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin:/usr/local/mysql/support-files:/usr/local/mysql/bin:/etc/platform-tools:/usr/local/go/bin:/usr/local/MacGPG2/bin:/usr/local/share/dotnet:~/.dotnet/tools:/Library/Apple/usr/bin:/Users/Mccranky/.nami/bin:/Users/Mccranky/.spicetify
// undefined

输出第一行为环境变量。
输出第二行的undefined示意无返回值。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
process.env.PATH.split(path.delimiter)
/*
[
'/opt/homebrew/opt/llvm/bin',
'/opt/homebrew/opt/llvm/bin',
'/Applications/Sublime Text.app/Contents/SharedSupport/bin/',
'/Applications/Visual Studio Code.app/Contents/Resources/app/bin/',
'/Users/Mccranky/.gem/ruby/3.1.3/bin',
'/Users/Mccranky/.rubies/ruby-3.1.3/lib/ruby/gems/3.1.0/bin',
'/Users/Mccranky/.rubies/ruby-3.1.3/bin',
'',
'/opt/local/bin',
'/opt/homebrew/bin',
'/opt/homebrew/sbin',
'/usr/local/bin',
'/usr/bin',
'/bin',
'/usr/sbin',
'/sbin',
'/usr/local/mysql/support-files',
'/usr/local/mysql/bin',
'/etc/platform-tools',
'/usr/local/go/bin',
'/usr/local/MacGPG2/bin',
'/usr/local/share/dotnet',
'~/.dotnet/tools',
'/Library/Apple/usr/bin',
'/Users/Mccranky/.nami/bin',
'/Users/Mccranky/.spicetify'
]
*/

path.delimiter: :

path.dirname

1
2
path.dirname('path/to/file.txt')
// 'path/to'

path.extname

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
path.extname('index.html')
// '.html'

path.extname('index.foo.md')
// '.md'

path.extname('index.')
// '.'

path.extname('index')
// ''

path.extname('.index')
// ''

path.extname('.index.md')
// '.md'

path.format(pathObject)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
// `${dir}${path.sep}${base}`
path.format({
root: '/ignored',
dir: '/home/user/dir',
base: 'file.txt',
})
// '/home/user/dir/file.txt'

path.format({
root: '/',
base: 'file.txt',
ext: 'ignored',
})
// '/file.txt'

path.format({
root: '/',
name: 'file',
ext: '.txt',
})
// '/file.txt'

path.format({
root: '/',
name: 'file',
ext: 'txt',
})
// '/file.txt'

path.isAbsolute

1
2
3
4
path.isAbsolute('/foo/bar') // true
path.isAbsolute('/baz/..') // true
path.isAbsolute('qux/') // false
path.isAbsolute('.') // false

path.join([...paths])

1
2
3
4
5
path.join('')
// '.'

path.join('/', '/foo', 'bar', 'baz', '..')
// '/foo/bar'

path.normalize(path)

1
2
path.normalize('/foo/bar//baz/asdf/quux/..')
// Returns: '/foo/bar/baz/asdf'

path.parse(path)

1
2
3
4
5
6
7
8
9
10
path.parse('/home/user/dir/file.txt')
/*
{
root: '/',
dir: '/home/user/dir',
base: 'file.txt',
ext: '.txt',
name: 'file'
}
*/
1
2
3
4
5
6
┌─────────────────────┬────────────┐
│ dir │ base │
├──────┬ ├──────┬─────┤
│ root │ │ name │ ext │
" / home/user/dir / file .txt "
└──────┴──────────────┴──────┴─────┘

path.posix

只是输出一下path模组所有的方法。

path.relative(from, to)

1
2
path.relative('/data/orandea/test/aaa', '/data/orandea/impl/bbb')
// '../../impl/bbb'

path.resolve([...paths])

1
2
3
4
5
path.resolve('')
'/Users/Mccranky'

path.join('foo', 'bar', 'baz', '..')
// '/Users/Mccranky/foo/bar'

path.sep

path.sep: /

1
2
'foo/bar/baz'.split(path.sep)
// ['foo', 'bar', 'baz']

While a clever person may have a strong intuition, their insights may only benefit themselves. On the other hand, a learned person who has gained knowledge through studies has the ability to express their ideas and contribute to society.

Intuition is often described as a gut feeling or a hunch that arises without conscious reasoning. It is a valuable skill, but it can be limited by personal biases and experiences. In contrast, knowledge gained through education and study is based on evidence and critical thinking. This allows learned individuals to make informed decisions and contribute to the advancement of society.

References:

  • The Catcher in the Rye: Mr. Antolini Quotes in Chapter 24

  • Steve Jobs: “Intuition is a very powerful thing, more powerful than intellect, in my opinion.”

A late addition to the stack, but by all accounts no less authentic. This is a real account of what transpired in Shanghai during the great lockdown. I, for one, was away at the time, locked up at school in a different city. Nonetheless, ripples of the cataclysmic effects had reached me in that northern coast-lined city, sending waves of shock and grief, piercing my heart like daggers.

My mom’s account of those three months is a testament to her talent with words and positive outlook on life in general. She describes the despair and hopelessness that consumed many during that time, but also the resilience and strength of the human spirit. Her words paint a vivid picture of what life was like during the lockdown, and the following account is for anyone interested in gaining a deeper understanding of this human experience.

2022年4月8日,是丈夫和我因上海疫情在家禁足的第十一天。

微信群每天都有几百上千条信息:朝阳群众雪亮的双眼把偷溜下楼的人拍照发群,让其 “见光死”,果然后面几天下楼溜达的人几乎绝迹;把各区发放的食品发群,让群众们点评到底哪个区更“豪横”?将各种荒唐离谱的防疫搞笑视频发群,对其极尽调侃之能事;最重要的是,街道居委的各类通知也由楼组长发布在群里,实施无漏洞的网格化管理。每天每日每个楼洞排着队整齐划一去做核酸检测,真乃“威武之师”也!

随着封控天数的增加,每家每户存粮逐渐耗尽。微信群里搞笑调侃的少了,群情激昂点评时事的少了,取而代之的是各种团购群组。从米面油蔬菜水果到婴儿奶粉纸尿裤,应有尽有。此时此刻,没有什么比一桶油一盒鸡蛋更重要的了。也有幸运儿在群里炫耀自己在各类APP上拼手速抢到的食物图片,普普通通的一块奶油蛋糕就足以让人垂涎欲滴。望着每日重复的食物和日渐的清空冰箱,我有点心动了。

当晚,我就在手机上下载了四个外卖应用:美团、叮咚买菜、每日优鲜、盒马。先看美团,发现“你所在的小区不在配送范围”。再看每日优鲜,很多东西是“缺货”,好不容易找到有货的,鸡蛋10个29.8元,狠狠心把它加入购物车,点击购买时,发现仍然需要在早上六点抢,当下不能生成订单,我如释重负,毫不犹豫将这“绿色有机”的鸡蛋删除了。叮咚买菜、盒马鲜生也都是需要在早上六点拼手速抢下单,浏览了一遍,东西普遍价格高,也谈不到质优,本就抱着试试看心态的我干脆放弃了。

回过来审视我自己的情况:目前家中存粮还够吃,挨饿是肯定不会的。这得益于我的一个好习惯:经常清点家中存货,一旦缺少,马上补足三个月的量。2020年武汉疫情时,由于对病毒的无知而产生的恐惧,我有过大半个月没有下楼的经历,一切全靠网购,且放足了余量。现在看来,未雨绸缪,广积粮是必要的。

如今,八旬老父老母在故乡身体健康,自强自立的两位老人把自己的生活安排得妥妥帖帖,不要子女操半点心;儿子离开上海在大学正常地学习生活,为着自己的目标在每天努力;海外的家人们安居乐业,捷报频传。身无后顾之忧的我俩一身轻松:我精打细算烧菜做饭,丈夫风花雪月吟诗作对,禁足的日子过得并不乏味且充满诗意。

惟愿疫情的阴霾尽快散去,我爱的家人们能很快相聚,上海重现东方明珠的风采!


On April 8th, 2022, it was the eleventh day of being confined at home in Shanghai due to the pandemic. Every day, there were hundreds or even thousands of messages in our WeChat groups. Some groups had photos of people sneaking out of their homes, which were then shared to shame them. This resulted in fewer people sneaking out in the following days. Other groups shared information about food distribution in different districts, and we would compare which district had better supplies. There were also many funny videos related to the pandemic that were shared, which made us laugh and helped to relieve some of the stress.

As the days went by, our food supplies began to dwindle. The funny videos and discussions about current events became less frequent in the groups, and were replaced by various group-buying options. Everything from rice, noodles, oil, vegetables, fruits, to baby formula and diapers were available. At this time, nothing was more important than having a bucket of oil and a box of eggs. Some lucky people would show off the food they had managed to buy from various apps, and even a simple piece of cake would make us drool.

That night, I downloaded four food delivery apps on my phone: Meituan, Dingdong Maicai, MissFresh, and Hema. I checked Meituan first, but my neighborhood was not within their delivery range. Then I tried MissFresh, but many items were out of stock. I finally found eggs, but they were priced at 29.8 yuan for a pack of 10. When I tried to purchase them, I found out that I still needed to compete with others at 6am to place my order. I felt relieved that I didn’t have to buy them and deleted them from my cart. Dingdong Maicai and Hema also required me to compete with others at 6am to place my order, and the prices were generally high with no guarantee of quality. I decided to give up on them altogether.

Looking back at my situation, I realized that we still had enough food to eat, and we wouldn’t go hungry. This was thanks to my good habit of regularly checking our food supplies and stocking up on a three-month supply when we were running low. During the Wuhan pandemic in 2020, I was afraid of the virus and didn’t leave my home for almost two weeks. All of our supplies were ordered online and we had extra just in case. Now, it’s clear that preparing ahead of time and stocking up on food is necessary.

Currently, my elderly parents are healthy and living in their hometown, and they are self-sufficient and don’t require any assistance from their children. My son is away at university, working hard towards his goals, and my family members who live abroad are doing well. With no worries, my husband and I are enjoying our time at home during the lockdown. I cook and budget carefully, while my husband writes poetry and enjoys his hobbies. Although we are confined to our home, it’s not boring and we find joy in simple things.

We hope that the pandemic will soon be over, and that we can be reunited with our loved ones. We also hope that Shanghai can once again thrive and shine like the Oriental Pearl it is.

The Daily Challenge mode boasts a significant player base, even compared to other popular modes, and offers several exclusive weapons to boot. However, this is bad news for us freeloaders who, through various means, gained access to the game without ever paying the publisher a single cent. Some may consider piracy bad practice, but to be honest, I’m broke as f*ck.

Since we can’t do the challenge runs like the normal person would, I find it not much of a stretch to hack the game a bit further.

Overview

The approach we’re taking is to assign the blueprints to a chosen enemy. Naturally we’d want to assign it to an enemy we frequently run into and have a decent drop rate. It so happens that the zombie fits our description perfectly.

Let us have a look at the relevant part of its JSON description:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
"name": "Zombie",
"score": 2,
"maxPerRoom": 0,
"canBeElite": true,
"glowInnerColor": 11197250,
"volteDelay": 1,
"flesh1": 5669211,
"flesh2": 12303527,
"pfCost": 0.5,
"blueprints": [
{
"item": "HorizontalTurret",
"rarity": "Rare",
"minDifficulty": 0
},
{
"item": "Bleeder",
"rarity": "Always",
"minDifficulty": 0
},
{
"rarity": "Rare",
"minDifficulty": 1,
"item": "PrisonerBobby"
}
]

Bleeder is the internal name for Blood Sword, which is typically the first blueprint obtained by Dead Cells players on their initial run. As such, it can be readily exchanged for an item of our preference.

Blood Sword

Blood Sword

Targets:

  1. Swift Sword (internal name SpeedBlade) - first run
  2. Lacerating Aura (internal name DamageAura) - 5th run
  3. Meat Skewer (internal name DashSword) - 10th run

Visit the official wiki page for more information!

CellPacker

I use CellPacker to extract the data.cdb file so as to avoid having to read from the hexdump. It is a lot more comfortable to inspect an formatted JSON file!

CellPacker

CellPacker

We can open CellPacker by double-clicking CellPacker.jar, however I suggest running the following command to avoid headaches:

1
java -jar /path/to/CellPacker.jar

GitHub Repo: ReBuilders101/CellPacker

Click here to install CellPacker.jar.

res.pak

As the name suggests, the res.pak is the resource pack for the game itself and contains every aspect of what the graphics and cutscenes require to load. It also contains the json data files which store the underlying logic behind the interactions in the game.

To hack it, we need a hexdump editor. I use ghex because it’s fairly simple and easy to use. We can install it on our MacBook via Homebrew, using the following command:

1
brew install ghex

Note:
Before we begin the actual editing process, make sure you have a copy of your game saves and the resource pack we’re editing on. This is in case we make a mistake and corrupt the files.

ghex

Run ghex in Terminal and open res.pak. Locate Bleeder and replace it with the internal name of any of the three daily challenge blueprints.

Before

Please note that the resulting file must remain the same size as the original. Making significant changes will only complicate matters. For instance, Bleeder has 7 characters, while SpeedBlade and DamageAura have 10, and DashSword has 9. To address this discrepancy, I reduced the length of “Zombie” by the difference. Here’s an example:

After


References

Array of pointers to ints

Code:

1
2
3
4
5
6
7
8
9
10
11
12
13
#include <iostream>

int main()
{
int* ap[15];
int ai[15] = {};
for (int i = 0; i < 15; ++i) {
ai[i] = i;
ap[i] = &ai[i];
std::cout << *ap[i] << " " << ap[i]<< std::endl;
}
}

Console:

0 0x16d296d14
1 0x16d296d18
2 0x16d296d1c
3 0x16d296d20
4 0x16d296d24
5 0x16d296d28
6 0x16d296d2c
7 0x16d296d30
8 0x16d296d34
9 0x16d296d38
10 0x16d296d3c
11 0x16d296d40
12 0x16d296d44
13 0x16d296d48
14 0x16d296d4c

Pointer to an array of ints

Code:

1
2
3
4
5
6
7
8
9
10
11
12
#include <iostream>

int main()
{
int ai[15];
for (int i = 0; i < 15; i++) ai[i] = i;
int (*ptr)[] = &ai;
for (int i = 0; i < 15; i++) {
std::cout << (*ptr)[i] << " ";
}
}

Console

0 1 2 3 4 5 6 7 8 9 10 11 12 13 14

Notes

  • The parentheses around *ptr are necessary because the [] operator has
    higher precedence than the * operator. Without the parentheses, the
    declaration would be parsed as an array of pointers to int, like int
    *ptr[15].

  • When I took the address of the array, I got a pointer to the first element
    of the array. However, the pointer itself does not contain any information
    about the size of the array.

Pointer to function taking a string argument;

returns an string

Code:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#include <iostream>
#include <string>

void replaceX(std::string *str, std::string x)
{
size_t pos = (*str).find("${x}");
if (pos != std::string::npos)
(*str).replace(pos, 4, x);
}

std::string foo(std::string x)
{
std::string str = "hello this is ${x}";
replaceX(&str, x);
return str;
}

int main()
{
std::string (*fp)(std::string) = &foo;
std::cout << (*fp)("foo") << std::endl;
std::cout << (*fp)("bar") << std::endl;
}

Console:

hello this is foo
hello this is bar

In Bash, positional parameters are special variables that hold the arguments passed to a script or function. The positional parameters are numbered from 0 to 9, where $0 holds the name of the script or function, and $1 through $9 hold the first through ninth arguments, respectively.

$@ and $* both hold all the arguments passed to the script. Note that $@ is an array-like variable that holds all the positional parameters as separate elements, while $* is another variable that holds all the positional parameters as a single string.

Here’s an example:

#!/bin/bash

#!/bin/bash

echo "Script Name: $0"
echo "First Arg: $1"
echo "Second Arg: $2"
echo "All args (arr): $@"
echo "All args (str): $*"

Now let’s run the script with three arguments:

1
2
3
4
chmod u+x ./script # Grant execution privilege for user

./script a b c

The output should be something like this:

Script Name: ./script.sh
First Arg: a
Second Arg: b
All args (arr): a b c
All args (str): a b c
0%